经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » C 语言 » 查看文章
C | 指针
来源:cnblogs  作者:就良同学  时间:2023/1/6 8:53:35  对本文有异议

1.什么是指针

指针是一种变量,也称指针变量,它的值不是整数、浮点数和字符,而是内存地址。指针的值就是变量的地址,而变量有拥有一个具体值。因此,可以理解为变量直接引用了一个值,指着间接地引用了一个值。一个存放变量地址的类型称为该变量的“指针”。

指针变量的大小?

32位系统为例,每个字节(即一个内存单元)都拥有一个地址编号,地址范围为0x00000000~0xffffffff。当指针变量占4个字节(即32bit)时,刚好能够表示所有地地址编号。

不管什么类型的指针,其大小只和系统编译器有关系。

image

2.指针的定义与使用

2.1 指针的定义

在C语言中,所有变量在使用前都需要声明。例如,声明一个指针变量的语句如下:

  1. int *qPtr, q;

q是整型变量,表示要存放一个整型类型的值;qPtr是一个整形指针变量,表示要存放一个变量的地址,而这个变量是整数类型。qPtr叫做一个指向整型的指针。

在声明指针变量时,“*”只是一个指针类型标识符,指针变量的声明也可以写成 int* qPtr。

定义指针三步骤(来自传智播客):

  1. *与符号相结合代表是一个指针变量,比如*p;
  2. 要保存谁的地址,就写出它的声明语句,比如int a, int a[10];
  3. 用*p替换掉变量名称,即int a→int *p,int a[10]→int (*p)[10](数组指针);

指针变量可以在声明时赋值,也可以在声明后赋值。例如,在声明时为指针变量赋值的语句如下:

  1. int q = 12;
  2. int *qPtr = &q;

也可以在声明后为指针变量赋值:

  1. int q = 12, *qPtr;
  2. qPtr = &q;

2.2 指针的使用

指针变量主要通过取地址运算符&和指针运算符*来存取数据。例如,&a指的是变量a的地址(取址),*ptr表示ptr所指向的内存单元存放的内容(取值)。

  1. #include<stdio.h>
  2. int main(){
  3. int q=12;
  4. int *qptr;
  5. qptr = &q;
  6. printf("q的地址是:%p\nqptr中的内容是:%p\n", &q, qptr);
  7. printf("q的值是:%d\n*qptr的值是:%d\n", q, *qptr);
  8. // 运算符'&'和'*'是互逆的
  9. printf("&*qptr=%p, *&qptr=%p\n因此有&*qptr=*&qptr\n", &*qptr, *&qptr);
  10. return 0;
  11. }

image

3.指针的宽度(步长)

  1. #include<stdio.h>
  2. int main(){
  3. int num = 0x01020304;
  4. char *p1 = (char *)&num;
  5. short *p2 = (short *)&num;
  6. int *p3 = &num;
  7. printf("%#x\n", *p1);
  8. printf("%#x\n", *p2);
  9. printf("%#x\n", *p3);
  10. return 0;
  11. }

image

通过*取指针变量所指向那块内存空间内容时,取得内存的宽度和指针变量本身指向变量的类型有关。

image

image

题目:

  1. int a[5] = {1, 2, 3,4 , 5};
  2. int *ptr = (int *)(&a+1);
  3. printf("%d,%d", *(a+1), *(ptr-1));

输出结果为:A.2,5 B.2,4 C.1,5 D.1,4

分析:

&a+1:跨过的是整个数组的宽度

&a[0]+1:跨过的是数组内单个元素的宽度

所以&a+1指向的内存地址已经不属于数组了,然后int *ptr = (int *)(&a+1)将&a+1强转为int*型,所以ptr-1将后退一个4个字节即一个数组元素大小,即指向a[4]=5

4.野指针和空指针和万能指针

4.1 野指针

野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)。

  1. #include<stdio.h>
  2. int main(){
  3. int *p ;
  4. *p = 200;
  5. printf("%d\n", *p);
  6. return 0;
  7. }

image

上述代码出现问题的原因:指针变量未初始化

任何指针变量刚被创建时不会自动成为 NULL 指针,它的缺省值是随机的。

所以,指针变量在创建的同时应当被初始化,要么将指针设置为 NULL ,要么让它指向合法的内存。

如果没有初始化,编译器会报错‘point’ may be uninitializedin the function。

