首页 > 其他分享 >C语言指针完整总结!!!

C语言指针完整总结!!!

时间:2024-03-18 14:29:52浏览次数:18  
标签:总结 arr int void C语言 数组 sizeof 指针

1.指针介绍

1.简介:

C语⾔中给地址起 了新的名字叫:指针。

一个内存单元是一字节

内存单元的编号 == 地址 == 指针

在x86的环境中,一共有32根地址总线,即32个比特位。一个字节有八个比特位,而⼀个比特位可以存储⼀个2进制的位1或者0,因此32根地址线,就能表示2^32种含 义,每⼀种含义都代表⼀个地址,如果一个地址就是一个内存单元(一字节),那么这2^32种情况一共就是4GB,也就是很多电脑的运存。而要把这2^32个比特位存起来作为地址,则需要4个字节的内存。相同的,x64环境下,则需要8个字节的内存.

2.指针操作符

&-取地址操作符

例如:&a取出的是a所占4个字节中地址较小的字节的地址。

只要知道了小字节的地址,往后找即可找到目标变量的所有字节地址

*-解引⽤操作符

与取地址操作符作用相反,解引⽤操作符是把地址解引用为变量指向的内容

int a = 10;
int * pa = &a

在以上代码中,*说明了pa是一个指针,而int说明了pa指向的内容数据类型是int型的。

当然指针也有数据类型,上面例子的pa指针就是int*型的。

3.指针变量的大小

32位机器假设有32根地址总线,每根地址线出来的电信号转换成数字信号后 是1或者0,那我们把32根地址线产⽣的2进制序列当做⼀个地址,那么⼀个地址就是32个bit位,需要4 个字节才能存储。 如果指针变量是⽤来存放地址的,那么指针变的⼤⼩就得是4个字节的空间才可以。 同理64位机器,假设有64根地址线,⼀个地址就是64个⼆进制位组成的⼆进制序列,存储起来就需要 8个字节的空间,指针变的⼤⼩就是8个字节。

32位平台下地址是32个bit位,指针变量⼤⼩是4个字节 • 64位平台下地址是64个bit位,指针变量大小是8个字节 • 注意指针变量的⼤⼩和类型是⽆关的,只要指针类型的变量,在相同的平台下,大小都是相同的。

4.指针类型的意义

另外,指针的类型决定了,对指针解引⽤的时候有多⼤的权限(⼀次能操作⼏个字节)

⽐如: char* 的指针解引⽤就只能访问⼀个字节,⽽ int* 的指针的解引⽤就能访问四个字节。

在指针+-整数中,因为指针类型不同,+-整数跳过的字节数也不同

例如:char* 类型的指针变量+1跳过1个字节, int* 类型的指针变量+1跳过了4个字节。 这就是指针变量的类型差异带来的变化。

void*指针是泛型指针,是一种无具体类型的指针,一般在不确定指向内容的数据类型的时候使用它,得知类型后强制类型转换进行解引用或者+-整数操作,不强制类型转换就无法进行以上操作。

⼀般 void* 类型的指针是使⽤在函数参数的部分,⽤来接收不同类型数据的地址,这样的设计可以 实现泛型编程的效果,使得⼀个函数来处理多种类型的数据。

5.const修饰指针

结论:const修饰指针变量的时候 • const如果放在*的左边,修饰的是指针指向的内容,保证指针指向的内容不能通过指针来改变。 但是指针变量本⾝的内容可变。 • const如果放在*的右边,修饰的是指针变量本⾝,保证了指针变量的内容不能修改,但是指针指 向的内容,可以通过指针改变。

6.野指针

1.野指针成因

指针未初始化

指针越界访问

指针指向的空间释放

2.规避野指针的方法

指针初始化

防止指针越界

指针变量不再使⽤时,及时置NULL,指针使⽤之前检查有效性

避免返回局部变量的地址

7.assert断言

下面代码在程序运⾏到这⼀⾏语句时,验证变量 p 是否等于 NULL 。如果确实不等于 NULL ,程序 继续运⾏,否则就会终⽌运⾏,并且给出报错信息提⽰。

assert(p!=NULL);

使用assert函数需要加入assert.h的头文件

8.传值调用与传址调用

传值调用是函数只需要用到变量的值,因此传参是只要把传值即可,而传址调用是把参数的地址传参,这样就能从根本上操控变量,解决了临时的局部变量出函数就被销毁的问题

2.指针与数组之间的联系

1.数组名

