今儿个,给大家分享c语言中的指针的相关内容,希望对大家学习c语言能有所帮助。

目录
c语言指针其实是一个整形变量,与其它数据不同的是,它的作用是用来存储其它变量的地址。指针是编程语言中的一个对象,利用地址,它的值直接指向存在电脑存储器中另一个地方的值。由于通过地址能找到所需的变量单元,可以说,地址指向该变量单元。因此,将地址形象化的称为“指针"。意思是通过它能找到以它为地址的内存单元,指针是内存中一个最小的单元的编号。指针的大小是固定的4/8个字节(32位平台/64位平台)。
可以通过&(取地址操作符)取出变量的内存其实地址,把地址可以存放到一个变量中,这个变量就是指针变量。
- int main()
- {
- int a = 25;//在内存中开辟一块空间
- int* pa = &a;//这里我们对变量a,取出它的地址,可以使用&操作符。
- //a变量占用4个字节的空间,这里是将a的4个字节的第一个字节的地址存放在pa变量中,pa就是一个之指针变量。
- printf("%p\n", &a);//打印地址
- char ch = 'w';//在内存中开辟一块空间
- char* pc = &ch;//这里我们对变量ch,取出它的地址,也可以使用&操作符。
- //ch变量占用1个字节的空间,这里是将ch的第一个字节的地址存放在pc变量中,pc就是一个之指针变量。
- printf("%p\n", &ch);//打印地址
- return 0;
- }
从我们监视所得的图中可知,pa作为指针变量,它里面存放的是a的地址,而它也有自己的地址。
- int main()
- {
- int a = 25;
- int* pa = &a;
- return 0;
- }

(1)、在32位的机器上,地址是32个0或者1组成二进制序列,那地址就得用4个字节的空间来存储,所以一个指针变量的大小就应该是4个字节。
(2)、在64位机器上,如果有64个地址线,那一个指针变量的大小是8个字节,才能存放一个地址。

| char * pa = NULL |
| int * pb = NULL |
| short * pc = NULL |
| long * pd = NULL |
| float * pe = NULL |
| double * pf = NULL |
当我们看到以下两段代码以及相应的截图的时候,我们会发现,同样都是把a的地址存放在指针变量当中,但是对于 int* 的指针解引用访问4个字节,而对于 char* 的指针解引用只访问1个字节,从而得出指针类型是有意义的。
意义1:指针类型决定了进行解引用操作的时候权限有多大;
- int main()
- {
- int a = 0x11223344;
- int* pa = &a;
- *pa=0;//重点在调试的过程中观察内存的变化
- return 0;
- }

- int main()
- {
- int a = 0x11223344;
- char* pc = &a;
- *pc = 0;//重点在调试的过程中观察内存的变化
- return 0;
- }

可以从下面的代码和截图中得出第二个意义:
意义2:指针类型决定了指针的步长(向前/向后 走一步走多大距离);
- int main()
- {
- int a = 0x11223344;
- int* pa = &a;
- char* pc = &a;
-
- printf("pa=%p\n", pa);
- printf("pc=%p\n", pc);
-
- printf("pa+1=%p\n", pa + 1);//int* 指针+1,意思是跳过一个整型,也就是向后走4个字节
- printf("pc+1=%p\n", pc + 1);//char* 指针+1,意思是跳过一个字符,也就是向后走1个字节
-
- return 0;
- }