4.2 空指针

  1. #include<stdio.h>
  2. int main(){
  3. // 将0号地址编号0x00000000号内存赋予指针内部值,等价于赋予NULL
  4. int *p = NULL;
  5. *p = 200; // 因为p保存了0号内存的地址,这个地址为内存的起始地址,是不可使用的,非法
  6. printf("%d\n", *p);
  7. return 0;
  8. }

image

NULL是C语言标准定义的一个值,这个值其实就是0,只不过为了使得看起来更加具有意义,才定义了这样的一个宏,中文的意思是空,表明不指向任何东西。

任何程序数据都不会存储在地址为0的内存块中,它是被操作系统预留的内存块。

空指针的作用:

如果指针使用完毕,需将指针赋予NULL;在使用指针前需判断指针是否为NULL

4.3 万能指针

void *p,可以保存任意的地址。

void p:不可遗定义void类型的变量,因为编译器不知道给该变量分配多大的内存空间;

void *p:可以定义void *变量,因为指针都是4个字节(32位系统)。

  1. #include<stdio.h>
  2. int main(){
  3. int a = 10;
  4. void *p = (void *)&a;
  5. printf("%d\n", *p);
  6. return 0;
  7. }

程序将报错,无法编译。因为虽然p内存内确实存储的是变量a的地址,但是由于p指向void型变量,导致根据p指针内部存储地址去取相应位置值时不知道取多大的内存大小。

  1. #include<stdio.h>
  2. int main(){
  3. int a = 10;
  4. void *p = (void *)&a;
  5. printf("%d\n", *(int *)p);
  6. return 0;
  7. }

image

*(int *)p:将指针p强转为int *型,此时根据p指针内部存储地址去取相应位置值时,将读取4个字节大小。

5.const修饰的指针变量

引子:const int a = 10;const修饰变量a,表示不能再通过a修改a内存里面的内容。

image

5.1 指向常量的指针

const修饰*,表示不能通过该指针修改指针所指内存的数值,但是指针指向可以变。

image

image

5.2 指针常量

修饰p,指针指向不能变,指针指向的内存可以被修改。

image

注:const int * const p =&a;表示p指针指向内存区域不能被修改,同时p的指向也不能被改变。

6.多级指针

  1. #include<stdio.h>
  2. int main(){
  3. int a = 10;
  4. int *p = &a;
  5. int **q = &p;
  6. // 通过q获取a的值
  7. printf("%d\n", **q);
  8. return 0;
  9. }

image

7.指针数组与数组指针

7.1 指向数组元素的指针

例如定义一个整型数组和一个指针变量,语句如下。

  1. int a[5]={10,20,30,40,50};
  2. int *aPtr;

这里的a是一个数组,它包含了5个整型数据。变量名a就是数组a的首地址,它与&a[0]等价。如果令aPtr=&a[0]或者
aPtr=a,则aPtr也指向了数组a的首地址。

也可以在定义指针变量时直接赋值,如以下语句是等价的。

  1. int *aPtr=&a[0];
  2. int *aPtr;
  3. aPtr =&a[0];

与整型、浮点型数据一样,指针也可以进行算术运算,但含义却不同。当一个指针加1(或减)1并不是指针值增加(或减少)1,而是使指针指向的位置向后(或向前)移动了一个位置,即加上(或减去)该整数与指针指向对象的大小的乘积。例如对于aPtr+=3,如果一个整数占用4个字节,则相加后aPtr=2000+4*3=2012(这里假设指针的初值是2000)。同样指针也可以进行自增(++)运算和自减(--)运算。

也可以用一个指针变量减去另一个指针变量。例如,指向数组元素的指针aPtr的地址是2008,另一个指向数组元素的指针bPtr的地址是2000,则a=aPtr-bPtr的运算结果就是把从aPtr到bPtr之间的元素个数赋给a,元素个数为(2008-2000)/4=2(假设整数占用4个字节)

我们也可以通过指针来引用数组元素。例如以下语句。

  1. *(aPtr+2);

如果aPtr是指向a[0],即数组a的首地址,则aPtr+2就是数组a[2]的地址,*(aPtr+2)就是30。