大家都知道,数组名是数组首元素的地址,其实它本质上就是指针,指向数组首元素。

但它有两个例外:
• sizeof(数组名),sizeof中单独放数组名,这⾥的数组名表⽰整个数组,计算的是整个数组的⼤⼩,
单位是字节
• &数组名,这⾥的数组名表⽰整个数组,取出的是整个数组的地址(整个数组的地址和数组⾸元素
的地址是有区别的)

2.数组使用的指针方式

int arr[3][5]={0};
for(int i=0;i<3;i++)
{
    int j=0;
    for(j=0;j<5;j++)
   {
    arr[i][j]=1;
    //这里相当于是(*(arr+i)+j)

   }
}

在二维数组中,首元素指的是整个第一行的地址,进行+-操作跳过的字节数也是一整行的字节数,+i说明了跳到了第几行,解引用操作后确定了行数,+j又确定了列数,再次解引用就是具体的某行某列的具体位置。

可以理解是指针模拟二维数组的过程,下面会有讲

3.指针模拟二维数组

#include <stdio.h>
int main()
{
 int arr1[] = {1,2,3,4,5};
 int arr2[] = {2,3,4,5,6};
 int arr3[] = {3,4,5,6,7};
 //数组名是数组⾸元素的地址,类型是int*的,就可以存放在parr数组中
 int* parr[3] = {arr1, arr2, arr3};
 int i = 0;
 int j = 0;
 for(i=0; i<3; i++)
 {
 for(j=0; j<5; j++)
 {
 printf("%d ", parr[i][j]);
 }
 printf("\n");
 }

parr[i]是访问parr数组的元素,parr[i]找到的数组元素指向了整型⼀维数组,parr[i][j]就是整型⼀维数 组中的元素。与2的原理一样。

4.冒泡排序

冒泡排序的核⼼思想就是:两两相邻的元素进⾏⽐较。

void bubble_sort(int arr[], int sz)//参数接收数组元素个数
{
 int i = 0;
 for(i=0; i<sz-1; i++)
 {
 int flag = 1;//假设这⼀趟已经有序了
 int j = 0;
 for(j=0; j<sz-i-1; j++)
 {
 if(arr[j] > arr[j+1])
 {
 flag = 0;//发⽣交换就说明,⽆序
 int tmp = arr[j];
 arr[j] = arr[j+1];
 arr[j+1] = tmp;
 }
 }
 if(flag == 1)//这⼀趟没交换就说明已经有序,后续⽆序排序了
 break;
 }
}
int main()
{
 int arr[] = {3,1,7,5,8,9,0,2,4,6};
 int sz = sizeof(arr)/sizeof(arr[0]);
 bubble_sort(arr, sz);
 for(i=0; i<sz; i++)
 {
 printf("%d ", arr[i]);
 }
 return 0;
}

以上是优化后的完整代码

5.二级指针与指针数组

*ppa 通过对ppa中的地址进⾏解引⽤,这样找到的是 pa , *ppa 其实访问的就是 pa .

int b = 20;
*ppa = &b;//等价于 pa = &b;

**ppa 先通过 *ppa 找到 pa ,然后对 pa 进⾏解引⽤操作: *pa ,那找到的是 a .

**ppa = 30;
//等价于*pa = 30;
//等价于a = 30;

以下是指针数组:

本质上是数组,指向的每一个元素都是int*型的,即存放指针的数组,故也有char*arr[]等等

3.数组指针与函数指针

1.数组指针

与指针数组不同,数组指针是指向一整个数组的指针

例如它:

int (*p2)[10];

p先和*结合,说明p是⼀个指针变量变量,然后指着指向的是⼀个⼤⼩为10个整型的数组。所以 p是⼀个指针,指向⼀个数组,叫 数组指针。 这⾥要注意:[]的优先级要⾼于*号的,所以必须加上()来保证p先和*结合。

2.函数指针

函数名就是函数的地址,当然也可以通过 &函数名 的方式获得函数的地址。

举例:

void test()
{
 printf("hehe\n");
}
void (*pf1)() = &test;
void (*pf2)()= test;
int Add(int x, int y)
{
 return x+y;
}
int(*pf3)(int, int) = Add;
int(*pf3)(int x, int y) = &Add;/

3.函数指针数组

把函数的地址存到⼀个数组中,那这个数组就叫函数指针数组

//例如:
int (*p[3])(int,int)={func1,func2,func3}

说明了指向的函数有两个int的参数,返回值为int

4.转移表

#include <stdio.h>
int add(int a, int b)
{
 return a + b;
}
int sub(int a, int b)
{
 return a - b;
}
int mul(int a, int b)
{
 return a*b;
}
int div(int a, int b)
{
 return a / b;
}
int main()
{
 int x, y;
 int input = 1;
 int ret = 0;
 int(*p[5])(int x, int y) = { 0, add, sub, mul, div }; //转移表
 do
   {
   printf("*************************\n");
   printf(" 1:add 2:sub \n");
   printf(" 3:mul 4:div \n");
   printf(" 0:exit \n");
   printf("*************************\n");
   printf( "请选择:" );
   scanf("%d", &input);
   if ((input <= 4 && input >= 1))
   {
     printf( "输⼊操作数:" );
     scanf( "%d %d", &x, &y);
     ret = (*p[input])(x, y);
     printf( "ret = %d\n", ret);
   }
  else if(input == 0)
    {
    printf("退出计算器\n");
    }
    else
    {
    printf( "输⼊有误\n" ); 
    }
   }while (input);
 return 0;
}

4.qsort回调函数

1.使⽤qsort函数排序整型数据

#include <stdio.h>
//qosrt函数的使⽤者得实现⼀个⽐较函数
int int_cmp(const void * p1, const void * p2)
{
 return (*( int *)p1 - *(int *) p2);
}
int main()
{
 int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
 int i = 0;
 
 qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof (int), int_cmp);
 for (i = 0; i< sizeof(arr) / sizeof(arr[0]); i++)
 {
 printf( "%d ", arr[i]);
 }
 printf("\n");
 return 0;
}