小试牛刀:编写代码用指针的方法将数组初始化为1 ~ 10
- int main()
- {
- int arr[10] = { 0 };
- int* pa = arr;
- int i = 0;
- for (i = 0; i < 10; i++)
- {
- *(pa + i) = i + 1;
- printf("%d ", *(pa + i));
- }
- return 0;
- }
- int main()
- {
- int* p;//p就是野指针
- *p = 10;
- return 0;
- }
- int main()
- {
- int arr[5] = { 0 };
- int* p = arr;
- int i = 0;
- for (i = 0; i <= 6; i++)
- {
- //当指针指向的范围超出数组arr的范围时,p就是野指针
- *(p++) = i;
- }
- return 0;
- }
当进入text函数的时候创建变量num,向内存申请一段空间,而出去之后就被释放,就把这段空间还给操作系统。回到主函数的时候记住了这段地址,则 p 就是野指针。
- #include
- int* text()
- {
- int num = 100;
- return #
- }
- int main()
- {
- int* p = text();
- *p = 200;
- return 0;
- }
- int main()
- {
- int a = 10;
- int* pa = &a;//明确初始化
-
- //NULL - 0 ,这也是初始化指针的
- int* p = NULL;
- return 0;
- }
- int main()
- {
- int *p = NULL;
- int a = 10;
- p = &a;
- if(p != NULL)
- {
- *p = 20;
- }
- return 0;
- }
- int main()
- {
- float arr[4] = { 0 };
- float* pa = arr;
- int i = 0;
- for (i = 0; i < 4; i++)
- {
- *(pa + i) = i + 1;
- printf("%lf ", *(pa + i));
- }
- return 0;
- }
指针与指针相减的前提是,指针指向的是同一块连续的空间。指针与指针相减求的是相差的元素个数。
- int main()
- {
- int arr[5]={0,1,2,3,4};
- printf("%d\n", arr[4] - arr[0]);
- return 0;
- }
小试牛刀:用指针-指针的方法求字符串的长度:
- int my_strlen(char* str)
- {
- char* start = str;
- while (*str != '\0')
- str++;
- return str - start;
- }
-
- int main()
- {
- char arr[] = "abcdefg";
- int len = my_strlen(arr);
- printf("%d\n", len);
- return 0;
- }
- //将数组中的每一个元素都置为0
- int main()
- {
- int values[5]={1,2,3,4,5};
- int* vp;
- for (vp = &values[5]; vp > &values[0];)
- {
- *--vp = 0;
- printf("%d ", *vp);
- }
- return 0;
- }
- for(vp = &values[N_VALUES-1]; vp >= &values[0];vp--)
- {
- *vp = 0;
- }
数组和指针不是一个东西,数组能够存放一组数,是一段连续的空间,数组的大小取决于元素的个数;而指针是一个变量,是存放地址的。它们之间的联系是:数组名是地址,数组把首元素的地址交给一个指针变量后,可以通过指针来访问数组。
- //用指针数组的方式打印数组中的内容
- int main()
- {
- int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
- int* p = arr;
- int i = 0;
- int sz = sizeof(arr) / sizeof(arr[0]);
- for (i = 0; i < sz; i++)
- {
- printf("%d " , *p );
- p++;
- }
- return 0;
- }
- int main()
- {
- int a = 10;
- int* p = &a;//p是一级指针变量
- int** pp = &p;//pp是二级指针变量
- return 0;
- }

对于二级指针的运算有:
(1)、*pp 通过对pp中的地址进行解引用,这样找到的是 p , *pp 其实访问的就是 p 。
- int a = 20;
- *pp = &a;//等价于 p = &a
- int* arr1[5];
- //arr1是一个数组,有五个元素,每个元素是一个整型指针
- char *arr2[4];
- //一级字符指针的数组
- char **arr3[5];
- //二级字符指针的数组
- //用法:用一维数组模拟二维数组
- #include
- int main()
- {
- int arr1[] = { 1,2,3,4,5 };
- int arr2[] = { 2,3,4,5,6 };
- int arr3[] = { 3,4,5,6,7 };
- int arr4[] = { 4,5,6,7,8 };
-
- int* arr[4]={arr1, arr2, arr3, arr4};
- int i = 0;
- for (i = 0; i < 4; i++)
- {
- int j = 0;
- for (j = 0; j < 5; j++)
- {
- printf("%d ",arr[i][j]);
- }
- printf("\n");
- }
- return 0;
- }

①:&tm 是取出 tm 的地址,可以把它存在 ps 中,而 ps 变量的类型为 char* 。
- char tm = 'w';
- char* ps = &tm;//①
①:把字符串的首元素(a)的地址存在pa中(提示:这样写在某些编译器下可能会出现报错,假如用const修饰将不会出现报错的情况);
②:这种写法是错误的,会发生报错(如图所示),因为常量字符串不可以修改;
- char* pa = "abcdef";//①
- *pa = 'w';//②

