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
=========00==========
addr: 0x0000001832fffbb0->data:11       addr: 0x0000001832fffbb0->data:11       addr: 0x0000001832fffbb0->data:11
=========01==========
addr: 0x0000001832fffbb4->data:12       addr: 0x0000001832fffbb4->data:12       addr: 0x0000001832fffbb4->data:12
=========02==========
addr: 0x0000001832fffbb8->data:13       addr: 0x0000001832fffbb8->data:13       addr: 0x0000001832fffbb8->data:13
=========03==========
addr: 0x0000001832fffbbc->data:14       addr: 0x0000001832fffbbc->data:14       addr: 0x0000001832fffbbc->data:14

=========10==========
addr: 0x0000001832fffbc0->data:21       addr: 0x0000001832fffbc0->data:21       addr: 0x0000001832fffbc0->data:21
=========11==========
addr: 0x0000001832fffbc4->data:22       addr: 0x0000001832fffbc4->data:22       addr: 0x0000001832fffbc4->data:22
=========12==========
addr: 0x0000001832fffbc8->data:23       addr: 0x0000001832fffbc8->data:23       addr: 0x0000001832fffbc8->data:23
=========13==========
addr: 0x0000001832fffbcc->data:24       addr: 0x0000001832fffbcc->data:24       addr: 0x0000001832fffbcc->data:24

=========20==========
addr: 0x0000001832fffbd0->data:31       addr: 0x0000001832fffbd0->data:31       addr: 0x0000001832fffbd0->data:31
=========21==========
addr: 0x0000001832fffbd4->data:32       addr: 0x0000001832fffbd4->data:32       addr: 0x0000001832fffbd4->data:32
=========22==========
addr: 0x0000001832fffbd8->data:33       addr: 0x0000001832fffbd8->data:33       addr: 0x0000001832fffbd8->data:33
=========23==========
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

函数指针数组

数组中的元素是函数的入口地址

类型 (*指针名[函数元素个数])(参数变量类型(可不写参数变量名),...);

  1. 数组类型要与函数返回值类型一致
  2. 在定义和使用时 ( *指针名[函数元素个数] ) 表达式中的 "()"不可缺少,会因为优先级问题出错,后面参数列表的 ()高于取值运算符(*)的。
......
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;			//段错误

====================================================================================

附录

  1. 描述和定义
    注意判断是数组,还是指针,或者是函数,括号起到很重要的作用
    在这里插入图片描述
    相关题目的入口

END
/<个人学习笔记,如有错误感谢指正,如果帮助到你也是我的荣幸!>/

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值