2.使⽤qsort排序结构数据

struct Stu //学⽣
{
    char name[20];//名字
    int age;//年龄
};

//假设按照年龄来⽐较
int cmp_stu_by_age(const void* e1, const void* e2)
{
    return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}

//strcmp - 是库函数,是专⻔⽤来⽐较两个字符串的⼤⼩的
//假设按照名字来⽐较

int cmp_stu_by_name(const void* e1, const void* e2)
{
    return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}
//按照年龄来排序

void test2()
{
    struct Stu s[] = { {"zhangsan", 20}, {"lisi", 30}, {"wangwu", 15} };
    int sz = sizeof(s) / sizeof(s[0]);
    qsort(s, sz, sizeof(s[0]), cmp_stu_by_age);
}
//按照名字来排序

void test3()
{
    struct Stu s[] = { {"zhangsan", 20}, {"lisi", 30}, {"wangwu", 15} };
    int sz = sizeof(s) / sizeof(s[0]);
    qsort(s, sz, sizeof(s[0]), cmp_stu_by_name);
}


int main()
{
    test2();
    test3();
    return 0;
}

3.qsort函数的模拟实现

使⽤回调函数,模拟实现qsort(采⽤冒泡的⽅式)。

#include <stdio.h>

int int_cmp(const void * p1, const void * p2)
{
 return (*( int *)p1 - *(int *) p2);
}


void _swap(void *p1, void * p2, int size)
{
 int i = 0;
   for (i = 0; i< size; i++)
   {
     char tmp = *((char *)p1 + i);
     *(( char *)p1 + i) = *((char *) p2 + i);
     *(( char *)p2 + i) = tmp;
   }
}


void bubble(void *base, int count , int size, int(*cmp )(void *, void *))
{
 int i = 0;
 int j = 0;
    for (i = 0; i< count - 1; i++)
    {
       for (j = 0; j<count-i-1; j++)
       {
         if (cmp ((char *) base + j*size , (char *)base + (j + 1)*size) > 0)
         {  
           _swap(( char *)base + j*size, (char *)base + (j + 1)*size, size);
         }
       }
    }
}


int main()
{
    int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
    int i = 0;
    bubble(arr, sizeof(arr) / sizeof(arr[0]), sizeof (int), int_cmp);
     for (i = 0; i< sizeof(arr) / sizeof(arr[0]); i++)
     {
        printf( "%d ", arr[i]);
     }
     printf("\n");
      return 0;
}

5.strlen与sizeof的对比

sizeof 在学习操作符的时候,我们学习了 sizeof , sizeof 计算变量所占内存内存空间⼤⼩的,单位是 字节,如果操作数是类型的话,计算的是使⽤类型创建的变量所占内存空间的⼤⼩。 sizeof 只关注占⽤内存空间的⼤⼩不在乎内存中存放什么数据

strlen 是C语⾔库函数,功能是求字符串⻓度

