C | 指针

1.什么是指针

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

指针变量的大小?

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

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

C | 指针

2.指针的定义与使用

2.1 指针的定义

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

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](数组指针);

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

int q = 12; int *qPtr = &q; 

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

int q = 12, *qPtr; qPtr = &q; 

2.2 指针的使用

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

#include<stdio.h>  int main(){     int q=12;     int *qptr;     qptr = &q;     printf("q的地址是:%pnqptr中的内容是:%pn", &q, qptr);     printf("q的值是:%dn*qptr的值是:%dn", q, *qptr);     // 运算符'&'和'*'是互逆的     printf("&*qptr=%p, *&qptr=%pn因此有&*qptr=*&qptrn", &*qptr, *&qptr);     return 0; } 

C | 指针

3.指针的宽度(步长)

#include<stdio.h>  int main(){     int num = 0x01020304;     char *p1 = (char *)&num;     short *p2 = (short *)&num;     int *p3 = &num;      printf("%#xn", *p1);     printf("%#xn", *p2);     printf("%#xn", *p3);     return 0; } 

C | 指针

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

C | 指针

C | 指针

题目:

int a[5] = {1, 2, 3,4 , 5}; int *ptr = (int *)(&a+1); 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 野指针

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

#include<stdio.h>  int main(){     int *p ;      *p = 200;     printf("%dn", *p);     return 0; } 

C | 指针

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

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

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

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

4.2 空指针

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

C | 指针

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

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

空指针的作用:

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

4.3 万能指针

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

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

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

#include<stdio.h>  int main(){     int a = 10;     void *p = (void *)&a;     printf("%dn", *p);     return 0; } 

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

#include<stdio.h>  int main(){     int a = 10;     void *p = (void *)&a;     printf("%dn", *(int *)p);     return 0; } 

C | 指针

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

5.const修饰的指针变量

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

C | 指针

5.1 指向常量的指针

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

C | 指针

C | 指针

5.2 指针常量

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

C | 指针

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

6.多级指针

#include<stdio.h>  int main(){     int a = 10;     int *p = &a;     int **q = &p;     // 通过q获取a的值     printf("%dn", **q);     return 0; } 

C | 指针

7.指针数组与数组指针

7.1 指向数组元素的指针

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

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

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

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

int *aPtr=&a[0]; int *aPtr; 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个字节)

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

*(aPtr+2); 

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

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

#include<stdio.h>  int main(){     int a[5] = {10, 20, 30, 40, 50};     int *aPtr, i;     aPtr = &a[0];     for(i=0;i<5;i++){ //通过数组下标引用元素的方式输出数组元素         printf("a[%d]=%dn", i, a[i]);     }     for(i=0;i<5;i++){ //通过数组名引用元素的方式是输出数组元素         printf("*(a+%d)=%dn", i, *(a+i));     }     for(i=0;i<5;i++){ //通过指针变量下标引用元素的方式输出数组元素         printf("aPtr[%d]=%dn", i, aPtr[i]);     }     for(aPtr=a, i=0; aPtr<a+5; aPtr++, i++){ //通过指针变量偏移的方式输出数组元素         printf("*(aPtr+%d)=%dn", i, *aPtr);     }     return 0; } 

C | 指针

7.2 指针数组

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

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

int *p[4] 

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

#include<stdio.h>  int main(){     // 定义指针数组     const char *s[4] = {"ABC", "DEF", "GHI", "JKL"};     int n = 4;     int i;     const char *aPtr;     // 方法1:通过数组名输出字符串     for(i=0;i<n;i++){         printf("第%d个字符串:%sn", i+1, s[i]);     }     // 方法2:通过指向数组的指针输出字符串     for(aPtr=s[0],i=0;i<n;aPtr=s[i]){         printf("第%d个字符串:%sn", i+1, aPtr);         i++;     }     return 0; } 

运行结果图示:

C | 指针

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

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

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

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

/* 利用指针数组实现对一组变量的值按照从小到大排序,排序时交换变量的指针值。 */ #include<stdio.h>  int main(){     int a, b, c, d, e;     int *s[5] = {&a, &b, &c, &d, &e};     int i,j;     int n=5;     int *p;     // 用户输入5个数(大小任意)     printf("请输入5个任意正整数(空格分隔):n");     scanf("%d%d%d%d%d", &a, &b, &c, &d, &e);     printf("排序前:n");     for(i=0;i<n;i++){         printf("%dn",*s[i]);     }     // 排序:冒泡排序     for(i=0;i<n-1;i++){ // 执行n-1趟         for(j=0;j<n-i-1;j++){ // 每一趟需要执行n-1-i次比较操作             if(*s[j] > *s[j+1]){                 p = s[j];                 s[j] = s[j+1];                 s[j+1] = p;             }         }     }     printf("排序后:n");     for(i=0;i<n;i++){         printf("%dn",*s[i]);     }     return 0; } 

运行结果图示:

C | 指针

7.3 数组指针

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

int (*p)[4] 

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

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

C | 指针

如果有如下语句:

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

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