注意:指向数组的指针可以进行自增或自减运算,但是数组名则不能进行自增或自减运算,这是因为数组名是一个常量指针,它是一个常量,常量值是不能改变的。

  1. #include<stdio.h>
  2. int main(){
  3. int a[5] = {10, 20, 30, 40, 50};
  4. int *aPtr, i;
  5. aPtr = &a[0];
  6. for(i=0;i<5;i++){ //通过数组下标引用元素的方式输出数组元素
  7. printf("a[%d]=%d\n", i, a[i]);
  8. }
  9. for(i=0;i<5;i++){ //通过数组名引用元素的方式是输出数组元素
  10. printf("*(a+%d)=%d\n", i, *(a+i));
  11. }
  12. for(i=0;i<5;i++){ //通过指针变量下标引用元素的方式输出数组元素
  13. printf("aPtr[%d]=%d\n", i, aPtr[i]);
  14. }
  15. for(aPtr=a, i=0; aPtr<a+5; aPtr++, i++){ //通过指针变量偏移的方式输出数组元素
  16. printf("*(aPtr+%d)=%d\n", i, *aPtr);
  17. }
  18. return 0;
  19. }

image

7.2 指针数组

定义:指针数组其实也是一个数组,只是数组中的元素是指针类型的数据。换句话说,指针数组中的每一个元素都是一个指针变量。

定义指针数组的方式如下:

  1. int *p[4]

例1:使用指针数组保存字符串并将字符串打印输出。

  1. #include<stdio.h>
  2. int main(){
  3. // 定义指针数组
  4. const char *s[4] = {"ABC", "DEF", "GHI", "JKL"};
  5. int n = 4;
  6. int i;
  7. const char *aPtr;
  8. // 方法1:通过数组名输出字符串
  9. for(i=0;i<n;i++){
  10. printf("第%d个字符串:%s\n", i+1, s[i]);
  11. }
  12. // 方法2:通过指向数组的指针输出字符串
  13. for(aPtr=s[0],i=0;i<n;aPtr=s[i]){
  14. printf("第%d个字符串:%s\n", i+1, aPtr);
  15. i++;
  16. }
  17. return 0;
  18. }

运行结果图示:

result

注:常量与指针间的转换 warning: ISO C++ forbids converting a string constant to 'char*'

Q:为什么s[i]打印的是值而不是地址?

A:%s占位符的特点就是只要告诉他字符串的首地址,就可以读取整个字符串

例2:利用指针数组实现对一组变量的值按照从小到大排序,排序时交换变量的指针值。

  1. /* 利用指针数组实现对一组变量的值按照从小到大排序,排序时交换变量的指针值。 */
  2. #include<stdio.h>
  3. int main(){
  4. int a, b, c, d, e;
  5. int *s[5] = {&a, &b, &c, &d, &e};
  6. int i,j;
  7. int n=5;
  8. int *p;
  9. // 用户输入5个数(大小任意)
  10. printf("请输入5个任意正整数(空格分隔):\n");
  11. scanf("%d%d%d%d%d", &a, &b, &c, &d, &e);
  12. printf("排序前:\n");
  13. for(i=0;i<n;i++){
  14. printf("%d\n",*s[i]);
  15. }
  16. // 排序:冒泡排序
  17. for(i=0;i<n-1;i++){ // 执行n-1趟
  18. for(j=0;j<n-i-1;j++){ // 每一趟需要执行n-1-i次比较操作
  19. if(*s[j] > *s[j+1]){
  20. p = s[j];
  21. s[j] = s[j+1];
  22. s[j+1] = p;
  23. }
  24. }
  25. }
  26. printf("排序后:\n");
  27. for(i=0;i<n;i++){
  28. printf("%d\n",*s[i]);
  29. }
  30. return 0;
  31. }

运行结果图示:

image

7.3 数组指针

定义:数组指针是指向数组的一个指针。如下定义:

  1. int (*p)[4]

其中,p是指向一个拥有4个元素的数组的指针,数组中每个元素都为整型。与前面刚刚介绍过的指针数组做比较,这里定义的数组指针多了一对括号,*p两边的括号不可以省略。这里定义的p仅仅是一个指针,不过这个指针有点特殊,这个p指向的是包含4个元素的一维数组。

数组指针p与它指向的数组之间的关系可以用下图来表示。

image

如果有如下语句:

  1. int a[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};
  2. p=a;

数组指针p与数组a中元素之间的关系如图所示。其中,(*p)[0]、(*p)[1]、(*p)[2]、(*p)[3]分别保存的是元素值为1、2、3、4的值。p、p+1和p+2分别指向二维数组的第一行、第二行和第三行,p+1表示将指针p移动到下一行。

image

*(p+1)+2表示数组a第1行第2列的元素的地址,即&a[1][2],*(*(p+1)+2)表示a[1][2]的值即7,其中1表示行,2表示列。

image

Q:为什么(*p)[0]、(*p)[1]、(*p)[2]、(*p)[3]分别保存的是元素值为1、2、3、4的值而不是它们的地址呢?

A:指针[i] == *(指针+i)