6.结语

指针的总结就到这里,指针是学好c语言的核心,还有很多大大小小的知识点和细节值得大家不断的学习,修炼好指针的内功!!!

标签:总结,arr,int,void,C语言,数组,sizeof,指针
From: https://blog.csdn.net/2301_79139273/article/details/136805098

相关文章

  • 【GPT总结】Why Can GPT Learn In-Context?
    原文:https://ar5iv.labs.arxiv.org/html/2212.10559概述这篇论文提出了一种新的方法,利用大型预训练语言模型展示了惊人的上下文学习能力。通过少量的示范输入-标签对,它们可以在没有参数更新的情况下预测未见输入的标签。尽管在性能上取得了巨大成功,但其工作机制仍然是一个开放......
  • #c语言程序设计————实验报告
    实验项目名称:实验一熟悉C语言运行环境实验项目类型:验证性实验日期:2023年3月14日一、实验目的下载安装Devc6.0程序。了解在该系统上如何进行编辑、编译、连接和运行一个C程序。通过运行简单的C程序了解C程序的特点。二、实验硬、软件环境Windows计算机、Devc6.0三、......
  • C语言自定义类型:枚举(C语言进阶)
    目录前言1、枚举类型定义2、枚举的优点3、枚举的使用结语前言    本篇文章讲解C语言自定义类型:枚举类型。    枚举顾名思义就是一一列举,把可能的值一一列举。像一周的周一到周日可以枚举;每年12个月,可以枚举。1、枚举类型定义enumDay//星期{ Mo......
  • C语言最重要的知识点(6)
    第六章指针变量的本质是用来放地址,而一般的变量是放数值的。1、int *p中  *p和p的差别:简单说*p是数值,p是地址!*p可以当做变量来用;*的作用是取后面地址p里面的数值 p是当作地址来使用。可以用在scanf函数中:scanf(“%d”,p);2、*p++和(*p)++的之间的差别:改错题目中很重要......
  • C语言:渣女\0与被它区别对待的兄弟俩字符串与字符
    虽然这个拟人手法用的挺那啥,但是我觉得还挺贴切的。你觉得呢?欢迎浏览后发表自己的看法!目录一、字符(一)概念(二)实际操作二、字符串     (一)概念(二)实际操作三、\0对字符串特殊照顾,但对字符就不太行了四、小结一、字符(一)概念        其实字符理解起......
  • C语言:爱跳舞的转义字符——旋转,跳跃,我闭着眼
    转义字符,顾名思义:原来意思被转变的字符。比如0和\0。前者就是数字0,而后者被\这个符号垫起来后,就成了字符串的结束标志,代表没有内容。就好像\0在说:“旋转、跳跃我闭着眼,我已不在乎自己是个数字0。”目录一、概念二、一些常见的转义字符——让我们一起欣赏一场舞会盛宴......
  • c语言编程题
    1.密码破译要将"China"译成密码,译码规律是:用原来字母后面的第4个字母代替原来的字母。例如,字母"A"后面第4个字母是"E"."E"代替"A"。因此,"China"应译为"Glmre"。请编一程序,用赋初值的方法使cl、c2、c3、c4、c5五个变量的值分别为,’C’、’h’、’i’、’n’、’a’,经过运算,使c1、......
  • 深入了解指针(2)【指针与数组】【二级指针】
    一.使用指针访问数组关于数组,我在上次的博客中也提到了一些比较重要的知识点。首先就是数组名,数组名只有在两种情况下代表的是整个数组的地址,一个是在sizeof里面,一个是用&符号。那么为什么我们可以用指针来访问数组呢?因为数组在内存中是连续存放的,只要我们找到第一个地址,那么......
  • C++中的this指针、访问控制和构造函数
    C++中的this指针、访问控制和构造函数this指针在C++中,this指针是一个特殊的指针,它指向当前对象的地址。每个非静态成员函数(包括成员函数模板)都有一个this指针作为其隐含参数,这意味着在成员函数内部,this可以用来引用调用该成员函数的对象。this指针是自动传递给成员函数的,......
  • 排序算法01 - 插入排序 (C语言)
    原理将a[0]作为哨兵,然后从a[2]开始遍历数组,如果发现前者比后者大,则将后者存入哨兵,再从后向前调整数组元素的位置,最后将哨兵插入即可。图示代码#include<stdio.h>constintN=10010;inta[N];n;voidinsert_sort(inta[]){intj;for(inti=2;i<=n;i++){......