C | 指针

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

C | 指针

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)即对地址取值。

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

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

运行结果图示:

C | 指针

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

8.指针函数与函数指针

8.1 指针函数

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

float *func(int a, int b); 

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

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

#include<stdio.h>  int *FindAddress(int (*ptrScore)[4], int index); void Display(int *, int n);  int main(){     int score[3][4] = {{83, 78, 79, 88}, {71, 88, 92, 63}, {99, 92, 87, 80}};     int n = 4;     int row;     int *p;     while(1){         printf("请输入学生编号(1 or 2 or 3),输入0退出程序:n");         scanf("%d", &row);         if(row == 0){             break;         }else if(row == 1 || row == 2 || row == 3){             printf("第%d名学生的各科成绩分别为:n", row);             p = FindAddress(score, row-1);             Display(p,n);         }else{             printf("输入不合法,请重新输入!n");         }     }     return 0; }  int *FindAddress(int (*ptrScore)[4], int index){     /*查找某条学生成绩记录地址函数。通过传递的行地址找到要查找学生成绩所在行,并返回该行的首元素地址*/     int *ptr;     ptr = *(ptrScore+index);     return ptr; }  void Display(int *ptr, int n){     /*输出学生成绩的实现函数。利用传递过来的指针输出每门课的成绩*/     int col;     for(col=0; col<n; col++){         printf("%4d", *(ptr+col));     }     printf("n"); } 

C | 指针

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

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

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

8.2 函数指针

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

1)函数指针的调用

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

#include<stdio.h>  int Mult(int a, int b);  int main(){     int a, b;     int (*func)(int, int);     printf("请输入2个数:n");     scanf("%d%d", &a, &b);     /*方法1:函数名调用*/     printf("%d * %d = %dn", a, b, Mult(a, b));     /*方法2:函数指针调用*/     func = &Mult; // 因为函数名本身就是地址,所以&可以省略     printf("%d * %d = %dn", a, b, func(a, b));     return 0; }  int Mult(int x, int y){     return x*y; } 

C | 指针

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

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

#include<stdio.h> void SelectSort(int *, int, int (*)(int, int)); //选择排序,函数指针作为参数调用 int Ascending(int, int); // 是否进行升序排列 int Descending(int, int); // 是否进行降序排列 void swap(int *, int *); void Display(int a[], int n);  int main(){     int a[10] = {13, 23, 11, 4, 9, 16, 22, 23, 9, 10};     printf("排序前数列:n");     Display(a, 10);     printf("升序排列:n");     SelectSort(a, 10, Ascending);     Display(a, 10);     printf("降序排列:n");     SelectSort(a, 10, Descending);     Display(a, 10);     return 0; }  void swap(int *a, int *b){     int temp = *a;     *a = *b;     *b = temp; }  void Display(int a[], int n){     int i;     for(i=0;i<n;i++){         printf("%4d", a[i]);     }     printf("n"); }  int Ascending(int a, int b){     if(a>b){         return 1;     }else{         return 0;     } } int Descending(int a, int b){     if(a<b){         return 1;     }else{         return 0;     } }  void SelectSort(int *ptr, int n, int (*compare)(int, int)){     /*选择排序基本思想(升序):每一趟排序从n-i个元素中选取关键字最小的元素作为有序序列的第i个元素*/     int i, j, k;     // 将第i个元素与后面n-i个元素进行比较,将关键字最小的元素放在第i个位置     for(i=0;i<n;i++){         j=i; // 初始时,关键字最小的元素下标为i         for(k=j+1;k<n;k++){             if(compare(*(ptr+j), *(ptr+k))){                 j=k;             }         }         if(j!=i){             swap(ptr+j, ptr+i);         }     } } 

C | 指针

其中,函数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个函数作为数组元素存放在一个数组中,需要定义一个指向函数的指针数组指向这
三个函数,代码如下:

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

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

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

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

#include<stdio.h>  void f1(int n); /*函数f1声明*/ void f2(int n); /*函数f2声明*/ void f3(int n); /*函数f3声明*/  int main(){     void (*f[3])(int)={f1,f2,f3}; /*声明指向函数的指针数组*/     int flag;     printf("调用函数请输入1、2或者3,结束程序请输入0。n");     scanf("%d",&flag);     while(flag){         if(flag==1||flag==2||flag==3){             f[flag-1](flag); /*通过函数指针调用数组中的函数*/             printf("请输入1、2或者3,输入0结束程序.n");             scanf("%d",&flag);         }else{             printf("请输入一个合法的数(1~3),输入0结束程序.n");             scanf("%d",&flag);         }     }     printf("程序结束.n");     return 0; } void f1(int n) /*函数f1的定义*/ {     printf("函数f%d:调用第%d个函数!n",n,n); } void f2(int n) /*函数f2的定义*/ {     printf("函数f%d:调用第%d个函数!n",n,n); } void f3(int n) /*函数f3的定义*/ {     printf("函数f%d:调用第%d个函数!n",n,n); } 

C | 指针

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

发表评论

相关文章