目录
一、什么是指针变量?
二、 字符指针
1.一般用法:指向字符变量
2.其它用法:指向字符串常量
3.字符串有关 常量区,栈区 创建区别
三、 指针数组
1.定义
2.运用
四、数组指针
1.预备知识
2..定义
五、 数组传参 和 指针传参
1.一维数组传参
2.二维数组传参
六、函数指针
1.定义
2.解引用调用函数指针
3.两个有趣的代码
七、函数指针数组
1.定义
八、指向函数指针数组的指针
1.定义
九、回调函数
1、定义
一、什么是指针变量? 地址:内存会被划分为小的内存单元,每个内存单元都有唯一的编号,这个编号就是地址,地址也叫指针。这个编号是一个数值。
不过我们平时所说的指针是指针变量。
1.指针就是一个变量,用来存放地址,地址唯一标识一块空间。由上述可以得到:内存编号 = 地址 = 指针
2.指针的大小4/8个字节(32位平台/64位平台)。
3.指针是有类型的,指针的类型决定了指针 + - 整数的步长,指针解引用操作时候的权限。
4.指针的 + - 运算是两个相同类型指针之间的元素个数。
指针或者地址,要存储,就可以放到指针变量中去。
二、 字符指针 1.一般用法:指向字符变量
文章图片
2.其它用法:指向字符串常量
文章图片
如上所示的代码:p指向了一个常量字符串,我们需要先知道p是一个指针变量,它存放的内容一定是地址,大小一定为4/8个字节,这里的字符串已经超过了8个字节,所以一定不是存放的字符串,它存放的其实的这个字符串的首字符地址,也就是’a‘的地址,解释了为什么打印一个字符是 ’a‘。那么我们可不可以修改其中的内容呢?
不可以
这里的 p 指针指向的是一个常量字符串,是不允许修改的,如果非要进行修改就会运行出错。程序运行后会直接挂掉。
文章图片
事实上:这里的常量字符串是储存在内存的常量区中的(只读),在里面的数据是不允许修改的。所以为了明确它不能被修改,我们尽量在前面加上 const 关键字修饰一下。
文章图片
这样如果我们不小心,将其修改了,编译器也会告诉我们报错的信息。3.字符串有关 常量区,栈区 创建区别 我们可以看看一下一道列题
int main()
{
const char* p1 = "abcdef";
//p1 指向 'a' 地址
const char* p2 = "abcdef";
//p2 指向 'a' 地址 char arr1[] = "abcdef";
//arr1 指向 'a' 地址
char arr2[] = "abcdef";
//arr2 指向 'a' 地址//以下是为了判断是否两个地址是相等的
if (p1 == p2)
{
printf("p1 == p2\n");
}
else
{
printf("p1 != p2\n");
} if (arr1 == arr2)
{
printf("arr1 == arr1\n");
}
else
{
printf("arr1 != arr2\n");
} return 0;
}
它的输出是什么呢?
文章图片
我们可以直观的看到p1 和 p2 指向的地址是相同的,arr1 和 arr2 指向的地址是不相同的。
两者的区别就在于这里:
p1 和 p2 指向的是常量字符串,是储存在常量区的,而常量的数据是不能被修改的,所以同一个数据不会被多次创建,只会在常量区进行一次数据的创建,相当于p1 、 p2 指向的是同一个内容。
而arr1 和 arr2 是在栈区创建了两个不同的空间,其内容是用字符串来初始化的,所以两者的地址不同。
三、 指针数组 1.定义 定义:指针数组是一个数组,是用来存放指针的一个数组。
int main()
{
int* arr[10];
//整型指针数组
char* ch[10];
//字符指针数组 return 0;
}
解释:int* arr[10]; arr先与[10]结合,说明arr是一个数组,剩下的int* 说明它其中的元素类型是int*.2.运用
其他类型的指针数组解释方法和定义方法类似。
int main()
{
int arr1[] = {1,2,3,4};
int arr2[] = {1,2,3,4};
int arr3[] = {1,2,3,4};
int* arr[10] = { arr1, arr2, arr3 };
return 0;
}
这样可以模拟出来一个二维数组。
四、数组指针 1.预备知识 首先需要明确知道数组首元素地址和整个数组地址概念的差别:
一般情况下数组名表示数组首元素地址,除了两种情况:数组名放在sizeof内部,或者&取地址数组名时,表示的是整个数组的地址。
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,0 };
printf("%p\n", arr + 0);
//数组首元素地址
printf("%p\n", arr);
//数组首元素地址
printf("%p\n",&arr);
//整个数组的地址 return 0;
}
文章图片
我们发现:三者的数值是一样的。
文章图片
我们对三者进行加一处理,发现数值出现了不同,前两者加一,是加了四个字节,而整个数组地址加一是跳过了 (010FFC74 - 010FFC4C = 40)个整型,就相当于这里的整个数组。从这里我们得出了他们之间的差距。2..定义 定义:数组指针是一个指针,是用来指向一个数组的指针。
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,0 };
int(*p)[10] = &arr;
//一个整型数组元素为10个的指针 return 0;
}
解释:p 先和 * 结合,说明 p 是一个指针,然后再和[10]结合,说明 p 指向的是一个元素为10个的数组,int说明元素类型是整型。
这里 p 指针的类型是:int (*)[10]。
五、 数组传参 和 指针传参 1.一维数组传参 形参可以写成数组,也可以写成指针。
void print(int arr[], int sz)//形参为数组
{
int i = 0;
for (i = 0;
i < sz;
i++)
{
printf("%d ",arr[i]);
}
}
void print1(int* arr, int sz)//形参为整型指针
{
int i = 0;
for (i = 0;
i < sz;
i++)
{
printf("%d ",arr[i]);
}
}
void print3(int (*p)[10], int sz)//形参为数组指针
{
int i = 0;
for (i = 0;
i < sz;
i++)
{
printf("%d ",(*p)[i]);
}
}
int main()
{
int arr[10] = { 0 };
int sz = sizeof(arr) / sizeof(arr[0]);
print(arr, sz);
return 0;
}
2.二维数组传参 形参可以写成数组,也可以写成指针。需要知道二维数组的首元素地址是它的第一行的地址。
void print(int arr[3][5], int c, int r)//形参为数组
{
for (int i = 0;
i < c;
i++)
{
for (int j = 0;
j < r;
j++)
{
printf("%d ",arr[i][j]);
}
printf("\n");
}
}
void print1(int (*arr)[5], int c, int r)//形参为首元素指针
{
for (int i = 0;
i < c;
i++)
{
for (int j = 0;
j < r;
j++)
{
printf("%d ", *((*arr + i) + j));
}
printf("\n");
}
}
void print(int (*p)[3][5], int c, int r)//形参为二维数组指针
{}
int main()
{
int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };
print(arr, 3, 5);
return 0;
}
注意:形参不能用二级指针,二级指针是一个指针变量的地址,而二维数组的元素是一行一行的,是一维数组,类型应该是一维数组指针的类型。练习:
int* parr1[10];
//数组,每个元素的类型是int*int (*parr2)[10];
//指向数组的指针int (* parr3[10])[5];
//数组,每个元素的类型是:int (*p1)[5];
六、函数指针 1.定义 定义:指向函数的指针。
&函数名 == 函数名,函数的地址就是函数名。
void test(int a)
{}
int main()
{
void (*p1)(int) = test;
void (*p2)(int) = &test;
return 0;
}
解释:p1 先和 * 结合,说明p1是一个指针,在和()结合,说明指向的是一个函数,void (int),声明函数的参数是int,返回类型是void2.解引用调用函数指针 函数指针的解引用调用可以不写 * ,也可以不写 * 。
void test(int a)
{}
int main()
{
void (*p1)(int) = test;
void (*p2)(int) = &test;
(*p1)(2);
p1(2);
//这两种方式都可以调用函数 return 0;
}
3.两个有趣的代码
int main()
{
//代码1
(*(void (*) ())0)();
//代码2
void (*signal(int, void(*) (int)))(int);
return 0;
}
代码1:
void (*)() 是一个函数指针类型;
(void (*)())0 相当于把 0 地址转化为了 这个函数的指针类型,就相当于再 0 地址处放了一个 该函数。
(* (void (*) (0))0)(); 相当于解引用0地址处的这个函数指针,然后进行传参。
代码2:
signal 先和 (int, void (*) (int))结合,说明他是一个函数,参数为int ,void (*)(int),现在我们明确了它的函数名和参数,还需要知道它的返回类型,先看一个函数,int Add(int),这个函数的函数名是Add,参数是int,把函数名和参数去掉后就是返回类型,同样,把这里的函数名和参数去掉,void (*)(int)这个就是它的返回类型。
方便理解我们可以简化一下代码2定义的函数
七、函数指针数组 1.定义 定义:是一个数组,存放函数指针的数组。typedef void (*pf_t)(int); //重定义一下函数类型的名字void (*signal(int, void(*) (int)))(int); //修改后 pf_t signal(int, pf_t);
void test1(int a)
{}
void test2(int a)
{}
int main()
{
void (*p[2])(int) = { test1, test2 };
return 0;
}
解释:p 先和 [2] 结合,说明 p 是一个数组,再和 * 结合,说明说明数组中每一个元素都是指针,再和()结合,说明指向的每一个元素都是函数类型的,去掉数组名p和[2]剩下void (*) (int) ,说明函数的返回类型是void,参数类型是int.八、指向函数指针数组的指针 1.定义
定义:是指针,指向函数指针数组的指针。
void test()
{}
void test1()
{}void test2()
{}
int main()
{
void (*pf[3])() = { test1, test2, test };
//函数指针数组 void (*(*p)[3])() = { &pf };
//函数指针数组指针 return 0;
}
解释:(*p)说明p是指针,[]说明指向了一个数组,去掉函数名是void (*)(),说明指向的数组的元素类型是void (*)()类型函数的指针。九、回调函数 1、定义
定义:通过函数指针来调用的函数
例如:将B函数的指针传给A函数,在A函数里面通过B函数的指针来调用B函数,那么这个B函数就是回调函数。
void B()
{
printf("hehe\n");
}
void A(void (*pf)())
{
pf();
//在A里面用其的函数指针来调用B,那么B就是一个回调函数
}int main()
{
A(B);
//将B函数的地址传给A return 0;
}
【C语言|指针进阶超详解】
推荐阅读
- 1000道程序员常见问题解析|一文搞懂Linux 内存管理原理
- c语言|问题 D: 马走日
- C语言教程 - 制作单位转换器
- 软件测试|详解软件测试中白盒测试基本概念及四种白盒测试方法
- C++|C++中的类与对象
- c语言|数组与指针超强巩固练习题——搞清各种数组与指针的关系
- c语言|通讯录的思路讲解和代码实现
- C语言|【C语言】动态内存管理详解
- c语言|C语言之动态内存管理