数组名数组名在大多数情况下会被隐式地转换为指向数组第一个元素的指针在特定情况下数组名可以被视为一个指针具有一些指针的特性数组名和指针的不同点但是数组名与真正的指针是不同的主要有以下几点区别1使用 sizeof 运算符数组名得到的是整个数组的大小指针得到的是本身的大小// 定义数组 int nums[5] {10, 20, 30, 40, 50}; // 定义指针指向nums的首元素 int *ptr nums[0]; // 1. sizeof 计算结果不同 printf(sizeof nums: %zu \n, sizeof nums); // 20 printf(sizeof nums: %zu \n, sizeof ptr); // 82数组名不能自增自减// 2. 数组名不能进行自增、自减运算 // nums ; // 报错 ptr; // 地址指向下一个元素 printf(%p, %d \n, ptr, *ptr);3数组名的指向不能更改// 3. nums不可以重新赋值 ptr可以重新赋值 // nums {100, 200, 300, 400, 500}; //因为刚开始初始化了一个数组这里就不能直接更改数组中的元素 // ptr nums[1];数组与指针的相同点1. 使用 %p 打印数组名可以得到首元素地址2. 使用 * 解应用数组名可以得到首元素的值3. 数组名可以进行指针运算加减整数、同类型指针相减、指针比较大小4. 数组名[下标] 是个语法糖本质是 *(数组名下标)int main() { // 定义数组 int nums[5] {10, 20, 30, 40, 50}; // 定义指针指向nums的首元素 int *ptr nums[0]; // 1. 使用 %p 打印两个标识符的值 // 数组名可以当做首元素指针 printf(ptr%p \n, ptr); printf(nums%p \n, nums); printf(\n); // 2. 解引用两个标识符 printf(*ptr%d \n, *ptr); printf(*nums%d \n, *nums); printf(\n); // 3. nums 可以指针运算 printf(ptr2: %p, *(ptr2): %d \n, ptr 2, *(ptr 2)); printf(nums2: %p, *(nums2): %d \n, nums 2, *(nums 2)); printf(numsptr: %d \n, nums ptr); printf(\n); // 4. ptr使用[下标]访问数组元素 // 数组名[下标] 是个语法糖本质是 *(数组名下标) ptr[4] 100; printf(第1个元素: %d \n, ptr[0]); printf(第2个元素: %d \n, ptr[1]); printf(第3个元素: %d \n, ptr[2]); printf(第4个元素: %d \n, ptr[3]); // 本质 *(ptr3) nums[3]本质 *(nums3) printf(第5个元素: %d \n, ptr[4]); printf(\n); return 0; }核心疑问ptr 是指针存的是地址int 占 4 字节第 4 个元素下标 3按理地址应该ptr 3*4为什么 C 语言写的是*(ptr3)不是*(ptr12)关键点C 语言指针加法不是直接加字节指针 整数整数代表元素个数不是字节数。简单来说就是往后数三位数组作为函数形参数组退化为指针⚠核心知识点把数组传给函数的时候不会把整个数组拷贝一份传进去传递的仅仅是数组首元素的地址。函数形参写成int arr[]只是语法糖编译器在编译阶段会直接把它等价转换成int *arr。 也就是说形参位置的arr本质就是一个普通指针变量里面存放传入过来的首元素地址。带来 2 个非常重要的后果在函数内部sizeof(arr)得不到数组真实总字节大小得到的是指针本身的字节大小64 位系统得到 8所以数组传参必须额外传入数组长度len。实参可以传多种形式数组名、已经保存首地址的指针、nums[0]首元素地址三者效果完全一样。#include stdio.h // int arr[] 只是语法糖编译器等价处理为 int *arr int sum_array(int *arr, int len) { int sum 0; for (int i 0; i len; i) { // arr[i] 语法糖底层等价 *(arr i) sum arr[i]; } return sum; } int main() { int nums[5] {10, 20, 30, 40, 50}; int *ptr nums[0]; // 三种实参写法效果完全相同都是传递首元素地址 printf(和: %d \n, sum_array(nums, 5)); printf(和: %d \n, sum_array(ptr, 5)); printf(和: %d \n, sum_array(nums[0], 5)); return 0; }指针数组定义顾名思义就是存放指针的数组其中的每个元素都是指针#include stdio.h int main() { // 定义三个变量 int num1 100, num2 200, num3 300; // 定义指针数组对三个变量进行取址存放早数组中 int *ptr_arr[3] {num1, num2, num3}; // 遍历指针数组 for (int i 0; i 3; i) { //元素地址指的是数组中元素的地址//元素值指的是存放在数组中变量的地址即为元素值 printf(下标:%d; 元素地址:%p; 元素值:%p; 元素指向的值:%d \n, i, ptr_arr[i], ptr_arr[i], *ptr_arr[i]); } printf(数组ptr_arr所使用的内存空间%zu \n, sizeof ptr_arr); return 0; }应用可以定义一个二维字符数组里面可以存放不同长度的字符串注首字符地址凭什么能代表整个字符串地址本身只记录开头位置它并不知道字符串有多长真正完整的字符串靠两件东西配合首字符地址起点内存末尾隐藏的结束符\0终点标记#include stdio.h int main() { // 将多个城市名称保存到一个数组中 char city1[] Beijing; char city2[] Shanghai; char city3[] Bayinguolemeng; char city4[] Xian; // 定义数组 字符串名就表示首字符地址 首字符地址就可以代表整个字符串首元素地址代表整个数组 char *citys[4] {city1, city2, city3, city4}; // 遍历 for (int i 0; i 4; i) { printf(%s \n, citys[i]); //%s打印整个字符串只需要传首字符地址不需要解引用函数内部自己向后遍历直到\0 } printf(citys的内存空间大小%zu \n, sizeof citys);// 32 return 0; }这样子我们可以通过指针来实现存放不同长度的字符串的数组相当于一个二维数组通过遍历能打每一个字符串为什么sizeof citys算出来是32因为我指针数组中存放的是字符串首字符地址里面的每个元素都是指针64 位系统一个指针占 8 字节数组一共 4 个指针元素4 * 8 32→ 所以sizeof citys结果是 32数组指针数组指针Array Pointer是一个指针它指向一个数组而指针数组是存放指针的数组两者要区分开。注意数组指针指向的是整个数组的地址而不是第一个元素的地址虽然二者值是相同的但在运算中会表现出不同。语法规则数据类型*数组指针名)[长度]#include stdio.h int main() { // 定义数组 int nums[5] {10, 20, 30, 40, 50}; // 显示定义指向首元素的指针 // int *ptr nums[0]; int *ptr nums; // 与上一行等价 // 定义数组指针,指向整个数组 int(*arr_ptr)[5] nums; //%p 打印 printf(nums: %p \n, nums); // 00000015f1bff8f0 printf(arr_ptr: %p \n, arr_ptr); // 00000015f1bff8f0 printf(\n); // 地址1 printf(nums1: %p \n, nums 1); // 00000015f1bff8f4 printf(arr_ptr1: %p \n, arr_ptr 1); // 00000015f1bff904 因为数组占了20个字节1之后要移动到另一个数组指针所以要移动20 // 两个标识符类型不同 // nums 的类型 作为数组时int [5] 或者 作为首元素指针时int * // arr_ptr 的类型是 int (*)[5] // 解引用 nums解应用解出来时首元素的值 arr_ptr解引用解出来是数组 printf(*nums: %d \n, *nums); printf(*arr_ptr: %d \n, (*arr_ptr)[0]); printf(**arr_ptr: %d \n, **arr_ptr);//因为int(*arr_ptr)[5] nums;所以arr_ptr解引用解出来的是nums首元素地址再进行解引用才能拿首元素的值给解出来 return 0; }printf(**arr_ptr: %d \n, **arr_ptr)这里**是什么意思因为int(*arr_ptr)[5] nums所以arr_ptr存放的是nums的地址对其进行解引用得到的是nums数组名--首元素地址因为还是个地址所以要对他再次解引用从而得到数组首元素的值也就是10.字符指针指向字符串字面量常量的字符指针具有如下特点① 可以作为字符串使用可以%s打印可以遍历输出每个字符可以strlen()获取长度② 相对于字符数组名字符指针可以修改指针指向新的字符串③ 不可以使用下标修改其中某个字符④ 不能用于获取用户输入的字符串#include stdio.h #include string.h int main() { // 定义字符数组 char msg[] Shanghaishi; // 定义字符指针指向msg首地址 char *str1 msg; // 定义字符指针 指向字符串字面量常量 (这是与其他指针的区别) char *str2 Beijingshi; // 使用 %s 打印 printf(msg: %s \n, msg); printf(str1: %s \n, str1); printf(str2: %s \n, str2); printf(\n); // 使用 %p 打印 printf(msg: %p \n, msg); printf(str1: %p \n, str1); printf(str2: %p \n, str2); printf(\n); // 使用 strlen() 计算字符串长度 printf(msg: %zu \n, strlen(msg));// 11 printf(str1: %zu \n, strlen(str1));// 11 printf(str2: %zu \n, strlen(str2));// 10 printf(\n); // 使用 sizeof 计算 printf(msg: %zu \n, sizeof(msg));// 12 printf(str1: %zu \n, sizeof(str1));// 8 printf(str2: %zu \n, sizeof(str2));// 8 printf(\n); return 0; }从中可以看到字符指针可以指向字面量常量而其他指针不行。因str1指向的是msg的首元素地址又因为msg是数组名也代表首元素地址因此用%s和%p打印出来两者的内容是相同的。疑问为什么strlen算出来的长度和sizeof算出来的长度是不一样的strlen()库函数统计字符串有效字符个数不包含末尾\0结束符sizeof运算符算整个内存占用字节会把字符串末尾\0也算进去strlen只看字符串内容走到\0停下strlen(msg)11strlen(str1)str1 是 char * 指针指向 msg 首字符顺着内存读到\0结果也是 11strlen(str2)统计字面量Beijingshi有效字符数量不含\0✔strlen接收的是首字符地址不管是数组名还是字符指针只要给到字符串起始地址就向后遍历直到遇见\0。sizeof算变量本身占多少内存sizeof(msg)msg 是真实字符数组算出数组全部字节包含末尾\0→12sizeof(str1)str1 只是普通char*指针变量64 位系统指针占 8 字节 →8sizeof(str2)str2 同样是char*指针也是 8 字节 →8⚠重点sizeof(str2)不会去算字符串字面量 Beijingshi 的长度sizeof 只算指针变量本身的内存大小字符串字面量本体存放在只读常量区str2 仅仅保存它的首地址。重新赋值// 重新赋值 // msg Xianshi;// 报错 // str1 Xianshi; str2 Jiangsusheng; // ----------------------------------------------- // 使用下标修改其中某个字符的值 msg[0] H; str1[0] O; // str2[0] O;// 报错数组不可重新赋值msg Xianshi;会导致编译错误因为数组名是常量指针指针可以重新赋值str1 Xianshi;和str2 Jiangsuzheng;都合法因为指针变量可以改变指向本质区别数组名是地址常量指针是地址变量代码内存位置能否修改字符内容char msg[] xxx;栈内存可读可写✅可以msg[0]Hchar *str1 msg;指针指向栈数组✅str1[0]O修改栈数组char *str2 xxx;指针指向只读常量区❌禁止str2[0]O但是可以改变指针指向str2yyy使用字符数组获取用户输入的字符串char s1[100]; printf(请输入字符串); scanf(%s, s1);这里scanf中为什么不需要加s1声明char s1[100]分配固定大小的连续内存空间输入scanf(%s, s1)不需要取地址符因为数组名本身就是首元素地址所以就不再需要取地址了。