(*p)[0] == *(*p+0)→p本来表示的是第0行一整行的数据,出现在表达式中将自动转为指向a[0][0]的指针→*p+0还是指向a[0][0]的指针→*(*p+0)即对地址取值。

下面编程输出以上数组指针的值和数组的内容。

  1. #include<stdio.h>
  2. int main(){
  3. int a[3][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}};
  4. int (*p)[4] = a; // 声明数组指针p(p是一个指向'内含4个整型元素的数组'的指针)
  5. int row, col;
  6. // 输出数组的内容
  7. for(row=0;row<3;row++){
  8. for(col=0;col<4;col++){
  9. printf("a[%d][%d]=%-4d", row, col, *(*(p+row)+col));
  10. }
  11. printf("\n");
  12. }
  13. // 输出数组指针的值
  14. for(row=0;row<3;row++, p++){
  15. for(col=0;col<4;col++){
  16. printf("(*p[%d])[%d]=%p\t", row, col, (*p+col));
  17. }
  18. printf("\n");
  19. }
  20. return 0;
  21. }

运行结果图示:

image

注释:[] == *()→如,p[0] 等价于 *(p+0)

8.指针函数与函数指针

8.1 指针函数

指针函数是指函数的返回值是指针类型的函数。例如,以下是一个指针函数的声明:

  1. float *func(int a, int b);

func是函数名,前面的'*'表明返回值的类型是指针类型,因为前面的类型标识符是float,所以返回的指针是指向浮点型的。

例:假设若干个学生的成绩存放在二维数组中,要求输入学生编号,利用指针函数实现其成绩的输出。

  1. #include<stdio.h>
  2. int *FindAddress(int (*ptrScore)[4], int index);
  3. void Display(int *, int n);
  4. int main(){
  5. int score[3][4] = {{83, 78, 79, 88}, {71, 88, 92, 63}, {99, 92, 87, 80}};
  6. int n = 4;
  7. int row;
  8. int *p;
  9. while(1){
  10. printf("请输入学生编号(1 or 2 or 3),输入0退出程序:\n");
  11. scanf("%d", &row);
  12. if(row == 0){
  13. break;
  14. }else if(row == 1 || row == 2 || row == 3){
  15. printf("第%d名学生的各科成绩分别为:\n", row);
  16. p = FindAddress(score, row-1);
  17. Display(p,n);
  18. }else{
  19. printf("输入不合法,请重新输入!\n");
  20. }
  21. }
  22. return 0;
  23. }
  24. int *FindAddress(int (*ptrScore)[4], int index){
  25. /*查找某条学生成绩记录地址函数。通过传递的行地址找到要查找学生成绩所在行,并返回该行的首元素地址*/
  26. int *ptr;
  27. ptr = *(ptrScore+index);
  28. return ptr;
  29. }
  30. void Display(int *ptr, int n){
  31. /*输出学生成绩的实现函数。利用传递过来的指针输出每门课的成绩*/
  32. int col;
  33. for(col=0; col<n; col++){
  34. printf("%4d", *(ptr+col));
  35. }
  36. printf("\n");
  37. }

image

注:p = FindAddress(score, row-1);二维数组的数组名表示啥?

若a是一维数组,则a指向的是第一个元素。

若a是二维数组,也可以将a看成一个一维数组,那么其元素是其行向量。则a指向的是第一个行向量。

8.2 函数指针

指针可以指向变量、数组,也可以指向函数,指向函数的指针就是函数指针。

1)函数指针的调用

例1:通过一个函数求两个数的乘积,并通过函数指针调用该函数。

  1. #include<stdio.h>
  2. int Mult(int a, int b);
  3. int main(){
  4. int a, b;
  5. int (*func)(int, int);
  6. printf("请输入2个数:\n");
  7. scanf("%d%d", &a, &b);
  8. /*方法1:函数名调用*/
  9. printf("%d * %d = %d\n", a, b, Mult(a, b));
  10. /*方法2:函数指针调用*/
  11. func = &Mult; // 因为函数名本身就是地址,所以&可以省略
  12. printf("%d * %d = %d\n", a, b, func(a, b));
  13. return 0;
  14. }
  15. int Mult(int x, int y){
  16. return x*y;
  17. }

image

2)函数指针作为函数参数的使用

