【C语言】路漫漫其修远兮,深入[指针]正当下,C语言指针深入解析,路漫漫其修远兮

马肤

温馨提示:这篇文章已超过439天没有更新,请注意相关的内容是否还可用!

摘要:本文围绕C语言中的指针进行深入探讨。指针作为C语言的核心特性之一,对于编程初学者来说是一大难点。文章指出,要想真正掌握C语言,必须深入理解指针的概念、作用及使用方法。文章强调,在学习的过程中,要勇于面对挑战,不断探索,因为“路漫漫其修远兮”。当前正是深入学习指针的绝佳时机,通过不断实践,逐步掌握指针的应用技巧。

目录

一. 指针初步

1.概念定义

2.基本运算符

• 取地址操作符(&)

• 解引⽤操作符 (*)

3.指针变量的⼤⼩

二. 指针运算

1.指针+- 整数

 

2.指针 - 指针

3.指针的关系运算

三. 野指针

1.野指针成因

    • 指针未初始化

    • 指针越界访问

    • 指针指向的空间释放

2.如何规避野指针

• 指针初始化

• 小心指针越界

• 指针变量不再使⽤时,及时置NULL,指针使⽤之前检查有效性  

• 避免返回局部变量的地址

四. assert断言

1.assert基本概念

2.assert启动关闭

 五. 指针的传值调用与传址调用 

1.传值调用

2.传址调用

六. 数组 + 指针(深入解析)

      1. 数组名的理解

注:对于“[ ]”来说,只是一个操作符,比如arr[i]即*(arr+i)

  •    main函数中将arr作为参数传到函数,传的只是arr的首元素地址。

      2. 使⽤指针访问数组

      3. ⼀维数组传参的本质

      4. 冒泡排序

              • 代码实现

      5. ⼆级指针

      6. 指针数组

      7. 指针数组模拟⼆维数组

      8. const引入

              • const定义

            • const语法

七. 一系列的指针变量 

      1. 字符指针变量

      2. 数组指针变量

             1. 数组指针概念

              2. 数组指针初始化 

      3. ⼆维数组传参的本质

      4. 函数指针变量

            1. 函数指针变量

2. typedef 关键字

      5. 函数指针数组

      6. 转移表

• 举例:计算器的⼀般实现:

• 使⽤函数指针数组的实现: 

八. qsort函数深入解析

1. qsort定义

2. qsort排序 

3. qsort模拟实现 

九. sizeof与strlen

 • sizeof

 • strlen

 • 两者比较 


一. 指针初步

1.概念定义

地址:我们在内存中开辟空间时,为了方便后续访问,每个数据有确切的地址。

指针:指向数据的地址,并将其地址储存在指针变量中。

2.基本运算符

• 取地址操作符(&)

%p是用于打印地址的格式。

【C语言】路漫漫其修远兮,深入[指针]正当下,C语言指针深入解析,路漫漫其修远兮 第1张

• 解引⽤操作符 (*)

*pa解引用a的数据。 【C语言】路漫漫其修远兮,深入[指针]正当下,C语言指针深入解析,路漫漫其修远兮 第2张

3.指针变量的⼤⼩

#include 
//指针变量的⼤⼩取决于地址的⼤⼩
//32位平台下地址是32个bit位(即4个字节)
//64位平台下地址是64个bit位(即8个字节)
int main()
{
 printf("%zd\n", sizeof(char *));
 printf("%zd\n", sizeof(short *));
 printf("%zd\n", sizeof(int *));
 printf("%zd\n", sizeof(double *));
 return 0;
}
• 32位平台下地址是32个bit位,指针变量⼤⼩是4个字节。 • 64位平台下地址是64个bit位,指针变量⼤⼩是8个字节。 • 指针变量的⼤⼩与类型⽆关,只要是指针类型的变量,在相同的平台下,⼤⼩都是相同。

二. 指针运算

1.指针+- 整数

【C语言】路漫漫其修远兮,深入[指针]正当下,C语言指针深入解析,路漫漫其修远兮 第3张