①:const 修饰指针,放在 * 的左边限制的是 *pa,这样就不能改变 pa 中的字符串内容了。
②:如果用const修饰指针的时候又想改变字符串中的值是实现不了的,这样一句代码根本编译不过去,是一句无效代码;
- const char* pa = "abcdef";//①
- *pa = 'w';//②
①:如果想改变字符串的内容,必须给他一个可以改变的空间,如语句1所示。
②:数组名就是数组的首元素地址;
- char arr[] = "abcdef";//①
- char* p = arr;//②
小试牛刀:以下代码段打印的结果是?
- #include
- int main()
- {
- char str1[] = "hello bit.";
- char str2[] = "hello bit.";
- const char* str3 = "hello bit.";
- const char* str4 = "hello bit.";
- if (str1 == str2)
- printf("str1 and str2 are same\n");
- else
- printf("str1 and str2 are not same\n");
- if (str3 == str4)
- printf("str3 and str4 are same\n");
- else
- printf("str3 and str4 are not same\n");
- return 0;
- }
代码解析:代码中的str3和str4指向的是一个同一个常量字符串,从内存优化的角度来说,会把常量字符串存储到单独的一个内存区域,当几个指针指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以str1和str2不同,str3和str4不同,在这里要注意的是它们比较的不是字符串中的内容,而是比较存储时的地址;如果要比较字符串的内容,要使用strcmp。

以下代码为整型指针、字符指针以及数组指针之间的类比。
- int main()
- {
- //整型指针 - 指向整型的指针 - 存放整型变量的地址
- int a = 10;
- int* p1 = &a;
- //int* - 整型指针的类型
- //字符指针 - 指向字符的指针 - 存放字符变量的地址
- char ch = 'w';
- char* p2 = &ch;
- //char* - 字符指针的类型
- //数组指针 - 指向数组的指针 - 存放的是数组的地址
- int arr[5] = { 1,2,3,4,5 };
- int(*pa)[5] = &arr;//取出的是数组的地址存放在pa中,pa是数组指针变量
- //int(*)[5] - 数组指针类型
- return 0;
- }
下面的代码段是对上述某语句的解释:
- int (*pa)[5];
- //解释:pa先和*结合,说明pa是一个指针变量,然后指着指向的是一个大小为5个整型的数组。所以pa是一个
- 指针,指向一个数组,叫数组指针。
- //这里要注意:[]的优先级要高于*号的,所以必须加上()来保证p先和*结合。
- int main()
- {
- int arr[5] = { 0 };
- printf("%p\n", arr );//int*
- printf("%p\n", arr+1 );
-
- printf("%p\n", &arr[0] );//int*
- printf("%p\n", &arr[0]+1 );
-
- printf("%p\n", &arr );//int(*)[5]
- printf("%p\n", &arr+1 );
- return 0;
- }

