文章目录
- 前言
- 指向整型变量的指针
- 指向一维数组的指针
- 以指针为形参且以指针为返回值类型
- 以局部变量地址作为返回值存在的安全隐患
- 指向字符串的指针
- 通过指针交换两变量的值
- 多级指针
- 指针数组
- 数组指针
- 指向函数的指针
前言
C语言中的指针实质是指向某一对象的内存地址,对象可以是变量、常量、数组、函数等。不同类型的指针大小是不变的,例如在64位操作系统中,指针大小占8个字节。可以对地址进行前移、后移运算;可以通过运算符*
获取内存地址保存的数据。
操作系统 | 大小(字节) | 备注 |
---|---|---|
62位 | 8 | int*、float*、char*等指针大小均为8字节 |
32位 | 4 | int*、float*、char*等指针大小均为4字节 |
指向整型变量的指针
- 建立一个整型变量num,假设其地址为1000,并在其内存空间中保存数据10
- 建立一个整型变量temporary,假设其地址为1020,并在其内存空间中保存数据20
- 建立一个整型指针变量p,指向num的地址,也就是地址1000,此时p和num指向同一个内存地址
- 将指针p指向的地址中保存的数据更改为temporary的数据,即修改为20,因为p和num指向同一个内存地址,故*p和num的值均被更改为20
以此可以推导出其他基本数据类型指针的简单运用
/// Description 指向整形变量的指针
void varPrime(){int num = 10,temporary = 20;//一个整形指针变量指向了一个整形变量的地址int *p = #//修改p指向的地址中的数据内容*p = temporary;printf("%d",num);
}
指向一维数组的指针
- array是一个一维整型数据,代表首元素首地址,也就是
&array[0]
- 整型指针变量p指向array的首元素首地址,假设首元素首地址为1000
- p++;即代表地址后移
(1000+(1*4))=1004
,因为int型在64位操作系统中占4个字节,故每次进行地址移动,以4位倍数,执行完p++;后,p当前指向数组内第二个元素array[1]的地址 - p–;即代表地址前移
(1004-(1*4))=1000
(因为上一次执行完之后p指向的地址是1004,故从此地址开始计算),执行完p–;后,p当前指向数组内第一个元素array[0]的地址 - *(p+i):因为一维数组采用顺序存储,拥有随机访问的特点,因为指针指向的数组也可以通过随机访问进行读取内存空间上的数据
/// Description 指向一维数组的指针
void oneArrayPrime(){int array[] = {10,20,30};//因为array代表的是数组的首元素地址,故前面不需要加&地址符号int *p = array;//数组下标后移,初始化指向第一个元素10,后移操作之后指向20p++;//数组下标前移,继上一个操作之后,指针指向元素10,前移操作之后指向10p--;//遍历数组,输出数组所有内容for(int i=0; i<3; i++){printf("current element: %d\n",*(p+i));}
}
以指针为形参且以指针为返回值类型
通过在一个数组内寻找最大值的例子,概述以指针为形参且以指针为返回值类型
- 实参中传入一维数组的首元素首地址,形参则使用一个整型指针来进行传递,此指针指向一维数组的首元素首地址
- 然后在通过地址符号
&
返回最大值的地址,同样,因为是地址,所以返回值类型需要变为指针来进行传递 - 此处以局部变量地址作为返回值,存在一定安全隐患,将在下一节讲述
/// Description 数组作为函数实参,指针作为函数形参寻找数组内最大值,并以指针作为返回值类型
/// - Parameters:
/// - array: 整形指针变量,指向一维数组
/// - size: 数组长度
int* findMax(int *array,int length){int max = *array;for(int i=1; i<length; i++){if(*(array+i) > max) max = *(array+i);}return &max;
}void testMax(){int array[] = {1,2,3,4};int length = sizeof(array) / sizeof(int);int *max = findMax(array, length);printf("max=%d\n",*max);
}
以局部变量地址作为返回值存在的安全隐患
- List item上述寻找最大值例子以局部变量地址作为返回值进行返回,但上述例子可以正常运行,而下列例子则存在安全隐患。
- 因为当函数执行完成之后,系统会对此函数进行回收,其局部变量也被回收,但系统会留有一个时机,上述例子抓住时机,并将其进行输出。
- 而下列例子在中间穿插了一句代码,导致错过时机,指针变量p变为野指针,指向一个系统随机分配的地址。
- 一般会保留一个语句的执行频度(各编译器存在差异),也就是说当下一个语句执行完后,系统会对其进行释放
int* returnPrime(){int n = 10;return &n;
}/// Description 接受来自另一个函数的局部变量返回值
/// 当函数执行完成后,系统不会立即回收此地址
///一般会保留一个语句的执行时间(各编译器存在差异),也就是说当下一个语句执行完后,系统会对其进行释放
///所以p指向的地址被系统释放,故随后指向一个随机的地址
void testReturnPrime(){int *p = returnPrime();printf("split line!\n");printf("n=%d\n",*p);
}
指向字符串的指针
字符串不等同于字符数组
类型 | 举例 | 长度 | 区别 |
---|---|---|---|
字符数组 | char str1[] = {‘a’,‘b’,‘c’}; | 3 | 系统不会默认在结尾追加结尾符'\0' |
字符串 | char str2[] = “abc”; | 4 | 系统会默认在结尾追加结尾符'\0' |
字符指针p指向字符串str首元素首地址,因为str代表的就是首元素首地址,本身就是一个地址,所以在赋值给字符指针p时,无须加地址符号&
,此处区别于上述指针整型变量的指针
/// Description 指向字符串的指针
void stringPrime(){//字符串,系统默认在其最后添加结尾符'\0'char str[] = "Hello World!";char tempStr[] = "C Prime!";//字符指针变量p指向字符串首元素首地址char *p = str;int size = strlen(p);printf("The array length:%d\n",size);//输出方式一:直接使用字符串方式输出,因为数组str占据着一连串连续的地址空间printf("%s\n",p);//输出方式二:通过类似数组str[i]的方式逐个访问地址然后输出for(int i=0; i<size; i++){printf("%c",*(p+i));}putchar('\n');//输出方式三:通过指针后移操作,改变指向的地址,依次读取输出for(;*p!='\0';p++){printf("%c",*p);}putchar('\n');//更改字符指针指向的地址p = tempStr;printf("%s\n",p);putchar('\n');
}
通过指针交换两变量的值
- *a = *b:这一句为关键,代表着将b内存地址上的内容赋值到a内存地址上,则将原本a的内容进行覆盖
如果将上述第三行代码改为a=b;取消两端的指针*运算
则形参a指向b的地址,但由于未使用指针进行运算,形参a的内容只保留在局部范围之内,不改变实参数a的内容
故输出a=1,b=1
/// Description 通过将整形指针变量作文形参,交换两个变量的值
/// - Parameters:
/// - a: 第一个指针变量
/// - b: 另一个指针变量
void swap(int *a,int *b){int temp;temp = *a;*a = *b;*b = temp;//如果将上述第三行代码改为a=b;取消两端的指针取值操作//则形参a指向b的地址,但由于未使用指针进行运算,形参a的内容只保留在局部范围之内,不改变实参数a的内容//故输出a=1,b=1
}
void testSwap(){int a=1,b=2;printf("before swap:a=%d,b=%d\n",a,b);swap(&a, &b);printf("after swap:a=%d,b=%d\n",a,b);
}
多级指针
- 一级整型指针变量指向a的地址
- 二级指针指向一级指针的地址;依次类推
- 可以换一种思维,把二级指针看作一级指针,一级指针看作一个变量,各“向下降一级",就回到一级整型指针变量指向变量a的地址
- ***p3:等同于
*(*(*p3))
,从括弧内往外剖析
最里面的(p3)取出的是p2的地址
然后第二层就为(p2)取出来的是p1的地址
第一层就为(p1)的值
则p3取的是变量a的值
void multPrime(){int a = 10;//一级整型指针变量指向a的地址int *p1 = &a;//二级指针指向一级指针的地址,//可以换一种思维,把二级指针看作一级指针,一级指针看作一个变量,各“向下降一级",就回到第一条语句思想int **p2 = &p1;//***p3可以看作是*(*(*p3))//从内往外剖析//最里面的(*p3)取出的是p2的地址//然后第二层就为(*p2)取出来的是p1的地址//第一层就为(*p1)的值int ***p3 = &p2;printf("p1=%d\n",*p1);printf("p2=%d\n",**p2);printf("p3=%d\n",*(*(*p3)));
}
指针数组
定义:指针数组:包含n个元素,其中每一个元素都是指针
例如: int * p[n]; 其中[]的运算优先级大于*,p为数组,其类型为int*,则数组内每个元素类型皆为int*
- 下列指针数组array中保存p1,p2,p3三个整型指针变量
- 同样,因为是数组具有随机访问的特性;
- **(array+i)等同于
*(*(array+i))
- 其中
*(array+i)
获取是第i个元素,因为元素是指针,是指向的一个内存地址,故*(array+i)
取出的是pi的地址 - 再在外面加一个取值
*
符号,就是对*pi等于*(*(array+i))
进行取值运算,获取其内存空间的数据
/// Description 指针数组:包含n个元素,其中每一个元素都是指针
/// 例如: int * p[n];
/// 其中[]的运算优先级大于*,p为数组,其类型为int*,则数组内每个元素类型皆为int*
void primeArray(){int a = 10,b=20,c=30;int *p1=&a,*p2=&b,*p3=&c;//定义一个整型指针数组,包含3个元素,其中每一个元素都是整形指针int *array[] = {p1,p2,p3};//*指针在32位操作系统占4个字节,在64位操作系统占8个字节,//与其类型无关,int*,char*,float*...在64位操作系统都占占8个字节int length = sizeof(array) / sizeof(int*);for(int i=0; i<length; i++){printf("%d ",**(array+i));}}
数组指针
定义:数组指针:指向数组的指针
例如:int (*p)[n];
()的优先级大于[],p为指针,指向n个一维数组
此处的n必须与二维数组的列数保持一致(以二维为例)
int (*q)[3] = b;
数组指针p指向一个2行3列的二维数组的首元素首地址*(*(q+i)+j)
,其中*(q+i)先获取二维数组的地址,*(q+i)+j
获取b[i][j]的地址,最后在外面使用*取值运算,即获取b[i][j]的值
/// Description 数组指针:指向数组的指针
/// 例如:int (*p)[n];
/// ()的优先级大于[],p为指针,指向n个一维数组
void Arrayprime(){int a[5] = {1,2,3,4,5};int (*p)[5] = &a;for(int i=0; i<5; i++){//因为a是一维的,只有一行,所以下列语句等同于p[0][i]printf("%d ",*(*p+i));}putchar('\n');int b[2][3] = {{1,2,3},{4,5,6}};int (*q)[3] = b;for (int i=0; i<2; i++) {for (int j=0; j<3; j++) {printf("%d ",*(*(q+i)+j));}putchar('\n');}
}
指向函数的指针
此处以寻找最小值为例
int (*p)(int,int) = findMin;
从左到右结合,第一个(*p)大于第二个(),故p为一个指针,指向一个函数
- 函数指针p的函数参数定义必须和所指向函数的参数类型、个数、顺序保持一致
- 函数指针中的函数参数定义可以省略具体的参数名
int findMin(int a,int b){return a<b? a : b;
}/// Description 函数指针:顾名思义,指向函数的指针
void functionPrime(){int a=1,b=2;//其中函数指针指向函数定义对应的参数名称可省略//从左到右结合,第一个(*p)大于第二个(),故p为一个指针,指向一个函数int (*p)(int,int) = findMin;int min = (*p)(a,b);printf("min=%d\n",min);
}