文章目录
1 变量与指针
变量是某个地址空间的别名
int a = 1;
1 指针就是地址0x11110000
2 变量名就是地址的概念,int a = 1; 就是向a地址(编译器随机分配,本文默认为0x11110000)的存储空间写入数据1的补码。
2 指针运算和指针类型
下面po两张图方便理解。
注意:
1️⃣int a = 1;表示a地址处所存放的变量所占字节为4,编译器读的时候取4个字节;
2️⃣ char a = “1”; 表示a地址处所存放的变量所占字节为1,编译器读的时候取1个字节;
3️⃣ int *p = &i; 表示p所保存的地址处所存放的数据类型为int型,对应上图表示 *P所对应的值所占字节数为4,即0x11110000 ~ 0x11110004处是一个int值,取出来之后按int进行编译。
↪️ 换句话说(可以数星号):
↗️ int * p 表示p这个变量是int* 类型,就是表示一个int存储空间的地址值,既然是地址值,那么它的定义处就应该是int* p = &a;(p表示的是int数据的地址)
↘️ int** q表示q这个变量是int**类型,即q表示一个int* 类型的地址值,那么它的初始化就应该是:int**q = &q;(q表示的是int数据地址的地址)
3 空指针、野指针、空类型
/*空指针,任何栈和进程都不得访问0x00000000,一旦使用会进行报错*/
/*空指针适用机会:指针只是声明了,但暂时还不知道如何用它,可以先定义为NULL*/
int *p = NULL;
/*野指针,只进行*p的声明,但不能直接使用*/
/*如果只是进行读取的话,有的编译器报错,有的不报错,但却没有任何意义*/
int *p;
/*但是进行写的话,绝对禁止,因为你不知道p到底指向哪里,如果是操作系统内核或者内部保留的寄存器不允许访问*/
int *p;
*p = 1;
/*一般不知道什么类型的时候使用viod*类型,是模板类型,能赋予除函数指针外的任何类型*/
/*在定义函数形参时使用较多*/
void * p;
4 指针初始化
初始化是指指针初始化,即指针一定要指向一段已知内存的地址空间,
未经初始化的指针严禁使用
/*****************************************************
*第一种方法
*****************************************************/
int i; //编译器自动分配了i变量的内存地址,是一段已知内存的地址
int* p = &i; //p变量是指针,指向的上面i的地址,此时的p是一段内存分配好的地址,此后就可以正常操作了
*p = 2; //向i地址处写入值:2
/*****************************************************
*第二种方法
*****************************************************/
int* p = NULL; //先定义一个空指针,一用就报错,因为0x00000000地址所有的进程和栈都无法使用
int i = 2; //定义一段内存,有一段已知的内存
p = i; //令p指向i
5 指针运算
常用的指针运算包括以下这些
- & 取地址
- * 取值
- < > = 关系运算符(主要是指向连续存储空间时使用较多)
- ++ 指针自加1
- – 指针自减1
但注意++、--并不是地址的简单地址+1,而是根据指针的数据类型所占的空间大小进行加减
具体观察以下代码:
char a = 'a';
char* p_char = &a;
int i = 1;
int *p_int = &i;
printf("p_int = %p ",p_int); //int型每次地址加4,int占4个字节
p_int++;
printf("p_int = %p ",p_int); //int型每次地址加4,int占4个字节
p_int++;
printf("p_int = %p ",p_int); //int型每次地址加4,int占4个字节
p_int++;
printf("p_int = %p\n",p_int); //int型每次地址加4,int占4个字节
printf("p_char = %p ",p_char); //char型每次地址加1,char占1个字节
p_char++;
printf("p_char = %p ",p_char); //char型每次地址加1,char占1个字节
p_char++;
printf("p_char = %p ",p_char); //char型每次地址加1,char占1个字节
p_char++;
printf("p_char = %p\n",p_char); //char型每次地址加1,char占1个字节
system("pause");
return 0;
/*************************************************************************
**********************************测试结果如下*****************************
p_int = 0061FF10 p_int = 0061FF14 p_int = 0061FF18 p_int = 0061FF1C
p_char = 0061FF17 p_char = 0061FF18 p_char = 0061FF19 p_char = 0061FF1A
*************************************************************************/
6 指针与一维数组
6.1 相似之处
数组是一段
连续存放的特定数据类型的内存
/**********************************************
*其实也可以换个角度看int arr[3];
*该数组就是arr开始的地址,因为是int型,每个占4字节
*因此这段内存就是[arr,arr+3]、[arr+4,arr+7]、[arr+8,arr+11]
*每个区间里面存放的值按照int类型进行编译
*具体内存数据显示如下图所示:
**********************************************/
int arr[3] = {1,2,3};
int *p = arr;
int i;
for(i=0;i<sizeof((arr)/sizeof(*arr));i++)
{
printf("%p --------> %d",a+i,arr[i]);
//printf("%p --------> %d",p+i,*(p+i));
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mOiEeCTL-1652836652196)(c-指针/1652685550997.png)]
总结:
value: a[i] = *(a+i) = *(p+i) = p[i]
&a[i]: &a[i] = a+i = p+i = &p[i]
6.2 区别之处
/**********************************************
* p是指针变量,arr数组名是指针常量
* p++可以改变p的值,arr不可以进行arr++操作
* 因此p++操作之后一定要注意p指向的地址值已经发生变化
**********************************************/
int arr[3] = {1,2,3};
int *p = arr;
int i;
for(i=0;i<sizeof(arr)/sizeof(*arr);i++)
printf("%p--->%d\n",&arr[i],arr[i]);
for(i=0;i<sizzeof(arr)/sizeof(*arr);i++)
scanf("%d",p++);
p = arr; //p已经发生了变化,要进行复原
for(i=0;i<sizzeof(arr)/sizeof(*arr);i++)
printf("%p--->%d\n",p,*(p++);
笔记:
p++:p的指向发生了变化
p+1:p的指向没有发生变化
7 指针与二维数组
二维数组有点意思,可以帮你进行指针的进一步理解,我知道我的理解和传统介绍不尽相同,但请先按我的笔记进行理解。
你可以把二维数组当成一个新的复合数据类型:int a[2][3]二维数组表示以下内容:
a是顶层结构:
a[0]和a[1]是第一层结构,隶属于a;
a[0][0]、a[0][1]、a[0][2](a[1][0]、a[1][1]、a[1][2])是第二层结构,隶属于第一层结构;
注意:
所谓*是指进入下一层结构,也就是a是最外层的地址,加一个*表示进入第一层,他有两个元素a[0]和a[1],*a表示*(a+0)的复合数据类型,表示a[0][0]、a[0][1]、a[0][2]的整体,**a表示*(*(a+0)+0)的数据类型,即a[0][0]的值
a+1是指地址从a加上一个它元素大小的数值,(对比之前的int a[2],a+1是加一个int(4)的大小)即地址元素相加3*sizeof(int)的值。*a的包含元素是int型,故*a+1只是加4的值
int a[2][3] = {1,2,3,4,5,6};
int i,j;
for(i=0;i<2;i++)
{
for(j=0;j<3;j++)
{
printf("a[%d][%d]--->%p\n",i,j,&a[i][j]);
}
printf("\n");
}
printf("a--->%p\n",a);
printf("*a--->%p\n",*a);
printf("a+1--->%p\n",a+1);
printf("*a+1--->%p\n",*a+1);
printf("**a--->%d\n",**a);
printf("*a[0]--->%d\n",*a[0]);
printf("a[0][0]--->%d\n",a[0][0]);
printf("*(a+1)--->%d\n",*(a+1));
printf("**(a+1)--->%d\n",**(a+1));
system("pause");
return 0;
/*************************************************************************
**********************************测试结果如下*****************************
a[0][0]--->0061FF00 a[0][1]--->0061FF04 a[0][2]--->0061FF08
a[1][0]--->0061FF0C a[1][1]--->0061FF10 a[1][2]--->0061FF14
a--->0061FF00
*a--->0061FF00
a+1--->0061FF0C
*a+1--->0061FF04
**a--->1
*a[0]--->1
a[0][0]--->1
*(a+1)--->6422284
**(a+1)--->4
*************************************************************************/
/**********************************************
*如果上面的例子你看懂了,那么你应该明白p初始化应该怎么初始化了
*有以下方法
**********************************************/
int a[2][3] = {1,2,3,4,5,6};
int i;
int *p = *a;
// 等价int *p = &a[0][0];
for(i=0;i<6;;i++,p++)
printf("%d",p[i]);
printf("\n");
8 指针数组与数组指针
数组指针,主要是指针指向一种数据类型的数组
int (*p) [3] 它表示两层意思:
1️⃣ p是一个指针,
2️⃣ 他指向的内容是一个 int [3]这样的数组,因此每一次p+1内存地址增加的值为12
此时p和a[2][3]中的a类型等价。
指针数组:
int * arr[3]; 指针数组;int * 是表示一个类型 ,即整型指针
char *name[2] ={“dan”,“san”};
9 指针与字符数组
/**********************************************
*普通用法
**********************************************/
char str[] = "hello";
char *p = str+1;
/**********************************************
*赋值,字符串等号左边的str是常量,不能直接赋值
*但str的本质是一段连续的存储空间
**********************************************/
char str[] = "hello";
//str = "world"; 错误
sizeof(str); //6算上结尾的0
strlen(str); //5不算结尾的0
strcpy(str, "world");
/**********************************************
*字符串指针
*字符串指针的本质还是一个地址
**********************************************/
char *str = "hello"; //注意hello是char常量,字符堆放在一起,不允许改变,str = 0x00406044
sizeof(str); //4,其实就是指针的长度,32位地址为4
strlen(str); //5不算结尾的
//strcpy(str, "world"); 不能这么用,str是一个地址
str = "world"; //注意此时str指向的地址发生了变化,不是在源地址“hello”段上修改的,str = 0040604E
10 const与指针
常量两种方法
1️⃣ #define PI 3.14
缺点:不检查语法错误
2️⃣ const float pi = 3.14;
const作用是将变量pi保存了常量3.14,表示我不希望该变量的值发生任何改变
int main{
const float pi = 3.14;
// pi = 3.1415926 错误,const类型无法修改
/************************************************
float *p = π
*p = 3.1415926 只有警告,不会报错,内容可以修改
************************************************/
system("pause");
return 0;
}
指针常量与常量指针
1️⃣ 常量指针:const int *p; int const *p; (const在*前,修饰的是*p)
指针指向的内容不能发生变化,地址可以
2️⃣ 常量指针:int *const p; (const在*后,修饰的是p)
指针指向的地址不能发生变化,内容可以变化
int main{
int i =1;
int const *p = &i;
i=10; //可以通过i来修改,i没有被const修饰,可以这样修改
//F *p=10; 错误,*p是被const修饰了
int j = 100;
p = &j; //可以修改p的指向
/************************************************
int *const p = π
//p = &j; 报错,指针的指向不能发生变化
************************************************/
}
const int *const p p的指向和p的内容都不能发生变化
常见的函数函数:char *strcpy( char *dest , const char *src) :证明我对你传过来的src内容不会进行任何修改
本文主体结构及主体代码参考李慧琴老师公开课,但是加入了大量自己的思考,如果有时间,B站搜索相关视频进行观看。