南京网站建设包括哪些,wordpress指定目录为首页,网站怎么做移动端的,找什么公司做网站一、指针概念 1.1 内存和地址 在开始学习指针前#xff0c;我们先来讲一个例子#xff0c;假如你身处一栋楼中#xff0c;你点了一份外卖#xff0c;那么#xff0c;外卖员如何能找到你#xff1f;有两种方法。法一#xff1a;直接一间一间找#xff0c;这样做不仅消耗…一、指针概念 1.1 内存和地址 在开始学习指针前我们先来讲一个例子假如你身处一栋楼中你点了一份外卖那么外卖员如何能找到你有两种方法。法一直接一间一间找这样做不仅消耗时间长而且效率低。法二把房间编号然后告诉外卖员房间号即可。⽣活中每个房间有了房间号就能提⾼效率能快速的找到房间。 生活中如此计算机中亦如此。计算机为了实现对内存的高效管理是把内存划分为⼀个个的内存单元每个内存单元的⼤⼩取1个字节。其中每个内存单元相当于⼀个房间⼀ 个⼈字节空间⾥⾯能放8个⽐特位就好⽐一个房间住八个人每个⼈是⼀个⽐特位。 每个内存单元也都有⼀个编号这个编号就相当 于宿舍房间的⻔牌号有了这个内存单元的编 号CPU就可以快速找到⼀个内存空间。 ⽣活中我们把⻔牌号也叫地址在计算机中我们 把内存单元的编号也称为地址。C语⾔中给地址起 了新的名字叫指针。 所以我们可以理解为 内存单元的编号 地址 指针 1.2 指针变量和地址 理解了以上概念我们再回到C语⾔在C语⾔中创建变量其实就是向内存申请空间。 那我们怎么拿到地址呢那就不得不拿出一个操作符了——取地址操作符它的作用便是拿出地址。 int a 4;printf(%p, a); 这样便可打印出a的地址。 1.3 指针的类型 那么指针都有哪些类型呢 int *ptr; // 声明一个指向整数的指针 char *ptr; // 声明一个指向字符的指针 double *ptr; // 声明一个指向双精度浮点数的指针 总之和我们以前学习变量类型时差不多那我们该如何判断指针类型呢 从语法的角度看你只要把指针声明语句里的指针名字去掉剩下的部分就是这个指针的类型。这是指针本身所具有的类型。让我们看看各个指针的类型 int*ptr;//指针的类型是int* char*ptr;//指针的类型是char* int**ptr;//指针的类型是int** int(*ptr)[3];//指针的类型是int(*)[3] int*(*ptr)[4];//指针的类型是int*(*)[4] 怎么样找出指针的类型的方法是不是很简单 1.4 指针变量和解引⽤操作符* 那我们通过取地址操作符()拿到的地址是⼀个数值⽐如0x006FFD70这个数值有时候也是需要 存储起来⽅便后期再使⽤的那我们把这样的地址值存放在哪⾥呢答案是指针变量中。 int a 4;int* p a; 指针变量也是⼀种变量这种变量就是⽤来存放地址的存放在指针变量中的值都会理解为地址。 我们把地址保存好以后我们该如何使用呢大家想象一下我们如何把锁住门打开答案很简单使用钥匙即可。同理我们如何使用地址就要找出与之匹配的“钥匙”那就是叫解引⽤操作符(*)。 int a 4;int* p a;*p 0; 以上代码使用了*操作符什么意思呢即*pa 的意思就是通过pa中存放的地址找到指向的空间 *pa其实就是a变量了所以*pa 0这个操作符是把a改成了0。 1.5 指针的大小 初步了解了使用后我们肯定要了解一下指针的大小那指针的大小和什么有关呢大家可以先猜测一下我们随后解密。
#includestdio.h
int main()
{printf(%zd\n, sizeof(char*));printf(%zd\n, sizeof(short*));printf(%zd\n, sizeof(int*));printf(%zd\n, sizeof(double*));return 0;
} 答案如下 x64环境下输出结果 x86环境下输出结果 总结 32位平台下地址是32个bit位指针变量⼤⼩是4个字节 64位平台下地址是64个bit位指针变量⼤⼩是8个字节 注意指针变量的⼤⼩和类型是⽆关的只要指针类型的变量在相同的平台下⼤⼩都是相同的。 1.6 指针变量类型的意义 调试以下代码观察内存变化。
#include stdio.h
int main()
{int n 0x11223344;int *pi n; *pi 0; return 0;
}//代码一
#include stdio.h
int main()
{int n 0x11223344;char *pi n; *pi 0; return 0;
}//代码二 调试我们可以看到代码1会将n的4个字节全部改为0但是代码2只是将n的第⼀个字节改为0。 结论 指针的类型决定了对指针解引⽤的时候有多⼤的权限⼀次能操作⼏个字节。
1.7 野指针 概念 野指针就是指针指向的位置是不可知的随机的、不正确的、没有明确限制的 形成原因 1. 指针未初始化
#include stdio.h
int main()
{ int *p;//局部变量指针未初始化默认为随机值*p 20;return 0;
}//此代码在VS上会报错 2.指针越界访问
#includestdio.h
int main()
{int arr[10] { 0 };int* p arr;for (int i 0; i 11; i){//当指针指向的范围超出数组arr的范围时p就是野指针*p arr[i];p;}return 0;
} 3.指针指向的空间释放
#includestdio.h
int* test()
{int n 10;return n;
}
int main()
{int* p test();return 0;
} 那该如何避免指针变成野指针呢 1.指针初始化明确指针指向哪里。如若不知道可以赋值为NULLNULL 是C语⾔中定义的⼀个标识符常量值是00也是地址这个地址是⽆法使⽤的读写该地址 会报错。 2.⼩⼼指针越界不要越界访问。 3.指针变量不再使⽤时及时置NULL指针使⽤之前检查有效性。 4.避免返回局部变量的地址。 1.8 const修饰 变量是可以修改的如果把变量的地址交给⼀个指针变量通过指针变量的也可以修改这个变量。 但是如果我们希望⼀个变量加上⼀些限制不能被修改怎么做呢这就是const的作用。变量中可以这样使用指针亦是如此。
#includestdio.h
void test1()
{int n 10;int m 20;int* p n;*p 20;//ok?p m; //ok?
}
void test2()
{int n 10;int m 20;const int* p n;*p 20;//ok?p m; //ok?
}
void test3()
{int n 10;int m 20;int* const p n;*p 20;//ok?p m; //ok?
}
void test4()
{int n 10;int m 20;const int*const p n;*p 20;//ok?p m; //ok?
}
int main()
{test1();//无const修饰test2();//const在*的左边test3();//const在*的右边test4();//const在*的两边return 0;
}大家可自行在VS上验证观察是否会报错。 这里直接说结论 const如果放在*的左边修饰的是指针指向的内容保证指针指向的内容不能通过指针来改变。 但是指针变量本⾝的内容可变。 const如果放在*的右边修饰的是指针变量本⾝保证了指针变量的内容不能修改但是指针指 向的内容可以通过指针改变。 大家可以这样理解假如你带你女朋友去逛街突然想吃凉皮你要是想守住你的钱袋子就在左边加const这样你的钱袋子就保住了但是你的女朋友可能会想连个凉皮都不给我我要换男朋友然后你就没女朋友了。所幸刚刚是做梦但是不幸的是你女朋友现在就是要吃凉皮为了避免上一次情况的出现把*放入了const右边给她了一碗凉皮这样她就不会换男朋友了。就在你为余额清零心疼不已时你又醒了原来又是一个梦你女朋友又要吃凉皮你重生归来顿时恼怒结合前两世方法在const两边都加*这样你既不用花钱有能保住女朋友这才是重生文男主。仅当帮助记忆无其他不良引导 1.9 二级指针 指针变量也是变量是变量就有地址那指针变量的地址存放在哪⾥ 答案是二级指针。
#includestdio.h
int main()
{int a 10;int* p a;int** pa p;return 0;
} 对于⼆级指针的运算有 *pa 通过对pa中的地址进⾏解引⽤这样找到的是 p *pa 其实访问的就是 p 。 **pa 先通过 *pa 找到 p ,然后对 p 进⾏解引⽤操作 *pa 那找到的是 a 。 我觉得可以把多级指针想象成多级导数或者剥洋葱逐渐抽丝剥茧最后寻到本质。 二、指针运算 指针的基本运算有三种分别是 指针- 整数 指针-指针 指针的关系运算 2.1 指针- 整数 因为数组在内存中是连续存放的只要知道第⼀个元素的地址顺藤摸⽠就能找到后⾯的所有元素。
int main()
{int arr[10] { 0 };int* p arr[0];int sz sizeof(arr) / sizeof(arr[0]);for (int i 0; i sz; i){printf(%d , *(p i));}return 0;
}
2.2 指针-指针
int my_strlen(char* s)
{char* p s;while (*p ! \0)p;return p - s;
}
int main()
{printf(%d\n, my_strlen(abcd));return 0;
}
2.3 指针的关系运算
int main()
{int arr[10] { 1,2,3,4,5,6,7,8,9,10 };int* p arr;int i 0;int sz sizeof(arr) / sizeof(arr[0]);while (p arr sz){printf(%d , *p);p;}return 0;
}
三、指针与数组 3.1 数组名理解 在学习函数时我们要传数组时总是写的是数组名。当你看到这时或许会好奇数组名是什么用以下代码来演示
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;return 0;
} 运行结果如下 我们发现它们的地址一样所以我们得出如下结论 数组名是地址并且是数组首元素的地址。 这里强调两个例外 • sizeof(数组名)sizeof中单独放数组名这⾥的数组名表⽰整个数组计算的是整个数组的⼤⼩ 单位是字节 • 数组名这⾥的数组名表⽰整个数组取出的是整个数组的地址整个数组的地址和数组⾸元素 的地址是有区别的 那么arr和arr有什么区别呢那可以试试如下代码
#include stdio.h
int main()
{int arr[10] { 1,2,3,4,5,6,7,8,9,10 };printf(arr %p\n, arr);printf(arr1 %p\n, arr 1);printf(arr %p\n, arr);printf(arr1 %p\n, arr 1);return 0;这⾥我们发现arr[0]和arr[0]1相差4个字节arr和arr1 相差4个字节是因为arr[0] 和 arr 都是 ⾸元素的地址1就是跳过⼀个元素。 但是arr 和 arr1相差40个字节这就是因为arr是数组的地址1 操作是跳过整个数组的。 3.2使⽤指针访问数组 有了前⾯知识的⽀持再结合数组的特点我们就可以很⽅便的使⽤指针访问数组了。
#include stdio.h
int main()
{int arr[10] { 1,2,3,4,5,6,7,8,9,10 };int* p arr;int sz sizeof(arr) / sizeof(arr[0]);for (int i 0; i sz; i){printf(%d , (*p i));}return 0;
}这个代码搞明⽩后我们再试⼀下如果我们再分析⼀下数组名arr是数组⾸元素的地址可以赋值 给p其实数组名arr和p在这⾥是等价的。那我们可以使⽤arr[i]可以访问数组的元素那p[i]是否也可 以访问数组呢
#include stdio.h
int main()
{int arr[10] { 1,2,3,4,5,6,7,8,9,10 };int* p arr;int sz sizeof(arr) / sizeof(arr[0]);for (int i 0; i sz; i){printf(%d , p[i]);}return 0;
}我们可以得出arr[i]等价于*arri。 3.3 一维数组传参本质 我们在学习函数时也传递过数组。那么我们到底传过去的是什么呢
#include stdio.h
void test(int arr[])
{int sz sizeof(arr) / sizeof(arr[0]);printf(%d, sz);
}
int main()
{int arr[10] { 1,2,3,4,5,6,7,8,9,10 };test(arr);return 0;
} 结果如下 这就要学习数组传参的本质了说本质上数组传参本质上传递的是数组⾸元素的地址。 所以函数形参的部分理论上应该使⽤指针变量来接收⾸元素的地址。那么在函数内部我们写 sizeof(arr) 计算的是⼀个地址的⼤⼩单位字节⽽不是数组的⼤⼩单位字节。正是因为函 数的参数部分是本质是指针所以在函数内部是没办法求的数组元素个数的。 总结⼀维数组传参形参的部分可以写成数组的形式也可以写成指针的形式。 3.4 指针数组 指针数组是指针还是数组 我们类⽐⼀下整型数组是存放整型的数组字符数组是存放字符的数组。 那指针数组呢是存放指针的数组。 指针数组的每个元素都是⽤来存放地址指针的。 指针数组的每个元素是地址⼜可以指向⼀块区域。 3.5 指针数组模拟⼆维数组 我们知道 二维数组在内存里是连续存放的。 pa[i]是访问pa数组的元素pa[i]找到的数组元素指向了整型⼀维数组pa[i][j]就是整型⼀维数 组中的元素。
#include stdio.h
int main()
{int arr1[] { 1,2,3,4,5 };int arr2[] { 6,7,8,9,10 };int arr3[] { 11,12,13,14,15 };//数组名是数组⾸元素的地址类型是int*的就可以存放在pa数组中int* pa[3] { arr1, arr2, arr3 };int i 0;int j 0;for (i 0; i 3; i){for (j 0; j 5; j){printf(%d , pa[i][j]);}printf(\n);}return 0;
} 3.6 数组指针变量 前面我们已经学习过了指针数组指针数组是把指针放到一个数组中那么数组指针变量是指针变量还是数组 答案是指针变量。 我们已经熟悉 • 整形指针变量 int * pint; 存放的是整形变量的地址能够指向整形数据的指针。 • 浮点型指针变量 float * pf; 存放浮点型变量的地址能够指向浮点型数据的指针。 那数组指针变量应该是存放的应该是数组的地址能够指向数组的指针变量。
int* p1[10];
int (*p2)[10]; 大家可以先猜测一下p1和p2分别是什么 数组指针变量
int *p)[10]; 解释p先和*结合说明p是⼀个指针变量变量然后指着指向的是⼀个⼤⼩为10个整型的数组。所以 p是⼀个指针指向⼀个数组叫 数组指针。 这⾥要注意[]的优先级要⾼于*号的所以必须加上来保证p先和*结合。 那问题来了那该这么初始化?数组指针本质是什么答案是指针。指针是用来干什么的答案是存放地址那数组指针是不是可以理解为存放数组的地址。所以如果要存放个数组的地址就得存放在数组指针变量中。
int arr[10] {0};
int (*p)[10] arr; 通过调试我们发现arr与p的类型一致证明我们的结论是正确的。 数组指针类型解析
int(*p)[10] arr;
| | |
| | |
| | p指向数组的元素个数
| p是数组指针变量名
p指向的数组的元素类型 3.7 二维数组传参本质 学习到这里我们就可以解释二维数组传参本质。当时我们代码如下
#include stdio.h
int main()
{int arr1[] { 1,2,3,4,5 };int arr2[] { 6,7,8,9,10 };int arr3[] { 11,12,13,14,15 };//数组名是数组⾸元素的地址类型是int*的就可以存放在pa数组中int* pa[3] { arr1, arr2, arr3 };int i 0;int j 0;for (i 0; i 3; i){for (j 0; j 5; j){printf(%d , pa[i][j]);}printf(\n);}return 0;
} 这⾥实参是⼆维数组形参也写成⼆维数组的形式那还有什么其他的写法吗 ⾸先我们再次理解⼀下⼆维数组⼆维数组起始可以看做是每个元素是⼀维数组的数组也就是⼆维 数组的每个元素是⼀个⼀维数组。那么⼆维数组的⾸元素就是第⼀⾏是个⼀维数组。 所以根据数组名是数组⾸元素的地址这个规则⼆维数组的数组名表⽰的就是第⼀⾏的地址是⼀ 维数组的地址。根据上⾯的例⼦第⼀⾏的⼀维数组的类型就是 int [5] 所以第⼀⾏的地址的类 型就是数组指针类型 int(*)[5] 。那就意味着⼆维数组传参本质上也是传递了地址传递的是第⼀ ⾏这个⼀维数组的地址那么形参也是可以写成指针形式的。如下
#includestdio.h
void test(int *p[5],int a,int b)
{for (int i 0; i a; i){for (int j 0; j b; j){printf(%d, *((*pi)j));}}
}
int main()
{int arr[3][5] { {1,2,3,4,5}, {2,3,4,5,6},{3,4,5,6,7} };test(arr, 3, 5);return 0;
} 总结⼆维数组传参形参的部分可以写成数组也可以写成指针形式。
四、指针与函数 4.1 函数指针变量的创建 什么是函数指针呢通过前面的学习我们不难知道。函数指针变量应该是⽤来存放函数地址的未来通过地址能够调⽤函数的。 那我们不禁要问函数真的有地址吗我们来代码验证一下
#includestdio.h
void test()
{;
}
int main()
{test();printf(%p\n, test);printf(%p\n, test);return 0;
} 确实打印出来了地址所以函数是有地址的函数名就是函数的地址当然也可以通过 函数名 的⽅ 式获得函数的地址。 如果我们要将函数的地址存放起来就得创建函数指针变量咯函数指针变量的写法其实和数组指针 ⾮常类似。
#includestdio.h
void test()
{;
}
int add(int x, int y)
{return x y;
}
int main()
{int x 3;int y 4;test();int ret add(x, y);void(*p)() test;int(*p1)(int, int) add;//里面有无xy都一样return 0;
} 函数指针类型解析
int (*pf3) (int x, int y)
| | ------------
| | |
| | pf3指向函数的参数类型和个数的交代
| 函数指针变量名
pf3指向函数的返回类型
int (*) (int x, int y) //pf3函数指针变量的类型 4.2 函数指针变量的使⽤ 通过函数指针调⽤指针指向的函数。
#includestdio.h
int add(int x, int y)
{return x y;
}
int main()
{int x 2;int y 3;int ret add(x, y);int(*p1)(int, int) add;printf(%d\n, p1(2,3));printf(%d\n, (*p1)(2, 3));printf(%d\n,ret);return 0;
}4.3 typedef关键字 typedef 是⽤来类型重命名的可以将复杂的类型简单化。 像unsigned int 你觉得写得麻烦你就可以把它定义成unit。
typedef unsigned int unit 如果是指针类型能否重命名呢其实也是可以的。
typedef int* pt_t; 但是对于数组指针和函数指针稍微有点区别 ⽐如我们有数组指针类型 int(*)[5] ,需要重命名为 parr_t 那可以这样写
typedef int(*parr_t)[5];//parr_t必须放入*右边
函数指针类型的重命名也是⼀样的⽐如将 void(*)(int) 类型重命名为 pf_t ,就可以这样写
typedef void(*pf_t)int;//parr_t必须放入*右边 4.4 函数指针数组 数组是⼀个存放相同类型数据的存储空间那要把函数的地址存到⼀个数组中那这个数组就叫函数指针数组那函数指针的数组如何定义呢如下
int (*p[10])(); p 先和 [] 结合说明 parr1是数组数组的内容是什么呢 是 int (*)() 类型的函数指针。 五、转移表 函数指针数组的⽤途转移表。 举例计算器的⼀般实现 在没学习函数指针数组之前
#includestdio.h
void menu()
{printf(*************************\n);printf( *****1:add 2:sub *******\n);printf( *****3:mul 4:div *******\n);printf( *******0:exit **********\n);printf(*************************\n);printf(请选择);
}
int add(int x, int y)
{return x y;
}
int sub(int x, int y)
{return x - y;
}
int mul(int x, int y)
{return x * y;
}
int div(int x, int y)
{return x / y;
}
int main()
{int x 0;int y 0;int input 0;int ret 0;do{menu();scanf(%d, input);switch (input){case 1:printf(请输入要计算的两个数:);scanf(%d%d, x, y);ret add(x,y);printf(ret %d, ret);break;case 2:printf(请输入要计算的两个数:);scanf(%d%d, x, y);ret sub(x, y);printf(ret %d, ret);break;case 3:printf(请输入要计算的两个数:);scanf(%d%d, x, y);ret mul(x, y);printf(ret %d, ret);break;case 4:printf(请输入要计算的两个数:);scanf(%d%d, x, y);ret div(x, y);printf(ret %d, ret);break;default:printf(选择错误重新选择);break;}} while (input);return 0;
}我们可以看出不仅非常繁琐还涉及许多不必要的重复这是我们就可以来优化一下
#includestdio.h
void menu()
{printf(*************************\n);printf( *****1:add 2:sub *******\n);printf( *****3:mul 4:div *******\n);printf( *******0:exit **********\n);printf(*************************\n);printf(请选择);
}
int add(int x, int y)
{return x y;
}
int sub(int x, int y)
{return x - y;
}
int mul(int x, int y)
{return x * y;
}
int div(int x, int y)
{return x / y;
}
int main()
{int x 0;int y 0;int input 0;int ret 0;int (*p[5])(x, y) { 0,add,sub,mul,div };do{menu();scanf(%d, input);scanf(%d%d, x, y);if (input 0 input 5){printf(%d, (p[input])(x, y));}} while (input);return 0;
}我们可以看到更加的简洁这就是函数指针数组的好处。