根据上面的代码我们发现,其实&arr和arr,虽然值是一样的,但是意义应该不一样的。 实际上: &arr 表示的是数组的地址,而不是数组首元素的地址。例中 &arr 的类型是: int(*)[5] ,是一种数组指针类型 数组的地址+1,跳过整个数组的大小,所以 &arr+1 相对于 &arr 的差值是20。
- #define _CRT_SECURE_NO_WARNINGS
- #include
-
- void print1(int(*p)[5], int r, int c)//二维数组的首元素地址为二维数组的第一行,arr是二维数组的数组名,数组名又是首元素的地址,所以arr是第一行的地址,所以p指向的是二维数组的第一行,它表示第一行的地址
- {
- int i = 0;
- for (i = 0; i < r; i++)
- {
- int j = 0;
- for (j = 0; j < c; j++)
- {
- printf("%d ", * (*(p + i) + j));
- //p+i相当于第i行的地址,对它解引用后拿到了第i行{ *(p + i) ---> p[i] 又相当于第i行的数组名,数组名又相当于首元素的地址 }
- //+j表示地址偏移,向前或者向后移动,这时候拿到的是地址,解引用后就能拿到该地址上的元素了
- }
- printf("\n");
- }
- }
-
- void text1()
- {
- int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };
- print1(arr, 3, 5);
- }
-
- int main()
- {
- text1();
- return 0;
- }
- int arr[5];
- //整型数组
-
- int *sarr1[10];
- //指针数组
-
- int (*sarr2)[10];
- //数组指针
-
- int (*sarr3[10])[5];
- //数组指针类型,saar3是存放【数组指针】的数组
数组传参:形式参数可以是数组,也可以是指针。
- #include
- void test(int arr[])//---> 形参是数组,可以不写大小
- { }
- void test(int arr[10])//---> 形参是数组,也可以写大小,但是没什么用处
- { }
- void test(int *arr)//---> 形参是整型指针,也可以这样写
- { }
- void test2(int *arr[20])//---> 形参是指针数组,写法正确,可以省略掉20
- { }
- void test2(int **arr)//---> 形参是二级指针,书写正确
- { }
-
- int main()
- {
- int arr[10] = {0};//整型数组
- int *arr2[20] = {0};//一维整型指针数组(arr2有20个元素,每个元素的类型是int*)
- test(arr);
- test2(arr2);
- }
- void test(int arr[3][5])//---> 形参为数组
- { }
- void test(int arr[][])//---> 形参为数组,写法错误,可以省略列,不能省略行
- { }
- void test(int arr[][5])//---> 形参为数组,列号虚和main函数中的一样
- { }
- void test(int *arr)//---> 形参为指针,书写错误,类型不匹配
- { }
- void test(int* arr[5])//---> 书写错误
- { }
- void test(int (*arr)[5])//---> 书写正确
- { }
- void test(int **arr)//---> 书写错误,二级指针接收一级指针变量的地址
- { }
-
- int main()
- {
- int arr[3][5] = {0};//二维数组
- test(arr);
- }
- #include
- //指针传参,指针接收
- void print(int *p, int sz)
- {
- int i = 0;
- for(i=0; i
- {
- printf("%d\n", *(p+i));
- }
- }
-
- int main()
- {
- int arr[10] = {1,2,3,4,5,6,7,8,9};
- int *p = arr;
- int sz = sizeof(arr)/sizeof(arr[0]);
- //一级指针p,传给函数
- print(p, sz);
- return 0;
- }
9.4、二级数组传参
- #include
- void test(int** qq)
- {
- printf("num = %d\n", **qq);
- }
-
- int main()
- {
- int n = 20;
- int*p = &n;//一级指针
- int **pp = &p;//二级指针
- test(pp);
- test(&p);
- return 0;
- }
当函数的参数为二级指针的时候,可以接收什么参数?
- void test(char **p)
- {
-
- }
-
- int main()
- {
- char c = 'b';
- char*pc = &c;
- char**ppc = &pc;
- char* arr[10];
- test(&pc);
- test(ppc);
- test(arr);
- return 0;
- }
十、函数指针
不言而喻,函数指针就是指向函数的指针。
- #include
- Add(int x, int y)
- {
- return x + y;
- }
-
- int main()
- {
- printf("%p\n", Add);
- printf("%p\n", &Add);
- return 0;
- }

从代码打印的结果来看,可以知道 Add 和 &Add都是函数的地址,没有去区别。那我们如何用函数指针调用Add函数呢?
- #include
- Add(int x, int y)
- {
- return x + y;
- }
-
- int main()
- {
- int (*pa)(int x, int y) = &Add;
- int ret = (*pa)(3, 5);//*可以去掉,加在这是为了容易理解
- printf("%d\n", ret);
- return 0;
- }
代码分析:
- //代码1
- (*(void (*)())0)();
- //void (*)()---> 函数指针类型
- //(void (*)())0---> 把0当作一个函数的地址(强制类型转换)
- //(*(void (*)())0)---> 解引用,找到0地址处的函数
- //最后面的()---> 调用0地址处的函数,这个函数是没有参数
- /*整体来说,它的意思是“把0直接转换成一个void(*)()的函数指针,然后再去调用0地址处
- 的函数,解引用的*也可以省略掉”*/
注意:再日常写代码的过程中,我们不要写这种代码哦,是容易出错误的。
- //代码2
- void (*signal(int , void(*)(int)))(int);
- /*signal(int , void(*)(int))--->signal为函数名,他一共有两个参数,第一个参数类型为为int,
- 第二个参数为为函数指针*/
- //void ( * ……)(int)--->这部分说明signal的参数返回类型为函数指针
- /*总体的意思是:“上述代码是一次函数声明,声明的函数叫做signal,函数的第一个参数是int类型的,
- 第二个参数是一个函数指针类型,该函数指针指向的函数的参数是int,返回类型是void,signal函数的
- 返回类型也是一个函数指针类型,该函数指针指向的函数的参数是int,返回类型是void。*/
- //typedf可以把一个复杂的名字重新定义
- void (*signal(int , void(*)(int)))(int);
- 上面的代码可以写成下边两句代码:
- typedf void(*pf_t)(int);
- //typedf void(*)(int)pf_t;这种写法是错误的,但是大家可能都会这么想(pf_t就是个类型)
- pf_t signal(int,pf_t);
十一、函数指针数组
要把函数的地址存到一个数组中,那这个数组就叫函数指针数组。下面代码段显示的是函数指针数组定义的形式:
int (*pfArr1[10])(int,int);
下面展示一段利用函数指针数组的相关代码,计算器(整数的加减乘除):
- int Add(int x, int y)
- {
- return x + y;
- }
-
- int Sub(int x, int y)
- {
- return x - y;
- }
-
- int Mul(int x, int y)
- {
- return x * y;
- }
-
- int Div(int x, int y)
- {
- return x / y;
- }
-
-
- void menu()
- {
- printf("***************************\n");
- printf("***** 1.add 2. sub ****\n");
- printf("***** 3.mul 4. div ****\n");
- printf("***** 0.exit ****\n");
- printf("***************************\n");
- }
-
- int main()
- {
- int input = 0;
- int x = 0;
- int y = 0;
- int ret = 0;
-
- int (*pfArr[])(int, int) = { 0, Add, Sub, Mul, Div };
-
- do
- {
- menu();
- printf("请选择:>");
- scanf("%d", &input);
- if (input == 0)
- {
- printf("退出计算器\n");
- break;
- }
-
- if (input >= 1 && input <= 4)
- {
- printf("请输入2个操作数:>");
- scanf("%d %d", &x, &y);
- ret = pfArr[input](x, y);
- printf("%d\n", ret);
- }
- else
- {
- printf("选择错误\n");
- }
- } while (input);
- }
十二、指向函数指针数组的指针
指向函数指针数组的指针是一个
指针;指针指向一个 数组
,数组的元素都是函数指针。
- void test(int)
- {
- printf("%s\n", str);
- }
-
- int main()
- {
-
- //函数指针pf
- void (*pf)(int) = test;
-
- //函数指针的数组pfArr
- void (*pfArr[5])(int);
- pfArr[0] = test;
-
- //指向函数指针数组pfArr的指针ppfArr
- void (*(*ppfArr)[5])(int) = &pfArr;
-
- return 0;
-
- }
十三、回调函数
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个
函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数
的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进
行响应。(说一种大家能够理解的:回调函数其实不是直接去调这个函数,而是把函数的地址传递给另外一个函数,在函数内部通过函数指针去调用这个函数,这就叫回调函数。)
- //将函数指针数组中的计算器改写成利用回调函数的形式
-
- int Add(int x, int y)
- {
- return x + y;
- }
-
- int Sub(int x, int y)
- {
- return x - y;
- }
-
- int Mul(int x, int y)
- {
- return x * y;
- }
-
- int Div(int x, int y)
- {
- return x / y;
- }
-
-
- void menu()
- {
- printf("***************************\n");
- printf("***** 1.add 2. sub ****\n");
- printf("***** 3.mul 4. div ****\n");
- printf("***** 0.exit ****\n");
- printf("***************************\n");
- }
-
- void calc(int (*p)(int, int))
- {
- int x = 0;
- int y = 0;
- int ret = 0;
- printf("请输入2个操作数:>");
- scanf("%d %d", &x, &y);
- ret = p(x, y);
- printf("%d\n", ret);
- }
-
- int main()
- {
- int input = 0;
- do
- {
- menu();
- printf("请选择:>");
- scanf("%d", &input);
- switch (input)
- {
- case 1:
- calc(Add);
- break;
- case 2:
- calc(Sub);
- break;
- case 3:
- calc(Mul);
- break;
- case 4:
- calc(Div);
- break;
- case 0:
- printf("退出计算器\n");
- break;
- default:
- printf("选择错误\n");
- break;
- }
- } while (input);
- }
13.1、qsort函数
qsort为c语言标准库提供的排序函数。
- qsort 可以排序任意类型的数据
-
- //使用qsort函数,要包含头文件#include
-
- void qsort( void* base , //待排序数据的起始地址
- size_t num , //待排序数据的元素个数
- size_t size , //待排序数据元素的大小(单位是字节)
- int (*cmp)(const void*, const void*) //比较2个元素大小的函数指针
- );
-
-
- int (*cmp)(const void* p1 , const void* p2)
- //如果p1大于p2,返回一个大于0的数;
- //如果p1小于p2,返回一个小于0的数;
- //如果p1等于p2,返回0;
今天的内容到此结束就结束啦,本期内容到这里就跟大家告别一段落了,让我们期待下一篇的文章到来吧,感谢您的收看!