数组在内存中是连续存放的,随着数组下标的增长,地址由高到低变化。

 #include 
 int main()
 {
 int arr[10] = {1,2,3,4,5,6,7,8,9,10};
 int *p = &arr[0];
 int i = 0;
 int sz = sizeof(arr)/sizeof(arr[0]);
 for(i=0; i>  
#define NDEBUG
# include 
int main()
{
	int var= 8;
	assert (var==1);
	system("pause");
	return 0;
}

 

五. 指针的传值调用与传址调用 

例如:写⼀个函数,交换两个整型变量的值,分别采用传值调用与传址调用这两种方法

1.传值调用

#include 
void Swap1(int x, int y)
{
 int tmp = x;
 x = y;
 y = tmp;
}
int main()
{
 int a = 0;
 int b = 0;
 scanf("%d %d", &a, &b);
 printf("交换前:a=%d b=%d\n", a, b);
 Swap1(a, b);
 printf("交换后:a=%d b=%d\n", a, b);
 return 0;
}

 【C语言】路漫漫其修远兮,深入[指针]正当下,C语言指针深入解析,路漫漫其修远兮 第4张

显然,在这里,传值调用并没有将a,b的值进行交换。

2.传址调用

#include 
void Swap2(int*px, int*py)
{
 int tmp = 0;
 tmp = *px;
 *px = *py;
 *py = tmp;
}
int main()
{
 int a = 0;
 int b = 0;
 scanf("%d %d", &a, &b);
 printf("交换前:a=%d b=%d\n", a, b);
 Swap2(&a, &b);
 printf("交换后:a=%d b=%d\n", a, b);
 return 0;
}

【C语言】路漫漫其修远兮,深入[指针]正当下,C语言指针深入解析,路漫漫其修远兮 第5张

       实参传递给形参的时候,形参会单独创建⼀份临时空间来接收实参,对形参的修改不影响实参。顾名思义, 形参是实参的一份临时拷贝。

 如果要发生交换的效果,形参就应该接收main函数中的实参的地址所以……

      因此我们可以在main函数中将a和b的地址(通过指针变量)传递给Swap函数,Swap 函数⾥边通过地址间接的操作main函数中的a和b,并达到交换的效果就好了。

六. 数组 + 指针(深入解析)

      1. 数组名的理解

int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int *p = &arr[0];

             sizeof(数组名):表示整个数组,计算的是整个数组的大小,单位是字节。

             &数组名:也表示整个数组,取出的是整个数组的地址。

#include 
int main()
{
 int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
 printf("&arr[0] = %p\n", &arr[0]);
 printf("arr = %p\n", arr);
 return 0;
}

 【C语言】路漫漫其修远兮,深入[指针]正当下,C语言指针深入解析,路漫漫其修远兮 第6张

 &arr[0]即首元素地址,arr(数组名),在debug x86环境下,两地址相同。

注:对于“[ ]”来说,只是一个操作符,比如arr[i]即*(arr+i)

        因此,可以换成*(i+arr),加法的交换律。

  •    arr[ i ] == *(p+i)== *(arr)+i

  •    main函数中将arr作为参数传到函数,传的只是arr的首元素地址。

      2. 使⽤指针访问数组

#include 
int main()
{
 int arr[10] = {0};
 //输⼊
 int i = 0;
 int sz = sizeof(arr)/sizeof(arr[0]);
 //输⼊
 int* p = arr;
 for(i=0; i{1,2,3,4,5}, {2,3,4,5,6},{3,4,5,6,7}};
 test(arr, 3, 5);
 return 0;
}
blockquote ⼆维数组其实可以看做是每个元素是⼀维数组的数组,也就是⼆维 数组的每个元素是⼀个⼀维数组。那么⼆维数组的⾸元素就是第⼀⾏,是个⼀维数组。 /blockquote p img alt="50a1a81df50246c686d4a4f77f6b89c6.png" src="https://img-blog.csdnimg.cn/direct/50a1a81df50246c686d4a4f77f6b89c6.png" //p blockquote        第⼀⾏的⼀维数组的类型就是 int [5] ,所以第⼀⾏的地址的类型就是数组指针类型 int(*)[5] 。那就意味着⼆维数组传参本质上也是传递了地址,传递的是第⼀⾏这个⼀维数组的地址。tips:⼆维数组传参,形参的部分可以写成数组,也可以写成指针形式。 /blockquote h3 id="%C2%A0%20%C2%A0%20%C2%A0%204.%20%E5%87%BD%E6%95%B0%E6%8C%87%E9%92%88%E5%8F%98%E9%87%8F"      4. 函数指针变量/h3 h4 id="%C2%A0%20%C2%A0%20%C2%A0%20%C2%A0%20%C2%A0%20%C2%A0%201.%20%E5%87%BD%E6%95%B0%E6%8C%87%E9%92%88%E5%8F%98%E9%87%8F"            1. 函数指针变量/h4 p                       • 创建函数指针变量/p pre class="brush:python;toolbar:false"#include stdio.h void test() { printf("hehe\n"); } int main() { printf("test: %p\n", test); printf("&test: %p\n", &test); return 0; } //输出结果如下: test: 005913CA &test: 005913CA/pre blockquote 打印出来了地址,所以函数是有地址的,函数名就是函数的地址,当然也可以通过 &函数名 的⽅式获得函数的地址。 /blockquote p         • 使用函数指针变量 /p blockquote 如果我们要将函数的地址存放起来,就得创建函数指针变量咯,函数指针变量的写法其实和数组指针⾮常类似。如下: /blockquote pre class="brush:python;toolbar:false"void test() { printf("hehe\n"); } void (*pf1)() = &test; void (*pf2)()= test; int Add(int x, int y) { return x+y; } int(*pf3)(int, int) = Add; int(*pf3)(int x, int y) = &Add;//x和y写上或者省略都是可以的/pre p        • 解析函数指针变量 /p pre class="brush:python;toolbar:false"int (*pf3) (int x, int y) | | ------------ | | | | | pf3指向函数的参数类型和个数的交代 | 函数指针变量名 pf3指向函数的返回类型 int (*) (int x, int y) /pre h4 id="2.%20typedef%20%E5%85%B3%E9%94%AE%E5%AD%97"2. typedef 关键字/h4 blockquote        typedef 是⽤来类型重命名的,可以将复杂的类型,简单化。 /blockquote pre class="brush:python;toolbar:false"//⽐如,你觉得 unsigned int 写起来不⽅便,如果能写成 uint 就⽅便多了,那么我们可以使⽤: typedef unsigned int uint; //将unsigned int 重命名为uint //如果是指针类型,能否重命名呢?其实也是可以的,⽐如,将 int* 重命名为 ptr_t ,这样写: typedef int* ptr_t; //但是对于数组指针和函数指针稍微有点区别: //⽐如我们有数组指针类型 int(*)[5] ,需要重命名为 parr_t ,那可以这样写: typedef int(*parr_t)[5]; //新的类型名必须在*的右边 //函数指针类型的重命名也是⼀样的,⽐如,将 void(*)(int) 类型重命名为 pf_t ,就可以这样写: typedef void(*pfun_t)(int);//新的类型名必须在*的右边 //那么要简化代码2,可以这样写: typedef void(*pfun_t)(int); pfun_t signal(int, pfun_t);/pre h3 id="%C2%A0%20%C2%A0%20%C2%A0%205.%20%E5%87%BD%E6%95%B0%E6%8C%87%E9%92%88%E6%95%B0%E7%BB%84"      5. 函数指针数组/h3 blockquote 数组是⼀个存放相同类型数据的存储空间,我们已经学习了指针数组, ⽐如: /blockquote pre class="brush:python;toolbar:false"int *arr[10]; //数组的每个元素是int* /pre blockquote p 把函数的地址存到⼀个数组中,这个数组就叫函数指针数组,那函数指针的数组如何定义呢?/p /blockquote pre class="brush:python;toolbar:false"int (*parr1[3])(); int *parr2[3](); int (*)() parr3[3];/pre blockquote 答案是:parr1 parr1 先和 [] 结合,说明 parr1是数组,数组的内容是什么呢? 是 int (*)() 类型的函数指针 /blockquote h3 id="%C2%A0%20%C2%A0%20%C2%A0%206.%20%E8%BD%AC%E7%A7%BB%E8%A1%A8"      6. 转移表/h3 blockquote p函数指针数组的⽤途:转移表 /p /blockquote h4 id="%E2%80%A2%C2%A0%E4%B8%BE%E4%BE%8B%EF%BC%9A%E8%AE%A1%E7%AE%97%E5%99%A8%E7%9A%84%E2%BC%80%E8%88%AC%E5%AE%9E%E7%8E%B0%EF%BC%9A"• 举例:计算器的⼀般实现:/h4 pre class="brush:python;toolbar:false"#include stdio.h int add(int a, int b) { return a + b; } int sub(int a, int b) { return a - b; } int mul(int a, int b) { return a * b; } int div(int a, int b) { return a / b; } int main() { int x, y; int input = 1; int ret = 0; do { printf("*************************\n"); printf(" 1:add 2:sub \n"); printf(" 3:mul 4:div \n"); printf(" 0:exit \n"); printf("*************************\n"); printf("请选择:"); scanf("%d", &input); switch (input) { case 1: printf("输⼊操作数:"); scanf("%d %d", &x, &y); ret = add(x, y); printf("ret = %d\n", ret); break; case 2: printf("输⼊操作数:"); scanf("%d %d", &x, &y); ret = sub(x, y); printf("ret = %d\n", ret); break; case 3: printf("输⼊操作数:"); scanf("%d %d", &x, &y); ret = mul(x, y); printf("ret = %d\n", ret); break; case 4: printf("输⼊操作数:"); scanf("%d %d", &x, &y); ret = div(x, y); printf("ret = %d\n", ret); break; case 0: printf("退出程序\n"); break; default: printf("选择错误\n"); break; } } while (input); return 0; }/pre h4 id="%E2%80%A2%C2%A0%E4%BD%BF%E2%BD%A4%E5%87%BD%E6%95%B0%E6%8C%87%E9%92%88%E6%95%B0%E7%BB%84%E7%9A%84%E5%AE%9E%E7%8E%B0%EF%BC%9A%C2%A0"• 使⽤函数指针数组的实现: /h4 pre class="brush:python;toolbar:false"#include stdio.h int add(int a, int b) { return a + b; } int sub(int a, int b) { return a - b; } int mul(int a, int b) { return a*b; } int div(int a, int b) { return a / b; } int main() { int x, y; int input = 1; int ret = 0; int(*p[5])(int x, int y) = { 0, add, sub, mul, div }; //转移表 do { printf("*************************\n"); printf(" 1:add 2:sub \n"); printf(" 3:mul 4:div \n"); printf(" 0:exit \n"); printf("*************************\n"); printf( "请选择:" ); scanf("%d", &input); if ((input = 4 && input = 1)) { printf( "输⼊操作数:" ); scanf( "%d %d", &x, &y); ret = (*p[input])(x, y); printf( "ret = %d\n", ret); } else if(input == 0) { printf("退出计算器\n"); } else { printf( "输⼊有误\n" ); } }while (input); 49 return 0; }/pre h2 id="%E5%85%AB.%20qsort%E5%87%BD%E6%95%B0%E6%B7%B1%E5%85%A5%E8%A7%A3%E6%9E%90"八. qsort函数深入解析/h2 h3 id="1.%20qsort%E5%AE%9A%E4%B9%89"1. qsort定义/h3 pimg alt="1d77ccea21214a2e9a3d76e0876c6f08.png" src="https://img-blog.csdnimg.cn/direct/1d77ccea21214a2e9a3d76e0876c6f08.png" //p h3 id="2.%20qsort%E6%8E%92%E5%BA%8F%C2%A0"2. qsort排序 /h3 pre class="brush:python;toolbar:false"#define _CRT_SECURE_NO_WARNINGS 1 #includestdio.h #includestring.h struct Stu { char name[20]; int age; }; //比较两个字符串大小(按名字排序) int cmp_stu_1(const void* e1, const void* e2) { return strcmp(((struct Stu*)e1)-name, ((struct Stu*)e2)-name); //strcmp返回值是小于0,等于0,大于0 。 // 比较的是对应字符Ascall码大小 } //按照年龄大小进行比较(按年龄排序) int cmp_stu_2(const void* p1, const void* p2) { return ((struct Stu*)p1)-age - ((struct Stu*)p2)-age; } void print_arr(struct Stu* s, int sz) { for (int i = 0; i sizeof操作时在编译时,而表达式进行时在程序运行时进行操作

【C语言】路漫漫其修远兮,深入[指针]正当下,C语言指针深入解析,路漫漫其修远兮 第7张

 • strlen

主要关心‘/0‘,参数是地址

仅用于求字符串长度,计算“\0”之前字符的个数,当遇见'\0'时,结束。

strlen 是C语⾔库函数,功能是求字符串⻓度。函数原型如下:  

 size_t strlen ( const char * str );

      统计的是从 strlen 函数的参数 str 中这个地址开始向后, \0 之前字符串中字符的个数。

strlen 函数会⼀直向后 找 \0 字符,直到找到为⽌,所以可能存在越界查找。
#include 
int main()
{
 char arr1[3] = {'a', 'b', 'c'};
 char arr2[] = "abc";
 printf("%d\n", strlen(arr1));
 printf("%d\n", strlen(arr2));
 
 printf("%d\n", sizeof(arr1));
 printf("%d\n", sizeof(arr2));
 return 0;
}

 • 两者比较 

【C语言】路漫漫其修远兮,深入[指针]正当下,C语言指针深入解析,路漫漫其修远兮 第8张

【C语言】路漫漫其修远兮,深入[指针]正当下,C语言指针深入解析,路漫漫其修远兮 第9张


0
收藏0
文章版权声明:除非注明,否则均为VPS857原创文章,转载或复制请以超链接形式并注明出处。

相关阅读

  • 【研发日记】Matlab/Simulink自动生成代码(二)——五种选择结构实现方法,Matlab/Simulink自动生成代码的五种选择结构实现方法(二),Matlab/Simulink自动生成代码的五种选择结构实现方法详解(二)
  • 超级好用的C++实用库之跨平台实用方法,跨平台实用方法的C++实用库超好用指南,C++跨平台实用库使用指南,超好用实用方法集合,C++跨平台实用库超好用指南,方法与技巧集合
  • 【动态规划】斐波那契数列模型(C++),斐波那契数列模型(C++实现与动态规划解析),斐波那契数列模型解析与C++实现(动态规划)
  • 【C++】,string类底层的模拟实现,C++中string类的模拟底层实现探究
  • uniapp 小程序实现微信授权登录(前端和后端),Uniapp小程序实现微信授权登录全流程(前端后端全攻略),Uniapp小程序微信授权登录全流程攻略,前端后端全指南
  • Vue脚手架的安装(保姆级教程),Vue脚手架保姆级安装教程,Vue脚手架保姆级安装指南,Vue脚手架保姆级安装指南,从零开始教你如何安装Vue脚手架
  • 如何在树莓派 Raspberry Pi中本地部署一个web站点并实现无公网IP远程访问,树莓派上本地部署Web站点及无公网IP远程访问指南,树莓派部署Web站点及无公网IP远程访问指南,本地部署与远程访问实践,树莓派部署Web站点及无公网IP远程访问实践指南,树莓派部署Web站点及无公网IP远程访问实践指南,本地部署与远程访问详解,树莓派部署Web站点及无公网IP远程访问实践详解,本地部署与远程访问指南,树莓派部署Web站点及无公网IP远程访问实践详解,本地部署与远程访问指南。
  • vue2技术栈实现AI问答机器人功能(流式与非流式两种接口方法),Vue2技术栈实现AI问答机器人功能,流式与非流式接口方法探究,Vue2技术栈实现AI问答机器人功能,流式与非流式接口方法详解
  • 发表评论

    快捷回复:表情:
    评论列表 (暂无评论,0人围观)

    还没有评论,来说两句吧...

    目录[+]

    取消
    微信二维码
    微信二维码
    支付宝二维码