前言
推荐宝藏up视频C语言指针,up讲的超级好!本文便是基于up视频整理的C语言指针笔记。笔记仅用于复盘,真正学懂还得看up视频!
C语言指针笔记
指针的概念
int *p = &a;
*p = 100;
- 在64位操作系统里面,指针占8字节
- 星号的两种含义:
1、定义的时候(前面有类型),表示后面的变量是指针
2、使用的时候:表示取值(取指针指向的内存空间的值) - int*和char*步长(单位)不一样,指针加一之后的结果不同,取决于指针类型,如int为4字节,char为1字节
*px++和(*px)++
练习:
int x = 3, y = 0, *px = &x;
y = *px +5; //y等于8
y = ++*px; //y等于4
y = *px++; //先把*px(3)赋值给y,再把px+1(变成野指针)
- *px++和(*px)++是有区别的,前者是对px加加,后者对*px加加
const的用法
void f()
{
int num;
const int *p1 = # // const修饰*p1 即num
//(*p1)++;
p1++;
int *const p2 = #// const修饰p2
//p2++;
(*p2)++;
const int *const p3 = #// 即修饰了p3,也修饰了*p3,二者均不能修改
}
- const就近原则,修饰哪个,哪个就不能改
野指针和空指针
int main()
{
int *p = NULL;
*P = 100; // 段错误,访问了不能访问的内存
// 如何合法使用内存
// 1、系统分配的内存
int a;
int *p1 = &a;
// 2、用户申请的内存(堆内存)
char* str = (char*)malloc(32);
free(str); // 释放内存,如果不释放,内存泄漏
str = NULL;
return 0;
}
- 三种合法使用内存的方法:
1、使用系统分配的内存(定义一个变量,把变量的地址赋值给指针)
2、用户使用malloc自己申请堆内存
3、将字符串赋值给指针,字符串存放在只读数据区 - 申请堆内存注意事项:
1、使用完后释放内存,避免内存泄漏
2、释放完后,将指针赋值为空,否则指针会指向被释放掉的内存,成为野指针。(空指针是个可控指针)
指针和数组
int main()
{
int a[5] = {1,2,3,4,5};
int *p = a;
}
- 从使用的角度,指针和数组可以相互替换
- 通过指针的形式访问数组,a[i]等价于*(a+i)
指针和数组的区别
int main()
{
char str[32] = "helloworld";
char *p = "helloworld";
//str++; // 数组名是常指针(地址常量)不能修改。
p++; // 指向下一个元素
str[0] = 'x';
//p[0] = 'x'; // 字符串常量存在只读数据区里面,不能修改
}
- 数组名是常指针(地址常量)不能修改。数组名不能++,在定义数组的时候已经给数组分配的空间,数组名++代表把原先分配好的数组地址整体往后移动,这是不被允许的。
- 数组定义的字符串存放在栈空间,而指针指向的字符串常量在只读数据区,前者可修改,后者不能修改。
- sizeof(str)代表整个数组的长度,sizeof(p)代表指针长度
void f(int a[])
{
printf("%lu\n",sizeof(a)/sizeof(a[0]));// 2
}
int main()
{
int a[10] = {0};
printf("%d\n",sizeof(a)/sizeof(a[0]));// 10
f(a);
return 0;
}
- 特别注意!数组名作为参数传递之后会变成指针,f()函数中的sizeof(a)在64位操作系统里面,是a这个指针的大小,即8字节。而主函数中的sizeof(a)则代表a这个数组的大小,即10字节。
练习:
int a[5] = {1,2,3,4,5};
int *p1 = (int*)(&a + 1);
int *p2 = (int*)((int)a + 1);
int *p3 = (int*)(a + 1);
//假设a[0]地址为0x100,p1[0],p2[0],p3[0]的值分别为多少?
// &a+1为0x114 p1[0]就是0x114那个地址对应的值
// (int)a+1为0x101 p2[0]就是0x101地址对应的值
// a+1为0x104,p3[0]是0x104对应的值,即2,只有这个是正常的写法
- &a表示整个数组的地址
- a表示数组首元素的地址
- (int*)代表强转,比如&a + 1本来指向从0x114开始的5个字节的整体,强转之后就只指向0x114那一个字节
指针数组
int* p[5]本质是一个数组,数组的每个元素都是指针类型
数组指针
int (*p)[5]本质是一个指针,该指针指向数组
指针和二维数组
int a[3][4]
- a表示数组首行地址,一次加一行即16字节
- a[0]表示首行首元素的地址, 一次加一个元素即占4字节
- &a表示数组地址 ,一次加一个数组即占48字节
- 行:sizeof(a) / sizeof(a[0]) 即48/16 = 3
- 列:sizeof(a[0]) / sizeof(a[0][0]) 即16/4 = 4
用数组指针表示二维数组:int(*p)[4] = a;
- *(p+i)代表第i行首元素的值
- *(p+i)+j代表第i行第j列的地址
- *(*(p+i)+j)代表第i行第j列元素的值
用指针数组表示二维数组:int* p[3] = {a[0],a[1],a[2]};
- p[i][j]等价于a[i][j]
用指针表示二维数组:int* p = a[0];
- 当成一维数组来处理
int *p = a[0];
for (i = 0; i < 12; i++)
{
printf("%d ",p[i]);
}
总结
- &a表示指向二维数组的指针,步长为整个数组
- a表示二维数组名,指向一维数组a[0],即第0行首地址,步长为数组的一行
- a[0],*(a+0),*a表示第0行第0列元素地址
- a+1,&a[1]表示第1行首地址
- a[1],*(a+1)表示第1行第0列元素地址
- a[1]+2,*(a+1)+2,&a[1][2]表示第1行第2列元素地址
- *(a[1]+2),*(*(a+1)+2),a[1][2]表示第1行第2列元素的值
- 理解:一级地址是元素地址(a[0]),二级地址是行地址(a),三级地址是整个数组地址(&a)
指针和字符串
int main()
{
char *string[]={"abc","123"};
//printf("%s\n",string); // 报错
return 0;
}
- string是个指针数组,本质是个数组,里面存放的是指针
指针和函数
函数指针
void f1()
{
printf("helloworld\n");
}
int add(int x, int y)
{
return x+y;
}
typedef int (*T)(int,int);// 声明一个新的类型T T表示函数指针类型
int main()
{
void (*p)(); // 定义函数指针
p = f1;
p(); // 通过函数指针调用函数 等价于f1()
int (*q)(int,int) = add;
printf("%d\n",q(1,2));
T q1 = add; // 等价于int (*q)(int,int) = add;
return 0;
}
指针函数
char *init() // 指针函数
{
//char str[32] = {0}; // 栈内存在函数运行结束之后会被释放掉!
char *str = (char *)malloc(128);
return str;
}
int main()
{
char *s = init();
strcpy(s,"hello");
free(str);
str = NULL;
return 0;
}
-
函数指针和指针函数的区别:
-
1、int (*p)()定义的是一个变量,表示p是一个指向函数入口的指针变量,该函数的返回值是整型,(*p)两边的括号不能少
-
2、int *p()则不是变量声明而是函数声明,说明p是一个指针型函数,返回值是一个整型变量的指针,*p两边没有括号
-
函数指针可以用作回调函数,把函数名作为另一个函数的参数,以此来修改函数的功能。
复杂类型声明(右左法则)
int*(*(*fp)(int))[10];
fp是一个指针,指向一个函数,该函数有一个int类型的参数,返回值是指针,该指针指向一个有是个元素的数组,数组里的元素是int*类型。
int *(*(*array[5])())();
array是一个指针数组,有5个元素,每个元素都是指针,指向一个函数,该函数没有参数,返回值一个指针,指针又指向一个没有参数的函数,返回值为int*类型指针。