指针的学习(2)

指针的学习(2) 一.数组名数组名就是数组首元素(第一个元素)的地址。int main() { int arr[10] { 0 }; printf(%p\n, arr[0]); printf(%p\n, arr); return 0; }其实数组名就是数组首元素(第一个元素)的地址是对的但是有两个例外sizeof(数组名)sizeof中单独放数组名这里的数组名表示整个数组计算的是整个数组的大小 单位是字节数组名这里的数组名表示整个数组取出的是整个数组的地址整个数组的地址和数组首元素的地址是有区别的除此之外任何地方使用数组名数组名都表示首元素的地址。二.使用指针访问数组int main() { int arr[10] { 0 }; //使用指针给数组中输入1-10的值 int* p arr;//p指向首地址 int i 0; int sz sizeof(arr) / sizeof(arr[0]); for (i 0; i sz; i) { //scanf(%d, arr[i]); //scanf(%d, p i); scanf(%d, arr i); } //使用指针打印数组中的值 for (i 0; i sz; i) { //printf(%d ,* (p i)); //printf(%d , *(arr i)); //printf(%d , arr[i]); //printf(%d , p[i]); //printf(%d , *(i arr); printf(%d , i[arr]); } return 0; }如上代码将*(arri)换成arr[i]也是能够正常打印的所以本质上arr[i]是等价于*(arri)。数组元素的访问在编译器处理的时候也是转换成首元素的地址偏移量求出元素的地址然后解引用来访问的。数组是一块连续的空间指针1.指针就是地址 2.指针变量是变量是存放地址的变量三.一维数组传参的本质数组名是数组首元素的地址那么在数组传参的时候传递的是数组名也就是说本质上数组传参传递的是数组首元素的地址。//void test(int arr[10]) void test(int* arr) { int sz2 sizeof(arr) / sizeof(arr[0]); printf(sz1 %d\n, sz2); } //void test(int arr[]) //{ // int i 0; // for (i 0; i 10; i) // { // printf(%d , arr[i]); // } //} int main() { int arr[10] { 0 }; int sz1 sizeof(arr) / sizeof(arr[0]); printf(sz1 %d\n, sz1); test(arr);//这里的数组名就是数组首元素的地址 //test(arr[0]); return 0; }所以函数形参的部分理论上应该使用指针变量来接收首元素的地址。那么在函数内部我们写 sizeof(arr) 计算的是一个地址的大小单位字节而不是数组的大小单位字节。正是因为函数的参数部分是本质是指针所以在函数内部是没办法求的数组元素个数的。总结一维数组传参形参的部分可以写成数组的形式也可以写成指针的形式。四.冒泡排序冒泡排序的核心思想就是两两相邻的元素进行比较。//冒泡排序 void bubble_sort(int arr[], int sz) { int flag 0;//假设数据已经有序 //1.确定冒泡排序的趟数 int i 0; for (i 0; i sz - 1; i) { //每一趟内部元素排序 int j 0; for (j 0; j sz-1-i; j) { if (arr[j] arr[j 1]) { int temp arr[j]; arr[j] arr[j 1]; arr[j 1] temp; flag 1;//当前还不确定有序 } } if (flag 0) { break; } } } void print_arr(int arr[], int sz) { int i 0; for (i 0; i sz; i) { printf(%d , arr[i]); } } int main() { int arr[] { 6,7,8,4,5,6,4,7,8,0 }; int sz sizeof(arr) / sizeof(arr[0]); //排序为升序 bubble_sort(arr, sz); print_arr(arr, sz); return 0; }五.二级指针int main() { int a 10; int* pa a;//一级指针 int** ppa pa;//二级指针 return 0; } //第二个*说明ppa是指针变量 //int* 说明ppa指向的对象是int*类型的二级指针的运算*ppa 通过对ppa中的地址进行解引用这样找到的是 pa *ppa 其实访问的就是 pa .**ppa 先通过 *ppa 找到 pa ,然后对 pa 进行解引用操作 *pa 那找到的是 a .六.指针数组int main() { int a 10; int b 20; int c 30; int* p1 a; int* p2 b; int* p3 c; int* arr[3] { a,b,c }; return 0; }arr 是一个数组数组有3个元素每个元素的类型是 int* 指针数组的每个元素是地址又可以指向一块区域。指针数组模拟二维数组int main() { int arr1[] { 1,2,3,4,5 }; int arr2[] { 2,3,4,5,6 }; int arr3[] { 3,4,5,6,7 }; int* arr[3] { arr1, arr2, arr3 }; // 0 1 2 int i 0; for (i 0; i 3; i) { int j 0; for (j 0; j 5; j) { printf(%d , arr[i][j]); } printf(\n); } return 0; }七.字符指针变量int main() { const char* p abcdef;//常量字符串把字符串首字符a的地址放进p printf(%c\n,*p);//打印 a printf(%s\n, p);//打印 abcdef return 0; }#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);//1 else printf(str1 and str2 are not same\n);//2 if(str3 str4) printf(str3 and str4 are same\n);//3 else printf(str3 and str4 are not same\n);//4 return 0; }打印2和3str1和str2是数组名指向的是数组首元素的地址这里str3和str4指向的是同一个常量字符串。C/C会把常量字符串存储到单独的一个内存区域 当几个指针指向同一个字符串的时候他们实际会指向同一块内存。但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以str1和str2不同str3和str4相同。八.数组指针变量类比字符指针变量指向字符的指针变量可以存放字符变量的地址 char* p整型指针变量指向整型的指针变量可以存放整型变量的地址 int* p数组指针变量指向数组的指针变量可以存放数组的地址int* p1[10];//指针数组每个元素的类型是指针变量 int *p2[10];//p2是数组指针变量数组十个元素每个元素的类型是int解释 p2先和 * 结合说明 p 是一个指针变量然后指针指向的是一个大小为10个整型的数组。所以 p2 是一个指针指向一个数组叫数组指针。注意 [ ] 的优先级要高于 * 号的所以必须加上 () 来保证p2 先和 * 结合int arr[10] {0}; arr;//得到的就是数组的地址 int(*p)[10] arr如果要存放个数组的地址就得存放在数组指针变量中int (* p) [10] arr; | | | | | | | | p指向数组的元素个数 | p是数组指针变量名 p指向的数组的元素类型九.二维数组传参的本质二维数组可以看做是每个元素是一维数组的数组也就是⼆维 数组的每个元素是一个一维数组。那么二维数组的首元素就是第一行是个一维数组。根据数组名是数组首元素的地址这个规则二维数组的数组名表示的就是第一行的地址是一维数组的地址。如下例子第一行的一维数组的类型就是 int [5] 所以第一行的地址的类型就是数组指针类型 int(*)[5] 。那就意味着二维数组传参本质上也是传递了地址传递的是第一行这个⼀维数组的地址那么形参也是可以写成指针形式的。void print(/*int arr[3][5]*/ int (*arr)[5], int r, int c) { int i 0; for (i 0; i r; i) { //*(arr i) arr[i] int j 0; for (int j 0; j c; j) { printf(%d , /*arr[i][j] */ *(*(arr i) j )); } printf(\n); } } 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); //arr是数组名表示数组首元素的地址 // 二维数组的首元素是第一行 //二维数组传参传递的是地址 传递的是第一行的地址 //那么形参的本质应该是指针变量 return 0; }总结二维数组传参形参的部分可以写成数组也可以写成指针形式十.函数指针变量10.1函数指针变量的创建数组指针变量是指向数组的指针变量函数指针变量是指向函数的指针变量用来存放函数地址的未来通过地址能够调用函数。函数是有地址的函数名就是函数的地址当然也可以通过 函数名的方式获得函数的地址。 如果我们要将函数的地址存放起来就得创建函数指针变量函数指针变量的写法其实和数组指针 类似。如下void test() { printf(hehe\n); } void (*pf1)() test; void (*pf2)() test; int Add(int x, int y) { return xy; } int(*pf3)(int, int) Add; int(*pf3)(int x, int y) Add;//x和y写上或者省略都是可以的函数指针类型解析int (* pf3) (int x, int y) | | ------------ | | | | | pf3指向函数的参数类型和个数的交代xy可写可不写 | 函数指针变量名 pf3指向函数的返回类型 int (*) (int x, int y) //pf3函数指针变量的类型10.2函数指针变量的使用通过函数指针调用指针指向的函数。int Add(int x, int y) { return xy; } int main() { int(*pf3)(int, int) Add; printf(%d\n, (*pf3)(2, 3)); printf(%d\n, pf3(3, 5)); return 0; }两端有趣的代码代码1(*(void(*)())0)();代码解析1. 上述代码其实是一次函数调用调用的是0地址处的一个函数这个函数没有参数没有返回值。2. 代码中的 void (*)() 是函数指针类型 (void (*)())0 类型放在括号中意思是强制类型转化是将0这个整型值强制类型转化成这种函数指针类型也就是说0被当做函数的地址了。3. *(void (*)())0 前面加一个 * 就是调用0地址处的这个函数根据函数指针的类型能知道这个函数没有参数也没有返回值。代码2void ( *signal(int , void(*)(int)) )(int);代码解析1. 上述代码是一次函数的声明。2. 声明的函数名字叫 signal 函数的参数有2个第一个是int类型第二个是函数指针类型: void(*)(int) 。 signal 函数的返回值类型也是函数指针类型 void(*)(int) 。10.3typedef关键字typedef 是用来类型重命名的可以将复杂的类型简单化。 比如你觉得 unsigned int 写起来不方便如果能写成 uint 就方便多了那么我们可以使用typedef unsigned int unit; int main() { unsigned int num1; unit num2; return 0; } typedef int* pint; int main() { int* p1,p2;//p1的类型是int*,p2的类型是int pint p3,p4; return 0; } #define PINT int* typedef int* pint; int main() { PINT p1, p2;//p1的类型是int*,p2的类型是int pint p3, p4; return 0; } typedef int (*parr_t)[6]; //对数组指针类型重命名 int main() { int arr[6] { 0 }; int (*p)[6] arr; //int (*)[6] parr_t p2 arr; return 0; } //对函数指针重命名 size_t test(const char* str); { // } typedef size_t(*pf_t)(const char*); int main() { size_t (*p)(const char*) test; //size_t (*)(const char*) pf_t p2 test; return 0; } typedef void(*pf_t)(int); int main() { void (*signal(int, void(*)(int)))(int); pt_2 signal2(int, pf_t); return 0; }十一.函数指针数组把函数的地址存到一个数组中那这个数组就叫函数指针数组。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 (*p1)(int, int) Add; int (*p2)(int, int) Sub; int (*p3)(int, int) Mul; int (*p4)(int, int) Div;*/ //函数指针数组 int (*pfArr[4])(int, int) {Add, Sub, Mul, Div}; int i 0; for (i 0; i 4; i) { int r pfArr[i](9, 3); printf(%d\n, r);//12 6 27 3 } return 0; }十二.转移表函数指针数组的用途转移表举例计算器的一般实现——可以完成整数的加减乘除运算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; } void menu() { printf(*****************************\n); printf(***** 1.add 2.sub *****\n); printf(***** 3.mul 4.div *****\n); printf(***** 0.exit *****\n); printf(****************************\n); } int main() { int input 0; int x 0; int y 0; int r 0; //函数指针数组 int (*pfArr[5])(int, int) { NULL, Add, Sub, Mul, Div }; // 0 1 2 3 4 do { menu(); printf(请选择); scanf(%d, input); if (input 1 input 4) { printf(请输入两个整数); scanf(%d %d, x, y); r pfArr[input](x, y); printf(%d\n, r); } else if(input 0) { printf(退出计算器\n); } else { printf(选择错误请重新选择。\n); } } while (input); return 0; }