例2:利用函数指针作为函数参数,实现选择排序算法的升序排列和降序排列。

  1. #include<stdio.h>
  2. void SelectSort(int *, int, int (*)(int, int)); //选择排序,函数指针作为参数调用
  3. int Ascending(int, int); // 是否进行升序排列
  4. int Descending(int, int); // 是否进行降序排列
  5. void swap(int *, int *);
  6. void Display(int a[], int n);
  7. int main(){
  8. int a[10] = {13, 23, 11, 4, 9, 16, 22, 23, 9, 10};
  9. printf("排序前数列:\n");
  10. Display(a, 10);
  11. printf("升序排列:\n");
  12. SelectSort(a, 10, Ascending);
  13. Display(a, 10);
  14. printf("降序排列:\n");
  15. SelectSort(a, 10, Descending);
  16. Display(a, 10);
  17. return 0;
  18. }
  19. void swap(int *a, int *b){
  20. int temp = *a;
  21. *a = *b;
  22. *b = temp;
  23. }
  24. void Display(int a[], int n){
  25. int i;
  26. for(i=0;i<n;i++){
  27. printf("%4d", a[i]);
  28. }
  29. printf("\n");
  30. }
  31. int Ascending(int a, int b){
  32. if(a>b){
  33. return 1;
  34. }else{
  35. return 0;
  36. }
  37. }
  38. int Descending(int a, int b){
  39. if(a<b){
  40. return 1;
  41. }else{
  42. return 0;
  43. }
  44. }
  45. void SelectSort(int *ptr, int n, int (*compare)(int, int)){
  46. /*选择排序基本思想(升序):每一趟排序从n-i个元素中选取关键字最小的元素作为有序序列的第i个元素*/
  47. int i, j, k;
  48. // 将第i个元素与后面n-i个元素进行比较,将关键字最小的元素放在第i个位置
  49. for(i=0;i<n;i++){
  50. j=i; // 初始时,关键字最小的元素下标为i
  51. for(k=j+1;k<n;k++){
  52. if(compare(*(ptr+j), *(ptr+k))){
  53. j=k;
  54. }
  55. }
  56. if(j!=i){
  57. swap(ptr+j, ptr+i);
  58. }
  59. }
  60. }

image

其中,函数SelectSort(a,N,Ascending)中的参数Asscending是一个函数名,传递给函数定义void SelectSort(int *p,int n,int(*compare)(int,int))中的函数指针compare,这样指针就指向了Asscending。从而可以在执行语句(*compare)(a[j], a[j+1])时调用函数Ascending(int a,int b)判断是否需要交换数组中两个相邻的元素,然后调用swap(&a[j],&a[j+1])进行交换。

8.3 函数指针数组

假设有3个函数f1、f2和f3,可以把这3个函数作为数组元素存放在一个数组中,需要定义一个指向函数的指针数组指向这
三个函数,代码如下:

  1. void (*f[3])(int)={f1,f2,f3};

f是包含3个指向函数指针元素的数组,f[0]、f[1]和f[2]分别指向函数f1、f2和f3。通过函数指针f调用函数的形式如下。

  1. f[n](m); /*n和m都是正整数*/

例:声明一个指向函数的指针数组,并通过指针调用函数。

  1. #include<stdio.h>
  2. void f1(int n); /*函数f1声明*/
  3. void f2(int n); /*函数f2声明*/
  4. void f3(int n); /*函数f3声明*/
  5. int main(){
  6. void (*f[3])(int)={f1,f2,f3}; /*声明指向函数的指针数组*/
  7. int flag;
  8. printf("调用函数请输入1、2或者3,结束程序请输入0。\n");
  9. scanf("%d",&flag);
  10. while(flag){
  11. if(flag==1||flag==2||flag==3){
  12. f[flag-1](flag); /*通过函数指针调用数组中的函数*/
  13. printf("请输入1、2或者3,输入0结束程序.\n");
  14. scanf("%d",&flag);
  15. }else{
  16. printf("请输入一个合法的数(1~3),输入0结束程序.\n");
  17. scanf("%d",&flag);
  18. }
  19. }
  20. printf("程序结束.\n");
  21. return 0;
  22. }
  23. void f1(int n) /*函数f1的定义*/
  24. {
  25. printf("函数f%d:调用第%d个函数!\n",n,n);
  26. }
  27. void f2(int n) /*函数f2的定义*/
  28. {
  29. printf("函数f%d:调用第%d个函数!\n",n,n);
  30. }
  31. void f3(int n) /*函数f3的定义*/
  32. {
  33. printf("函数f%d:调用第%d个函数!\n",n,n);
  34. }

image

函数指针不能执行像f+1、f++、f--等运算。

原文链接:https://www.cnblogs.com/lijiuliang/p/17028849.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号