前言:Hello大家好😘,我是心跳sy,今天为大家带来C语言指针的知识点,由于内容过多,跨度较大,指针的篇章分为初阶和进阶两个章节,本篇文章对指针的进阶知识点进行汇总,内容全面,友友们也可放心食用哦💞💞💞我们一起来看看吧
目录
💞心跳sy的C语言专栏💞⏪C语言知识点汇总到这了,有兴趣的友友可以订阅看看哟~
🎀小 tip:本文内容较长,知识点汇总全面,所有代码皆配有详细解释,友友们放心观看,喜欢的友友们点个关注不迷路😘😘😘~后续会持续更新C++和Linux的知识,我们一起成长!💯
👉上篇指针初阶基础知识点击 http://t.csdnimg.cn/kCJvh ,建议先看完基础知识再来看进阶~
一、字符指针 💫
1、⭐️字符指针的概念及用法⭐️
在上篇初阶文章中我们了解到指针有许多类型,指针的类型中有一种指针类型为字符指针 char*
字符指针是一个指向字符或字符数组或字符串(以空字符
'\0'
结尾的字符数组)的指针,存储字符数组或字符串地址,字符指针在处理文本数据时非常有用,它允许程序读取、操作和处理字符串。
🌸插叙:
字符数组:用于存储字符序列的数组,例如 char str[] = "Hello";定义了一个包含字符序列 "Hello" 的字符数组,并以空字符 ‘\0’ 结尾,因此实际上在内存中存储的是 "Hello\0".
🍀字符指针一般是这样使用的:
char *ptr = "Hello"; //定义了一个指向字符串 "Hello" 的字符指针
⚠️注意语句将字符串 “Hello” 的起始地址赋值给指针变量 ptr
⚠️将字符 'w' 的地址存储到字符指针变量 pc 中
int main()
{
char ch = 'w';
char *pc = &ch;
*pc = 'w';
return 0;
}
2、⭐️常量字符串指针⭐️
👉先来看一段曾出现在面试题里的代码:
#include <stdio.h>
int main()
{
char str1[] = "hello bit.";
char str2[] = "hello bit.";
const char *str3 = "hello bit.";
const char *str4 = "hello bit.";
if(str1 ==str2)
printf("str1 and str2 are same\n");
else
printf("str1 and str2 are not same\n");
if(str3 ==str4)
printf("str3 and str4 are same\n");
else
printf("str3 and str4 are not same\n");
return 0;
}
👉输出结果:
⭕️可以看出,str1和str2所在内存块不一样,str3和str4所在内存块一样;
⭕️C语言规定,当用相同的常量字符串去初始化不同数组时,会开辟出不同的内存块;所以str1 和 str2 是两个独立的字符数组,str1 和 str2 各自存储在栈上的不同位置,所以输出地址不一样;
⭕️首先 const char* 是常量字符串指针,在C语言中用于指向不可变的字符串数据;
⭕️C/C++会把常量字符串存储到单独的一个内存区域,当几个指针指向同一个字符串时,它们实际指向的是同一块内存,这里const char *表示一个限定不会改变的指针变量,指向常量,其地址与值均不能被改变,所以输出地址是一样的
👉指向常量的字符串指针可以通过以下方式定义:
const char* strPtr = "Hello, World!";
⭕️在这里, strPtr 是一个指向声明为常量的字符串的指针。这意味着, strPtr 不能通过修改字符串的内容。例如,以下操作是非法的:
*strPtr = 'h'; // 错误:尝试修改常量字符串内容
二、指针数组与数组指针 💫
1、⭐️指针数组⭐️
🌈指针初阶篇最后提到了指针数组,我们知道了指针数组本质是数组,是存放指针的数组。
int* arr3[5];
👉arr3是一个数组,有五个元素,每个元素是一个指针;
一下都是指针数组:
int* arr1[10]; //整形指针的数组
char *arr2[4]; //一级字符指针的数组
char **arr3[5];//二级字符指针的数组
2、⭐️数组指针⭐️
⭕️与指针数组本质是数组对应,数组指针就是指针,意如其名,就是指向数组的指针
⭕️我们之前以及学习过指向其他类型的指针,比如:
· 整形指针: int * p1; 能够指向整形数据的指针
· 浮点型指针: float * p2; 能够指向浮点型数据的指针
❓那么数组指针长什么样子呢?先来看看两组对比:
int *p1[10];
int (*p2)[10];
//p1, p2分别是什么?
🔶第一个我们刚刚才学习过,就是指针数组,p1 是一个包含10个元素的数组,其中每个元素都是一个指针,这些指针指向 int 类型的值;
🔶第二个就是数组指针了,p2 是一个指针,该指针指向一个包含10个 int 类型元素的数组,这里的圆括号是必要的,表明 p 需要先和 * 结合,又因为 [ ] 的优先级比 * 高,所以必须加上();
✅记忆方式:可以理解为(*p)优先级会更高一点,来保证 p 先与 * 结合,所以是数组指针。
👉学完数组指针与函数指针,我们再来看一个混合用法:
int (*parr3[10])[5];
🔶parr3 是一个数组, 是存放数组指针的数组(该数组有10个元素),存放的这个数组指针,指向的数组数组有5个元素,每个元素是 int 类型。
3、⭐️&数组名VS数组名⭐️
👉对于下面的数组:
int arr[10];
❓arr 和 &arr 分别是啥?
🔸我们知道arr是数组名,数组名表示数组首元素的地址;
🔸那么&arr 是什么呢?来看下面的代码:
#include <stdio.h>
int main()
{
int arr[10] = {0};
printf("%p\n", arr);
printf("%p\n", &arr);
return 0;
}
🔸可见数组名和&数组名打印出来的地址是一样的;
❓❗难道这两个真的是一样的吗?再来看一段代码:
int main()
{
int arr[10] = { 0 };
printf("arr = %p\n", arr);
printf("&arr= %p\n", &arr);
printf("arr+1 = %p\n", arr+1);
printf("&arr+1= %p\n", &arr+1);
return 0;
}
🔹可以看到,前两个数组名未加1时,数组名和&数组名的地址相同, 当加一之后就不一样了,为什么呢?
printf("arr+1 = %p\n", arr + 1);
🔹这行代码打印了数组 arr 的第二个元素的地址( arr[1] 的地址)
printf("&arr+1= %p\n", &arr + 1);
🔹这行代码尝试是对整个数组的地址进行加1操作。由于 &arr 的类型是指向包含10个 int 的数组的指针,对其进行加1操作相当于将地址向后移动 sizeof(arr) 个字节(即10个 int 的总大小),远远大于 arr+1 的结果,所以这一组地址不一样;
💯总结:
🔴arr 和 &arr 在数值上可能相同,但类型意义不同。
🔴arr+1 移动到数组的下一个元素。
🔴&arr+1 移动到下一个地址,其偏移量等于整个数组的大小,因为它是基于包含10个 int 的数组的指针。
三、数组传参与指针传参 💫
💭思考:在编写代码时,我们经常需要将数组或者指针作为参数传递给函数作为参数,那么函数的参数应该怎样设计呢?
1、⭐️一维数组传参⭐️
#include <stdio.h>
void test(int arr[])//ok?
{}
void test(int arr[10])//ok?
{}
void test(int *arr)//ok?
{}
void test2(int *arr[20])//ok?
{}
void test2(int **arr)//ok?
{}
int main()
{
int arr[10] = {0};
int *arr2[20] = {0};
test(arr);
test2(arr2);
}
🌈以上所有传参都正确,数组在传参的时候,形式可以写成数组,也可以写成指针,一维指针的地址可以用二维指针接收;
2、⭐️二维数组传参⭐️
void test(int arr[3][5])//ok?
{}
void test(int arr[][])//ok?
{}
void test(int arr[][5])//ok?
{}
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
//这样才方便运算。
void test(int *arr)//ok?
{}
void test(int* arr[5])//ok?
{}
void test(int (*arr)[5])//ok?
{}
void test(int **arr)//ok?
{}
int main()
{
int arr[3][5] = {0};
test(arr);
}
🌈第二个不行,行可以省略,列不可以;第四个不可以,其形参是整型指针,用来指向整型变量,但是 test 函数传进去的是二维数组第一行的地址;第五个也不可以,它是一个指针数组,写法不伦不类,要么写成数组(如第一个),要么应写成数组指针;最后一个也不行,二级指针必须接收一级指针的地址,其他的均正确。
3、⭐️一级指针传参⭐️
👉可以修改数据内容,形参的改变影响实参
#include <stdio.h>
// 函数声明
void updateValue(int *p) {
*p = 100;
}
int main() {
int value = 10;
printf("Before update: %d\n", value);
updateValue(&value);
printf("After update: %d\n", value);
return 0;
}
4、⭐️二级指针传参⭐️
void test(int** ptr)
{
printf("num = %d\n", **ptr);
}
int main()
{
int n = 10;
int* p = &n;
int** pp = &p;
test(pp);//输出10
test(&p);//输出10
return 0;
}
代码很简单,相信友友们都能看懂,就不解释了
总结:
🔴数组传参:
🔸1、传递的是数组首元素的地址
一维数组传参,传递的是第一个元素的地址;
二维数组传参,传递的是第一行的地址;
🔸2、数组在传参的时候,形式可以写成数组,也可以写成指针
🔴指针传参:
🔸传变量的地址,用一级指针来接收
🔸传一级指针变量的地址,用二级指针来接收
四、函数指针 💫
❓不同类型的变量拥有指向其地址的指针,那么函数也应有自己的地址,那么如果我们想把函数的地址保存起来,如何保存?
1、⭐️函数指针的定义⭐️
函数指针是指向函数代码块的指针,它存储了函数的内存地址,允许程序在运行时动态地调用函数;函数指针在C语言中非常有用,特别是在回调函数、函数参数以及根据需要动态选择函数时。
👉函数指针的声明方式如下:
return_type (*pointer_name)(parameter_list)
👉这里的 return_type 是函数返回值类型, parameter_list 是函数的参数列表,pointer_name是指针的名称。
2、⭐️函数指针的使用⭐️
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int add(int a, int b) {
return a + b;
}
// 声明一个函数指针,它可以指向像上面那样接受两个int参数并返回int类型的函数
int (*func_ptr)(int, int);
int main() {
// 将函数add的地址赋值给函数指针
func_ptr = add;
// 使用函数指针调用函数
int result = func_ptr(3, 4);
printf("Result: %d\n", result); // 输出: Result: 7
return 0;
}
五、函数指针数组 💫
1、⭐️函数指针数组概念⭐️
🌈我们知道,数组是一个存放相同类型数据的存储空间,我们已经学习过了存放不同类型指针的指针数组,如果需要把函数的地址也存储到一个数组里,那么就需要使用函数指针数组。
函数指针数组是一组包含函数指针的数组,这些指针指向的是具有相同签名(即返回类型和参数列表相同)的函数。
❓那么函数指针如何定义呢?我们来看一个例子:
int (*parr1[10])();
🍁这就是函数指针数组,是不是看起来很麻烦?没关系,我们来剖析一下,首先我们知道,parr1先和 [ ] 相结合,[ ]优先级高,所以说明 parr1 是数组,那么数组的内容是什么?就是函数指针,是int (*)() 类型的函数指针;不明白的小伙伴可以回头看看本文的“数组指针“章节。
2、⭐️函数指针的使用⭐️
🔸函数指针可以根据条件动态选择不同函数:
#include <stdio.h>
int add(int a, int b) {
return a + b;
}
int subtract(int a, int b) {
return a - b;
}
int main() {
// 声明并初始化一个函数指针数组
int (*operations[2])(int, int) = {add, subtract};
int result1 = operations[0](3, 4); // 使用数组中add函数
int result2 = operations[1](3, 4); // 使用数组中subtract函数
printf("Add Result: %d\n", result1); // 输出: Add Result: 7
printf("Subtract Result: %d\n", result2); // 输出: Subtract Result: -1
return 0;
}
六、指向函数指针数组的指针 💫
🔸指向函数指针数组的指针是一个指针
🔸该指针指向一个数组,数组的元素都是函数指针
👉如下:
void test(const char* str)
{
printf("%s\n", str);
}
int main()
{
//函数指针pfun
void (*pfun)(const char*) = test;
//函数指针的数组pfunArr
void (*pfunArr[5])(const char* str);
pfunArr[0] = test;
//指向函数指针数组pfunArr的指针ppfunArr
void (*(*ppfunArr)[5])(const char*) = &pfunArr;
return 0;
}
👉此代码涉及知识点较多,一环套一环,需要友友们仔细理解前面所讲的函数指针概念与用法,函数指针数组的概念与用法,方可看懂此代码哦。
七、回调函数 💫
回调函数是一种通过函数指针传递的函数,它允许程序在执行过程中调用另一个函数。回调函数在事件处理、异步编程、回调机制中非常常见,它提供了一种方式,让我们可以将函数作为参数传递给其他函数,并在需要时执行这些函数。这种方式使得程序的设计更加模块化和灵活。
🌈使用回调函数,实现qsort函数
#include <stdio.h>
//qosrt函数的使用者得实现一个比较函数
int int_cmp(const void * p1, const void * p2)
{
return (*( int *)p1 - *(int *) p2);
}
int main()
{
int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
int i = 0;
qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof (int), int_cmp);
for (i = 0; i< sizeof(arr) / sizeof(arr[0]); i++)
{
printf( "%d ", arr[i]);//输出0 1 2 3 4 5 6 7 8 9
}
printf("\n");
return 0;
}
👉要想理解该代码需要先学习qsort函数
1、⭐️qsort函数的声明与参数⭐️
🔸void qsort(void *base, size_t nitems, size_t size, int (*compar)(const void *, const void*))
base 指向要排序的数组的第一个元素的指针。
nitems 由 base 指向的数组中元素的个数。
size 数组中每个元素的大小,以字节为单位。
compar 用来比较两个元素的函数,即函数指针(回调函数)
🔸其中compar参数指向一个比较两个元素的函数。比较函数的原型应该像下面这样。注意两个形参必须是const void *型,同时在调用compar 函数(compar实质为函数指针,这里称它所指向的函数也为compar)时,传入的实参也必须转换成const void *型。在compar函数内部会将const void *型转换成实际类型。
🔸int compar(const void *p1, const void *p2);
如果compar返回值小于0(< 0),那么p1所指向元素会被排在p2所指向元素的左面;
如果compar返回值等于0(= 0),那么p1所指向元素与p2所指向元素的顺序不确定;
如果compar返回值大于0(> 0),那么p1所指向元素会被排在p2所指向元素的右面。
2、⭐️qsort函数的功能⭐️
🔸使用排序例程进行排序。
3、⭐️qsort函数的说明⭐️
🔸该函数不返回任何值。
🔸头文件:stdlib.h;
4、⭐️用回调函数模拟实现qsort函数⭐️
🔶再来看看上文的源代码,我们开始分析:
//qosrt函数的使用者得实现一个比较函数
int int_cmp(const void* p1, const void* p2)
{
return (*(int*)p1 - *(int*)p2);
}
int main()
{
int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
int i = 0;
qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof(int), int_cmp);
for (i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
{
printf("%d ", arr[i]);//输出0 1 2 3 4 5 6 7 8 9
}
printf("\n");
return 0;
}
其中:
int int_cmp(const void * p1, const void * p2)
{
return (*( int *)p1 - *(int *) p2);
}
⭕️这个比较函数 int_cmp 接受两个 const void * 类型的参数,这意味着它接受任意类型的指针。在函数内部,首先将这些指针转换为 int* 类型,然后取出它们所指向的值并进行比较。比较的具体方式在上文“compar参数”这里讲过了,这种比较逻辑决定了 qsort 函数按升序排序。
⭕️在 main 函数中,首先定义了一个整数数组 arr 。然后,调用 qsort 函数对这个数组进行排序。qsort函数的参数如下:
- 第一个参数是要排序的数组的指针。
- 第二个参数是数组中元素的数量。
- 第三个参数是每个元素的大小,
- 第四个参数是用于比较元素的函数指针,这里是指向 int_cmp 函数的指针。
⭕️qsort 函数使用这些信息来对数组进行排序,排序后,使用一个循环遍历数组并打印排序后的结果。
🌸至此,C语言指针篇章结束💞💞💞感谢大家花费宝贵的时间阅读本文章,制作不易,希望大家多多支持呀😘😘😘,喜欢心跳文章的友友点个关注,持续更新不迷路~~~ 后续会持续更新C++与Linux的知识,如有任何问题欢迎各位大佬在评论区批评指正!!!