c指针
指针
- 指针就是地址
- &:取出变量的地址
- *:取值运算符,取出地址中的内容(非定义声明下)
e.g:
int main(){
int var = 10;
printf("var = %d\n",var);
printf("Addr of var = 0x%p\n",&var);
printf("var(pointer)= %d\n",*&var);
return 0;
}
输出
var = 10
Addr of var = 0x0000007b01fffb6c
var(pointer)= 10
=====================================================================================
指针变量
- 存放地址的变量
- 变量声明
int main(){
int a = 10;
int* p;
p = &a;
printf("a(pointer)= %d\n",*p); //p相当于前端代码中&var
return 0;
}
输出
a(pointer)= 10
“ * ” 是一个标识符,表示这是一个指针变量,用来保存地址
====================================================================================
指针类型
- 指针变量存放的是其他变量的地址,指针的类型决定了访问变量时的空间大小。如下例:
int main(){
int a = 0x5678;
int* p1;
char *p2;
p1 = &a;
p2 = &a;
//不同的取值范围
printf("a(pointer)= %x\n",*p1);
//取值运算符会根据变量的类型,访问不同大小的空间
printf("a(pointer)= %x\n",*p2);
//相同的地址
printf("p1 = 0x%p\n",p1);
printf("p2 = 0x%p\n",p2);
//检查大小
printf("++p1 = 0x%p\n",++p1);
printf("++p2 = 0x%p\n",++p2);
return 0;
}
gcc 编译会给予警告:
输出
a(pointer)= 5678
a(pointer)= 78
p1 = 0x000000a5553ffa7c
p2 = 0x000000a5553ffa7c
++p1 = 0x000000a5553ffa80
++p2 = 0x000000a5553ffa7d
不同类型指针变量存储的变量地址相同,但在取值时发生错误。
(此处曾经疑问地址+1,内存就+1字节。相关解答以防以后忘记)
====================================================================================
封装函数中使用指针间接访问
- 函数中的形参,其实是内存中将实参复制后的新值,不论封装函数中的值怎么改变,不会直接影响到原始的数据。此时可以使用指针来进行间接访问,对原始数据进行编辑,其原理是传递的参数是数据的地址,通过该地址去访问原数据。
void dataExchangeP(int* a,int* b){
int temp;
temp = *a;
*a = *b;
*b = temp;
return;
}
void dataExchange(int a,int b){
int temp;
temp = a;
a = b;
b = temp;
return;
}
int main(){
int data1 = 10;
int data2 = 17;
printf("Before being called: \ndata1=%d\tdata2=%d\n",data1,data2);
dataExchange(data1,data2);
printf("int parameters used: \ndata1=%d\tdata2=%d\n",data1,data2);
dataExchangeP(&data1,&data2);
printf("Pointer parameters used: \ndata1=%d\tdata2=%d\n",data1,data2);
return 0;
}
输出
Before being called:
data1=10 data2=17
Int parameters used:
data1=10 data2=17
Pointer parameters used:
data1=17 data2=10
====================================================================================
指针指向固定的内存地址
- 常用于在对单片机/arm中寄存器的地址使用
int main(){
int a =10;
printf("%p\n",&a);
volatile unsigned int *p = (volatile unsigned int *)0x000000ba3a5ff79c;
printf("%p\n",p);
return 0;
}
输出
000000d64c9ff934
000000ba3a5ff79c
*volatile:说明变量在程序执行中可被隐含地改变,防止编译器自动优化
====================================================================================
指针和数组
- 指针以++偏移的方式在对数组多次遍历时,需要在每次遍历完重新对指针赋值,指向数组首地址(*(p+i)等类型不需要,因为指针位置没变)
-数组与指针同
- 指向数组的指针,可以当作数组名来使用下标法访问。同时该数组名可以作为自己的指针偏移访问(如下)
int main(){
int array[5] = {1,2,3,4,5};
int* p = array;
printf("p:%d and array:%d\n",p[0],*array);
printf("p:%d and array:%d\n",*(p+1),*(array+1));
return 0;
}
输出
p:1 and array:1
p:2 and array:2
-数组与指针异
- 在另外一种情况下,数组与指针名不可以通用,因为所定义的数组为指针常量,而定义的指针为指针变量,指针常量是不允许++操作的。
int array[5] = {1,2,3,4,5};
int* p = array;
for(int i=0;i<5;i++){
printf("p:%d and array:%d\n",*p++,*array++); //报错
}
/*19:45: error: lvalue required as increment operand
19 | printf("p:%d and array:%d\n",*p++,*array++);
| ^~
*/
- 指针与数组的大小,苏族的大小与数组中元个数,以及数组类型有关。而定义的指针是作为指针变量(如同int为4个字节),在64windows位系统中,不论指向多大数组,或者什么类型指针,均为8字节(即一个地址下存储大小)。
int main(){
int array[5] = {1,2,3,4,5};
int* p = array;
printf("size of 'array' %d\n",sizeof(array));
printf("size of int %d\n",sizeof(int));
printf("size of pointer %d\n",sizeof(p));
printf("size of pointer %d\n",sizeof(char *));
printf("size of pointer %d\n",sizeof(int *));
return 0;
}
输出
size of 'array' 20
size of int 4
size of pointer 8
size of pointer 8
size of pointer 8
====================================================================================
函数形参的指针和数组变量
- 如果形参是数组类型,那么数组下标中的大小定义并没有实际用处,本质传递的只是数组的首地址,所以非要使用数组作为形参,一般再增加一个 int arraySize 的参数用来表示数组大小。在函数中对地址内容操作就是对main函数中数组操作(例array_1和函数a)
- 指针类同于数组,传递到函数中的只是首地址,地址变量只占8字节(在64位操作系统中),所以不论定义的数组大小和实际元素个数以及数据类型,在函数中使用sizeof查看的大小相同(例 array_1与array_2,函数a**)
- char 型数组在函数中使用size_t strlen(const char *str)来获取数组中元数个数(例 array_3 )
- 变量地址传到函数中的,对指针指向的内容修改,间接修改了实际参数的值(如上面说到的间接访问)(例 array_4,array_5 和 函数c)
- !对字符串的操作,由于字符串定义为字符串常量,存放在字符串常量区,其数据不可修改,而字符型指针变量存放于栈区,可通过交换栈区指针变量存储的 字符串内存空间中的地址 来实现“交换“,这需要一个二级指针指向前述指针。
(例string1和string2和函数e,将指针变量的地址传给函数,函数形参定义二级指针,取出二级指针中内容交换,实则是交换string1和string2,d函数作为对比)-------------------------[字符交换参考原文]
void a(int *p, int size){
printf("sizeof p size = %d\n",sizeof(p)); //sizeof p size = 8
printf("sizeof p/p[0] size = %d\n",sizeof(p)/sizeof(p[0])); //sizeof p/p[0] size = 2
printf("sizeof p[0] size = %d\n",sizeof(p[0])); //sizeof p[0] size = 4
for(int i=0;i<size;i++){
p[i] = 9;
}
return;
}
void b(int *p){
printf("sizeof p size = %d\n",sizeof(p)); //sizeof p size = 8
printf("sizeof p[0] size = %d\n",sizeof(p[0])); //sizeof p[0] size = 4
puts("");
return;
}
void c(char *p,char *p1,char *p2){
printf("sizeof p size = %d\n",sizeof(p)); //sizeof p size = 8
printf("sizeof p[0] size = %d\n",sizeof(p[0])); //sizeof p[0] size = 1
printf("strlen p size = %d\n",strlen(p)); //strlen p size = 14
char temp = *p1;
*p1 = *p2;
*p2 = temp;
return;
}
void d(char *p1,char *p2){
char *temp = p1;
p1 = p2;
p2 = temp;
return;
}
void e(char** p1,char** p2){
char *temp = *p1;
*p1 = *p2;
*p2 = temp;
return;
}
int main(){
int array_1[30] = {1,1,1,1,1,1,1,1,1,1};
int sizeArray_1 = sizeof(array_1)/sizeof(array_1[0]);
int array_2[15] = {7,7,7,7,7};
char array_3[20] = "TestSentence! ";
char array_4 = 'a';
char array_5 = 'b';
char *string1 = "1st words";
char *string2 = "2nd words";
a(array_1,sizeArray_1);
for(int i=0;i<sizeArray_1;i++){
printf("%d ",array_1[i]);
}
puts("\n");
b(array_2);
c(array_3,&array_4,&array_5);
printf("array_4 = %c\narray_5 = %c\n\n",array_4,array_5);//array_4 = b,array_5 = a
d(string1,string2);
printf("after d: string1 = %s\nstring2 = %s\n",string1,string2);//after d: string1 = 1st words
//string2 = 2nd words
e(&string1,&string2);
printf("after e: string1 = %s\nstring2 = %s\n",string1,string2); //after e: string1 = 2nd words
//string2 = 1st words
return 0;
}
输出
sizeof p size = 8
sizeof p/p[0] size = 2
sizeof p[0] size = 4
9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9
sizeof p size = 8
sizeof p[0] size = 4
sizeof p size = 8
sizeof p[0] size = 1
strlen p size = 14
array_4 = b
array_5 = a
p1 2nd words
p2 = 1st words
p1 00007ff65bd23083
p2 = 00007ff65bd23079
after d:
string1 = 1st words
string2 = 2nd words
after e:
string1 = 2nd words
string2 = 1st words
====================================================================================
二维数组和指针
- 二维数组
e.g float twoDimenArry[3][4];
(其中3为行,4为列)
1> twoDimenArry:是行数组的名和地址
2> twoDimenArry[0]、twoDimenArry[1]… :是列数组的名和地址
3> *twoDimenArry :等价于twoDimenArry[0],也就是列数组的首地址(&twoDimenArry[0][0])
4> *twoDimenArry +1 : 等价于twoDimenArry [0]+1
int main(){
int arr[3][4] = {{11,12,13,14},{21,22,23,24},{31,32,33,34}};
//内存偏移4x4=16(F)字节
printf("addr of arr:%p\tshift 1 :%p\n",arr,arr+1);
//内存偏移1x4=4(F)字节
printf("addr of arr[0]:%p\tshift 1 :%p\n",arr[0],arr[0]+1);
//同楼上
printf("addr of arr[0]:%p\tshift 1 :%p\n",*arr,*arr+1);
//取行数组arr内容再偏移等于列数组arr[0]地址偏移
printf("content in aarr:%p\t== :%p\n",*(arr+0)+1,arr[0]+1);
for(int i=0;i<3;i++){
for(int j=0;j<4;j++){
printf("=========%d 行 %d 列==========\n",i,j);
printf("addr: 0x%p->data:%d \t",&arr[i][j],arr[i][j]); //三种不同的方式
printf("addr: 0x%p->data:%d \t",arr[i]+j,*(arr[i]+j));
//取值:取出行地址偏移后的地址中的值,此时得到列地址*(arr+i)
// 再取列地址偏移后地址中的值,得到数值*(*(arr+i)+j)
printf("addr: 0x%p->data:%d \n\n",*(arr+i)+j,*(*(arr+i)+j));
}
putchar('\n');
}
return 0;
}
输出
addr of arr:0000001832fffbb0 shift 1 :0000001832fffbc0
addr of arr[0]:0000001832fffbb0 shift 1 :0000001832fffbb4
addr of arr[0]:0000001832fffbb0 shift 1 :0000001832fffbb4
content in aarr:0000001832fffbb4 == :0000001832fffbb4
=========0 行 0 列==========
addr: 0x0000001832fffbb0->data:11 addr: 0x0000001832fffbb0->data:11 addr: 0x0000001832fffbb0->data:11
=========0 行 1 列==========
addr: 0x0000001832fffbb4->data:12 addr: 0x0000001832fffbb4->data:12 addr: 0x0000001832fffbb4->data:12
=========0 行 2 列==========
addr: 0x0000001832fffbb8->data:13 addr: 0x0000001832fffbb8->data:13 addr: 0x0000001832fffbb8->data:13
=========0 行 3 列==========
addr: 0x0000001832fffbbc->data:14 addr: 0x0000001832fffbbc->data:14 addr: 0x0000001832fffbbc->data:14
=========1 行 0 列==========
addr: 0x0000001832fffbc0->data:21 addr: 0x0000001832fffbc0->data:21 addr: 0x0000001832fffbc0->data:21
=========1 行 1 列==========
addr: 0x0000001832fffbc4->data:22 addr: 0x0000001832fffbc4->data:22 addr: 0x0000001832fffbc4->data:22
=========1 行 2 列==========
addr: 0x0000001832fffbc8->data:23 addr: 0x0000001832fffbc8->data:23 addr: 0x0000001832fffbc8->data:23
=========1 行 3 列==========
addr: 0x0000001832fffbcc->data:24 addr: 0x0000001832fffbcc->data:24 addr: 0x0000001832fffbcc->data:24
=========2 行 0 列==========
addr: 0x0000001832fffbd0->data:31 addr: 0x0000001832fffbd0->data:31 addr: 0x0000001832fffbd0->data:31
=========2 行 1 列==========
addr: 0x0000001832fffbd4->data:32 addr: 0x0000001832fffbd4->data:32 addr: 0x0000001832fffbd4->data:32
=========2 行 2 列==========
addr: 0x0000001832fffbd8->data:33 addr: 0x0000001832fffbd8->data:33 addr: 0x0000001832fffbd8->data:33
=========2 行 3 列==========
addr: 0x0000001832fffbdc->data:34 addr: 0x0000001832fffbdc->data:34 addr: 0x0000001832fffbdc->data:34
- 总结
/这个部分理解上:大地址中的值是小地址/
====================================================================================
数组指针
对于int型二维数组,当定义int型指针指向该二维数组时,如果发生偏移,对于数组来说时列数组偏移,但对于单个int型指针只是偏移4个字节。
使用普通指针直接指向二维数组报错:
(其中int ()[4]中“4”是二维数组列数组大小)*
- 数据类型 (*指针名)[常量] (常量与二维数组列数组大小一致)
e.g
int array[2][3] = {{1,2,3},{4,5,6}};
int (*p)[3] = array;
- 数组指针+1的偏移量,取决于 指针类型x常量,如下:
例子p2指针偏移了16个字节0x…30->0x…40
int main(){
int arr[3][4] = {{11,12,13,14},{21,22,23,24},{31,32,33,34}};
//int *p1 = arr; //不合法
int (*p2)[4] = arr;
printf("p2 = %p\t arr = %p\n",p2,arr); //对比二者地址
printf("shift p2++: %p",++p2); //数组指针的偏移量
return 0;
}
输出
p2 = 0000008a881ff830 arr = 0000008a881ff830
shift p2++: 0000008a881ff840
- 胡思乱想不可:对于一维数组不可以使用
int (*p)[1]
指向,因为性质完全不同,列数组是一个数组且指针指向的是列数组首地址。
====================================================================================
函数指针
程序中定义的函数在编译时系统会为其分配一段储存空间,这段空间的起始地址(入口地址)称为这个函数的指针
- 函数名就是地址(函数指针变量)
e.g
int eFunction(int a,int b);
int (*p)(int a,int b); (注意括号给予指针优先级,表明这是个指针,而不是返回值为地址的指针函数)
int efunction(int data){
puts("This is a function");
printf("test parameter:%d\n",data);
return 0;
}
int main(){
int (*p)(); //函数指针的类型要与所指向的函数类型一致
p = efunction;
(*p)(27); //注意优先级
return 0;
}
- 函数指针的类型要与函数一致
- *同样用于取出地址中的函数
- (*函数指针名) :括号要注意
一个鸡肋例子练习
#include "stdio.h"
#include <stdlib.h>
int getMax(int a,int b){
return (a>b)?a:b;
}
int getMin(int a,int b){
return (a<b)?a:b;
}
int getEqual(int a,int b){
return a+b;
}
int testFunction(int data1,int data2, int (*pF)(int ,int)){
return (*pF)(data1,data2);
}
int main(){
int a = 10,b=7,ret = 0,cmd = 0;
int (*pfunc)(int ,int);
puts("enter one of 1 to 3:");
scanf_s("%d",&cmd);
//指令匹配:根据不同的指令将函数指针指向不同的函数
switch(cmd){
case 1:
pfunc = getMax;
break;
case 2:
pfunc = getMin;
break;
case 3:
pfunc = getEqual;
break;
default:
exit(-1);
}
//ret = (*pF)(data1,data2) //正常这样就可以了
ret = testFunction(a,b,pfunc);
printf("The result %d\n",ret);
return 1;
}
代码运行正常
- *在代码13,20行函数指针的参数列表中,变量名不写并不影响代码正常余运行,形参列表中强调的是参数的类型,如果没有用到函数名,可以不写,如下void *(*start_routine)(void *)
e.g
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine)(void*), void *arg);
====================================================================================
指针数组
本质是数组,其中每个元素是指针型数据
类型 *数组名[数组长度];
- 指向指针数组的指针,要定义二级指针(int **p)
- 另,对于指针数组中的每个指针元素,都是野指针,如果不赋值就读取数据或者直接写内容,会出现段错误
int main(){
int a=10;
int b=20;
int c=30;
int *parray[3]={&a,&b,&c};
int **p =parray;
for(int i=0;i<3;i++){
printf("%d ",**p++);
}
return 0;
}
输出
10 20 30
函数指针数组
数组中的元素是函数的入口地址
类型 (*指针名[函数元素个数])(参数变量类型(可不写参数变量名),...);
- 数组类型要与函数返回值类型一致
- 在定义和使用时 ( *指针名[函数元素个数] ) 表达式中的 "()"不可缺少,会因为优先级问题出错,后面参数列表的 ()高于取值运算符(*)的。
......
int main(){
int a = 10,b=7,ret = 0;
int (*pfunc)(int,int); //函数指针
/*getMax,getMin,getEqual三个函数名,上面代码段有定义*/
int (*pArray[3])(int,int) = {getMax,getMin,getEqual};//函数指针数组
for(int i=0;i<3;i++){
printf("%d ",(*pArray[i])(a,b));
}
return 1;
}
====================================================================================
指针函数
返回值的类型是指针(地址)的函数
类型 *函数名(形参列表);
int* getScore(int position,int (*p)[4]){
//这里注意不是*p+position/(*p)+position,那样是取出列地址后再偏移
return *(p+position);
}
int main(){
int score[3][4]={{56,47,83,42},
{64,94,30,0},
{74,88,80,99}};
int stuNo = 0;
int *stuScore;
puts("enter the student No.:");
scanf_s("%d",&stuNo);
stuScore = getScore(stuNo-1,score);
for(int i=0;i<4;i++){
printf("%d\t",*stuScore++);
}
return 0;
}
输出
//输入: 1
56 47 83 42
//输入:2
64 94 30 0
//输入:3
74 88 80 99
====================================================================================
二级指针
二级指针变量存放的时指针变量的地址
类型 **变量名;
e.g
int **p2 = &p1;(p1为一级指针)
仔细分析下方各指针的地址和存放的数据
int main(){
int data =10;
int *p = &data;
int **p1= &p;
printf(" data:%d\n *p:%d\n",data,*p);
printf(" addr of data:%p\n",&data);
printf(" p: %p\n",p);
printf(" addr of p: %p\n",&p);
printf(" p1: %p\n",p1);
printf(" *p1: %p\n",*p1);
printf(" **p1: %d\n",**p1);
return 0;
}
输出
data:10
*p:10
addr of data:000000bf249ffe14
p: 000000bf249ffe14
addr of p: 000000bf249ffe08
p1: 000000bf249ffe08
*p1: 000000bf249ffe14
**p1: 10
- 多级指针亦如此
小练习
1》
void getScore(int position,int (*p)[4],int **p3){ //指针的地址用二级指针承接
*p3 = *(p+position); //取出指针中地址,让其指向二维数组中目标行
return;
}
int main(){
int score[3][4]={{156,147,83,142},
{64,94,30,0},
{74,88,99,99}};
int stuNo = 0;
int *stuScore;
int *pRet;
puts("enter the student No.:");
scanf_s("%d",&stuNo);
getScore(stuNo-1,score,&pRet); //传递指针的地址,通过指针的地址间接访问指针
for(int i=0;i<4;i++){
printf("%d\t",*pRet++); //地址偏移打印
}
return 0;
}
二级指针与二维数组
如下方这中二级指针直接指向二维数组时不合法的,会报警告告知是不兼容的,因为二维数组所对应的是数组指针int (*p)[4]
。
如果强行使用,对*p操作时发现产生段错误
如果忽略警告,可以定义数组指针指向二维数组,把数组指针的地址给二级指针,修改就可以完成
int main(){
int score[3][4]={{156,147,83,142},
{64,94,30,0},
{74,88,99,99}};
//int **p = score; //不合法操作,*p中的指针任然是野指针,对其赋值>造成段错误
int (*p)[4] = score; //编译有警告,但是运行无错误,*p中时数组指针
int **pp = &p;
//**pp = 65; //取消注释则score[0][0]改为65
for (int i=0;i<4;i++){
printf(" %d ",*pp[0]++);
}
return 0;
}
====================================================================================
野指针
对没有明确指向的指针进行赋值,会造成段错误(segmentation fault),包括给指针赋值为NULL,因此定义指针时,要注意!
e.g.
int *p;
*p = 5; //段错误
====================================================================================
附录
- 描述和定义
注意判断是数组,还是指针,或者是函数,括号起到很重要的作用
相关题目的入口
END
/<个人学习笔记,如有错误感谢指正,如果帮助到你也是我的荣幸!>/