[TOC]
2. 指针进阶
2.1 字符指针
2.1.1 介绍
char c = 'h';
char* pc = &c;
// 访问权限为1bbyte
// ++/--为1byte
2.1.2 例
int main()
{
char str1[] = "Hello World!";
char str2[] = "Hello World!";
char* str3 = "Hello World!";
char* str4 = "Hello World!";
// 将常量字符串的首字母H存放到指针变量str中
// 常量是不可修改的
// 在内存中只需要存在一份
// 所以str3和str4指向的是同一块内存区域
// 则str3和str4的指针值相等
if (str1 == str2)
{
printf("same\n");
}else {
printf("not same\n");
}
if (str3== str4)
{
printf("same\n");
}
else {
printf("not same\n");
}
return 0;
}

2.2 数组指针
指向数组的指针。
2.2.1 格式
int arr[10] = { 0 };
int (*p)[10] = &arr;
// p 变量名
// *表示p是一个指针
// [10]表示该指针指向一个数组
// int 表示该指针指向的数组每个元素的类型为int
// 注意 int (*p)[10] 与 int *p[10] 区分
// 如果没有小括号 p先于[]结合 成数组
// 若有小括号p先于*结合 成指针
2.2.2 数组名
// 数组名是数组首元素的地址
// 但是有2个例外
// 1. sizeof(arr) 数组名表示整个数组,计算的是整个数组大小,单位是字节
// 2. &arr 数组名表示整个数组,取出的是整个数组的地址
2.2.3 arr和&arr
int main()
{
int arr[10] = { 0 };
printf("arr: %p\n", arr);
printf("arr+1: %p\n", arr + 1);
printf("&arr: %p\n", &arr);
printf("&arr+1:%p\n", &arr + 1);
return 0;
}
// arr是数组首元素的地址
// &arr是数组指针
// 指针值相同,但含义完全不同

2.2.4 应用
// 数组指针一般用于二维数组中,在一维数组中体现不出优势
// 普通方法
void print1(int arr[3][4], int r, int c)
{
int i = 0;
int j = 0;
for ( i = 0; i < r; i++)
{
for (j = 0; j < c; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
}
// 数组指针
void print2(int(*p)[4], int r, int c)
{
int i = 0;
int j = 0;
for (i = 0; i < r; i++)
{
for (j = 0; j < c; j++)
{
printf("%d ", *(*(p+i) + j));
}
printf("\n");
}
}
int main()
{
int arr[3][4] = {
{ 1, 2, 3, 4 },
{ 11, 22, 33, 44 },
{ 111, 222, 333, 444}
};
print1(arr, 3, 4);
print2(arr, 3, 4);
// arr数组名,传递的是数组首元素的地址
// 对于二维数组,则首元素就是第一行的一维数组
// 则arr为一维数组指针
// print2函数的需要使用数组指针接收
return 0;
}
2.2.5 类型判断
int (*parr1)[10];
// parr1是数组指针变量
// 该指针指向元素个数为10、每个元素为int类型的数组
int (*parr2[10])[5];
// parr2先和[10]结合 所以parr2是一个数组
// 数组的类型为 int (*)[5]; 是数组指针
// 则parr2是一个存储10个数组指针的数组
// 且每个数组指针指向元素类型为int、元素个数为5的数组
2.3 指针数组
2.3.1 介绍
int* arr1[10]; // 整形指针的数组
char *arr2[4]; // 一级字符指针的数组
char **arr3[5]; // 二级字符指针的数组
2.3.2 例
int main()
{
int a[] = {1, 2, 3, 4, 5};
int b[] = {11, 22 ,33, 44, 55};
int c[] = {111, 222 ,333, 444, 555};
int* arr[] = { a, b, c };
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 5; j++) {
printf("%d ", arr[i][j]);
printf("%d ", *(arr[i] + j));
}
printf("\n");
}
return 0;
}
// 模拟二维指针

2.4 数组传参和指针传参
2.4.1 一维数组传参
void test1(int arr[]) {} // 数组(省略大小)
// √
void test1(int arr[10]) {} // 数组(未省略大小)
// √
void test1(int *arr) {} // int指针
// √
void test2(int *arr[]) {} // 指针数组(省略大小)
// √
void test2(int **arr) {} // 二级指针
// √
int main()
{
int arr1[5] = { 0 }; // 一维整形数组
int* arr2[35] = { 0 }; // 一维整形指针数组
test1(arr1);
test2(arr2);
}
2.4.2 二维数组传参
void test(int arr[3][4]) {} // 二维数组(未省略)
// √
void test(int arr[][]) {} // 二维数组(行列都省略)
// ×
void test(int arr[][4]) {} // 二维数组(行省略)
// √
// 总结:二维数组传参,函数形参的设计只能省略第一个[]的数组
// 因为对一个二维数组,可以不知道有多少行,但必须一行有多少个元素
// 方便运算
void test(int *arr) {} // int指针(传递过来的是第一行数组的指针)
// ×
void test(int *arr[4]) {} // 指针数组
// ×
void test(int (*arr)[4]) {} // 数组指针
// √
void test(int **arr) {} // 二级指针
// ×
int main()
{
int arr[3][4] = { 0 };
test(arr);
return 0;
}
2.4.3 一级指针传参
// eg:
void print(int* ptr, int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", *(ptr + i));
}
}
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]);
print(p, sz);
return 0;
}
// 当函数的形参为指针时
// 实参可以是对应类型变量的地址 &var
// 也可以是一级指针变量 ptr
2.4.4 二级指针传参
void print(int** ptr)
{
// ...
}
int main()
{
// 指针数组首元素
int* arr[10] = { 0 };
int** p = arr;
print(p);
// 二级指针变量
int a = 7;
int* pa = &a;
int** ppa = &pa;
print(ppa);
print(&pa);
return 0;
}
// 当函数的形参是二级指针时
// 实参可以是指针数组首元素
// 也可以是二级指针变量
2.5 函数指针
2.5.1 介绍
指向函数的指针。存放函数地址的指针。
int Add(int x, int y)
{
return x + y;
}
int main()
{
printf("%p\n", &Add);
printf("%p\n", Add);
// 两种打印方式打印结果相同
// 都是函数的地址
// Add和&Add完全等价
// 函数名本质上就是地址
return 0;
}

2.5.2 格式
返回值类型 (*函数指针变量)(参数类型);
// eg:
int (*pf)(int, int) = &Add;
int (*pf)(int, int) = Add;
// * 表示pf是一个指针变量
// () 表示该指针指向一个函数
// (int, int) 表示指针指向的函数的参数类型
// int (*pf) 前面的int表示指针指向的函数的返回值类型
2.5.3 调用
int Add(int x, int y)
{
return x + y;
}
int main()
{
int (*pf)(int, int) = Add; // Add === pf
int ret1 = (*pf)(1, 2);
// 其中pf前的*只是摆设 可以省略 也可以有多个
int ret2 = pf(2, 3);
int ret3 = Add(3, 4);
return 0;
}
2.5.4 应用
-
代码一
(*(void (*)())0)(); // 调用0地址处的函数 // 该函数无参,返回值类型为void // 1. void(*)() - 函数指针类型 // 2. (void(*)())0 - 将0强制类型转换为函数指针类型 即函数地址 // 3. *(void (*)())0 - 将该函数地址进行指针解引用 // 4. (*(void (*)())0)() - 调用0地址处的函数
-
代码二
void (* signal(int, void(*)(int)))(int); // 1. signal和()先结合 说明signal是一个函数名 // 2. signal函数的参数是一个int和函数指针 // 该函数指针类型 void(*)(int) 指向参数为int 返回值类型为void的函数 // 3. signal函数的返回类型为函数指针 // 将void (* signal(int, void(*)(int)))(int); 中的 signal(int, void(*)(int)) 剔除 // 结果为void (*)(int) 即为函数的返回类型 // 该函数指针类型 void(*)(int) 指向参数为int 返回值类型为void的函数 // 总结:signal是一个 参数为int和函数指针 返回类型为函数指针 的函数声明 // 简化如下(含义相同 表达更清晰) typedef void(*func_ptr)(int); func_ptr signal(int, func_ptr);

2.6 函数指针数组
2.6.1 介绍
存放函数指针的数组。
// 定义:
// 函数指针
int (*fp)(int, int);
// fp为指针变量,*表示该变量为指针,其余 int (int, int) 为函数指针指向的函数类型
// 函数指针数组
int (*arr[5])(int, int);
// arr3为数组名,其余 int (* [5])(int, int) 为数组类型,int (* )(int, int) 为数组元素类型
2.6.2 应用
计算器实现
-
基本实现
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> void menu() { // 打印菜单 printf("*************************\n"); printf("**** 两位数加减乘除 *****\n"); printf("*************************\n"); printf("**** 1.add 2.sub *****\n"); printf("**** 3.mul 4.div *****\n"); printf("**** 0.exit *****\n"); printf("*************************\n"); } 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 input = 0; do { menu(); // 打印菜单 printf("请输入:> "); // 提示 scanf("%d", &input); // 获取用户输入 int x = 0; int y = 0; int ret = 0; switch (input) // 确定计算规则 { case 1: scanf("%d %d", &x, &y); // 获取用户需要计算的数据 ret = Add(x, y); // 相加 printf("结果是:%d\n", ret); break; case 2: scanf("%d %d", &x, &y); // 获取用户需要计算的数据 ret = Sub(x, y); // 相减 printf("结果是:%d\n", ret); break; case 3: scanf("%d %d", &x, &y); // 获取用户需要计算的数据 ret = Mul(x, y); // 相乘 printf("结果是:%d\n", ret); break; case 4: scanf("%d %d", &x, &y); // 获取用户需要计算的数据 ret = Div(x, y); // 相除 printf("结果是:%d\n", ret); break; case 0: break; default: printf("输入错误,请重新输入!\n"); break; } } while (input); }
-
函数指针数组实现
在上面代码的switch语句中发现有大量的代码冗余,接下来通过函数指针数组对main函数进行重写。
int main() { int input = 0; do { menu(); // 打印菜单 printf("请输入:> "); // 提示 scanf("%d", &input); // 获取用户输入 // 创建函数指针数组 int (*pfarr[5])(int, int) = { NULL, Add, Sub, Mul, Div }; // 数组首元素使用NULL补位,使得input值为数组下标 恰好对应相应的函数 if (input == 1 || input == 2 || input == 3 || input == 4) { int x = 0; int y = 0; int ret = 0; scanf("%d %d", &x, &y); ret = pfarr[input](x, y); printf("结果是:%d\n", ret); } else if (input == 0) { break; } else { printf("输入错误,重新输入!\n"); } } while (input); }
转移表 -- 函数指针数组。
2.7 指向函数指针数组的指针(了解)
指向函数指针数组的指针 指针指向一个数组 数组的每个元素是函数指针
2.7.1 指向整形数组的指针
// 1.整形数组
int arr1[5];
// arr1为数组名,其余 int [5] 为数组类型,int为数组元素类型
// 2.整形数组的指针
int(*parr1)[5] = &arr1;
// parr1为指针变量,*表示该变量为指针,其余 int [5] 为指针指向的对象的类型
2.7.2 指向整形指针数组的指针
// 1.整形指针数组
int* arr2[5];
// arr2为数组名,其余int* [5] 为数组类型,int*为数组元素的类型
// 2.整形指针数组的指针
int* (*parr2)[5] = &arr2;
// parr2为指针变量,*表示该变量为指针,其余 int* [5] 为指针指向的对象的类型
2.7.3 指向函数指针数组的指针
// 1.函数指针
int (*fp)(int, int);
// fp为指针变量,*表示该变量为指针,其余 int (int, int) 为函数指针指向的函数类型
// 2.函数指针数组
int (*arr3[5])(int, int);
// arr3为数组名,其余 int (* [5])(int, int) 为数组类型,int (* )(int, int) 为数组元素类型
// 3.函数指针数组的指针
int (*(*parr3)[5])(int, int) = &arr3;
// parr3为指针变量,*表示该变量为指针,其余 int (* [5])(int, int) 为指针指向对象的类型
2.8 回调函数
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,这就是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
2.8.1 再次重写计算器
根据回调函数再次重写计算器实例的main函数
int Cal(int (*fp)(int, int)) // 形参为函数指针类型int (* )(int, int)
{
int x = 0;
int y = 0;
scanf("%d %d", &x, &y);
return fp(x, y);
}
int main()
{
int input = 0;
do
{
menu(); // 打印菜单
printf("请输入:> "); // 提示
scanf("%d", &input); // 获取用户输入
int (*pfarr[5])(int, int) = { NULL, Add, Sub, Mul, Div }; // 数组指针
if (input == 1 || input == 2 || input == 3 || input == 4)
{
int ret = Cal(pfarr[input]); // 将函数指针作为参数进行传递
printf("结果是:%d\n", ret);
}
else if (input == 0)
{
break;
}
else
{
printf("输入错误,重新输入!\n");
}
} while (input);
}
2.8.2 模拟实现qsort库函数
qsort底层是快速排序。此处只需关注如何使用,即qsort函数的参数和返回值。所以使用冒泡排序实现相同使用方法的该函数。
-
介绍
-
头文件<stdlib.h>
-
语法
void qsort( void *base, size_t number, size_t width, int (__cdecl *compare )(const void *, const void *) );
-
参数
- base:数组的起始地址
- number:数组中元素的个数
- width:数组中元素的大小(单位:byte)
- compare:一个指向用户提供的函数的指针,该函数比较数组两个元素并返回一个值,改值表示他们之间的关系
-
compare
compare( (void *) & elem1, (void *) & elem2 );
比较返回值 描述 <0 elem1<elem2 =0 elem1=elem2 >0 elem1>elem2
-
> 数组按递增排序,如果要对数组进行递减排序,反转 比较返回值 中的`>`和`<`
-
使用实例
-
比较整形
#include <stdio.h> #include <stdlib.h> int compare_int(const void* e1, const void* e2) { return *(int*)e1 - *(int*)e2; } int main() { int arr[] = {5,3,1,2,4,6}; int num = 0; num = sizeof(arr) / sizeof(arr[0]); qsort(arr, num, 4, compare_int); for (int i = 0; i < 6; i++) { printf("%d ", arr[i]); } return 0; } // void*类型可以存放任意类型地址 // 但是不能解引用和计算(解引用时,并不清楚访问几个字节) // 使用时需要强制类型转换为需求的类型指针
-
比较字符串
// 字符串比较大小时,按位比较字符的ASCII码 // 如果字符串长度不相等,如"ab"和"abc"比较 // 'a'和'a'比较 相等 右移 // 'b'和'b'比较 相等 右移 // '\0'和'c'比较 '\0'<'c' "ab"<"abc" #define _CRT_NONSTDC_NO_DEPRECATE #include <stdlib.h> #include <string.h> #include <stdio.h> int compare_string(const void* e1, const void* e2) { return stricmp(*(char**)e1, *(char**)e2); } int main() { char* arr[] = { "afd","fasf","fadew","fdfa","rqewr","fdasf" }; int num = 0; num = sizeof(arr) / sizeof(arr[0]); qsort(arr, num, 4, compare_string); for (int i = 0; i < 6; i++) { printf("%s ", arr[i]); } return 0; }
-
比较结构体
#include <stdlib.h> #include <string.h> #include <stdio.h> struct Student { char name[20]; int age; }; int compare_by_name(const void* e1, const void* e2) { return strcmp(((struct Student*)e1)->name, ((struct Student*)e2)->name); } int compare_by_age(const void* e1, const void* e2) { return (*((struct Student*)e1)).age - (*((struct Student*)e2)).age; } print_struct_arr(struct Student arr[], int num) { for (int i = 0; i < 4; i++) { printf("name: %s, age: %d\n", (arr[i]).name, (arr[i]).age); } } int main() { struct Student arr[4] = { {"listen", 20},{"turbo", 34},{"dummy", 24},{"free", 19}}; int num = 0; num = sizeof(arr) / sizeof(arr[0]); qsort(arr, num, sizeof(arr[0]), compare_by_name); printf("按照姓名排序如下:\n"); print_struct_arr(arr, num); // 打印结构体数组 qsort(arr, num, sizeof(arr[0]), compare_by_age); printf("按照年龄排序如下:\n"); print_struct_arr(arr, num); // 打印结构体数组 return 0; }
-
-
冒泡排序实现(升序为例)
-
原理图
描述:当前元素和下一个元素进行比较。如果小于下一个元素,则保持不变;如果大于下一个元素,则交换位置。然后下标+1,再比较,再下标+1,直到比较完倒数第二个元素和最后一个元素,这个过程称为一趟。接着再从头开始进行比较,如下图所示:

容易得出:5个数进行排序需要4趟。
-
代码
void bubble_sort(int* p, int num) { for (int i = 0; i < num-1; i++) { for (int j = 0; j < num-1; j++) { if (p[j] > p[j+1]) { int tmp = p[j]; p[j] = p[j + 1]; p[j + 1] = tmp; } } } } int main() { int arr[] = {5,3,1,2,4,6}; int num = sizeof(arr) / sizeof(arr[0]); bubble_sort(arr, num); for (int i = 0; i < num; i++) { printf("%d ", arr[i]); } return 0; }
-
优化
第二趟6和8比较 与 前面的6和8比较 重复
第三趟4和6比较、6和8比较 与 前面的4和6比较、6和8比较 重复 第四趟2和4比较、4和6比较、6和8比较 与 前面的2和4比较、4和6比较、6和8比较 重复 所以:在第二趟中一次重复的比较;在第三趟中二次重复的比较;在第四趟中三次重复的比较。优化的原理就是让每两个数比较只出现一次。 如图中,圈中的是重复进行比较的两元素。  
void bubble_sort(int* p, int num) { for (int i = 0; i < num-1; i++) { for (int j = 0; j < num-i-1; j++) { if (p[j] > p[j+1]) { int tmp = p[j]; p[j] = p[j + 1]; p[j + 1] = tmp; } } } } int main() { int arr[] = {5,3,1,2,4,6}; int num = sizeof(arr) / sizeof(arr[0]); bubble_sort(arr, num); for (int i = 0; i < num; i++) { printf("%d ", arr[i]); } return 0; }
-
增强
前面的冒泡排序算法只能比较两个整形,现在将冒泡排序功能进行扩展,使其可以比较任何数据类型(参考sqort库函数)。
void bubble_sort(void* p, int num, int width, int (*compare)(const void*, const void*)) { for (int i = 0; i < num - 1; i++) { for (int j = 0; j < num - i - 1; j++) { // 比较大小,前者>后者返回整数 if (compare((char*)p + j * width, (char*)p + (j+1) * width) > 0) { // 交换值 // 确定大小,但不知道类型 // 按字节交换 char类型刚好代表一个字节,总共交换width次 for (int k = 0; k < width; k++) { char tmp = *((char*)p + j * width + k); *((char*)p + j * width + k) = *((char*)p + (j + 1) * width + k); *((char*)p + (j + 1) * width + k) = tmp; } } } } }
-
测试
// bubble_sort测试 int compare_int(const void* e1, const void* e2) { return *(int*)e1 - *(int*)e2; } int main() { int arr[] = {5,3,1,2,4,6}; int num = sizeof(arr) / sizeof(arr[0]); bubble_sort(arr, num, sizeof(arr[0]), compare_int); for (int i = 0; i < num; i++) { printf("%d ", arr[i]); } return 0; }
-
2.9 习题
2.9.1 指针
-
int a[] = { 1,2,3,4 };
int main() { int a[] = { 1,2,3,4 }; printf("%d\n", sizeof(a)); // 16 // 解释:a 单独放在 sizeof() 中时,a 表示整个数组, // 计算的是整个数组的大小,整个数组 4 个元素,且数组元素为 int 类型 // 所以数组大小为 4 × 4 = 16byte printf("%d\n", sizeof(a + 0)); // 4/8 // 解释:a 表示数组首元素的地址,+0 之后依然是数组是首元素的地址, // 作为地址在32位计算机中为 4byte,64位系统中为 8byte printf("%d\n", sizeof(*a)); // 4 // 解释:a 表示数组首元素的地址,*a 对 a 解引用,得到的是数组首元素 1, // 数组元素的类型为 int,所以 *a 的大小为 4byte printf("%d\n", sizeof(a+1)); // 4/8 // 解释:a 表示数组首元素的地址,a+1 表示数组第二个元素的地址, // 作为地址,其大小为 4byte(32位系统) 或 8byte(64位系统) printf("%d\n", sizeof(a[1])); // 4 // 解释:a[1] 表示数组首元素,数组元素的类型为 int,其大小为 4byte printf("%d\n", sizeof(&a)); // 4/8 // 解释:&a 中 a 表示的是整个数组,获取的是整个数组的指针 // 作为地址,其大小为 4/8byte printf("%d\n", sizeof(*&a)); // 16 // 解释:&a 获取的是整个数组的地址,再解引用得到的是整个数组 // 整个数组的大小为 4 × 4 = 16byte printf("%d\n", sizeof(&a + 1)); // 4/8 // 解释:&a 获取的是整个数组的指针,&a+1 跳过一个 int [4] 类型的数组 // &a+1 指向数组 arr 之后第一个位置的地址 // 作为指针,其大小为 4/8byte printf("%d\n", sizeof(&a[0])); // 4/8 // 解释:a[0] 获取数组首元素,&a[0] 对数组首元素取地址,即数组首元素的地址 // 作为指针,其大小为 4/8byte printf("%d\n", sizeof(&a[0] + 1)); // 4/8 // 解释:易得 &a[0] 为数组首元素的地址,&a[0]+1 跳过一个元素, // 即数组第二个元素的地址,作为指针,其大小为 4/8byte }

-
char arr[] = { 'a', 'b', 'c', 'd', 'e', 'f' };
#include <string.h> int main() { char arr[] = { 'a', 'b', 'c', 'd', 'e', 'f' }; printf("%d\n", sizeof(arr)); // 6 // 解释:arr 表示整个数组,数组共6个元素,且元素类型为 char // 所以,数组的大小为 6byte printf("%d\n", sizeof(arr + 0)); // 4/8 // 解释:arr 表示数组首元素的地址,arr+0 表示跳过 0byte, // 同样是数组首元素的地址,作为指针,其大小为 4 / 8byte
printf("%d\n", sizeof(*arr)); // 1
// 解释:arr 表示数组首元素的地址,*arr 对数组首元素的地址解引用
// *arr 表示数组首元素,数组元素为char类型,则数组首元素的大小为 1byte
printf("%d\n", sizeof(arr[1])); // 1
// 解释:arr[1] 表示数组第二个元素,数组元素为char类型,
// 则数组第二个元素的大小为 1byte
printf("%d\n", sizeof(&arr)); // 4/8
// 解释:arr 表示整个数组,&arr 对整个数组取地址,
// 作为地址,其大小为 4/8byte
printf("%d\n", sizeof(&arr + 1)); // 4/8
// 解释:arr 表示真个数组,&arr 对整个数组取地址,
// &arr+1 表示跳过一个 char [6] 类型数组的大小,
// 则指向数组后第一个位置的地址,
// 作为地址,其大小为 4/8byte
printf("%d\n", sizeof(&arr[0] + 1)); // 4/8
// 解释:arr[0] 表示数组首元素,&arr[0] 获取数组首元素的地址
// &arr[0]+1 跳过一个char类型元素的大小,
// 则表示数组第二元素的地址,作为地址,其大小为 4/8byte
printf("%d\n", strlen(arr)); // 随机值
// 解释:arr表示数组首元素的地址,从arr开始向后找 \0 ,
// 所以,结果为随机值
printf("%d\n", strlen(arr + 1)); // 随机值
// 解释:arr表示数组首元素的地址,则 arr+1 表示数组第二个元素的地址
// 从数组第二个元素开始向后查找 \0 ,所以结果为随机值
// 但相对于上一个随机值,相差1
//printf("%d\n", strlen(*arr)); // error
//// 解释:arr表示数组首元素的地址,*arr 表示对首元素地址解引用,则得到首元素 'a'
//// 而 strlen 函数的参数是指针类型,所以 error
//printf("%d\n", strlen(arr[1])); // error
//// 解释:arr[1] 表示数组第二个元素 'b' ,同样参数类型不否,error
printf("%d\n", strlen(&arr)); // 随机值
// 解释:&arr 取真个数组的地址,但是其值与数组首元素地址相等
// 所以,从数组首元素开始向后找 '\0' ,结果为随机值
printf("%d\n", strlen(&arr + 1)); // 随机值
// 解释:&arr 取整个数组的地址,&arr+1 指向数组后第一个位置的地址
// 从该位置开始向后找 '\0' 结果为随机值
// 但相对于上一个随机值,相差6
printf("%d\n", strlen(&arr[0] + 1)); // 随机值
// 解释:arr[0] 表示数组第一个元素,&arr[0] 对数组第一个元素取地址
// &arr[0]+1 跳过一个元素的大小,指向数组第二个元素,
// 从数组第二个元素开始向后查找 '\0',结果为随机值
return 0;
}
```
-
char arr[] = "abcdef";
#include <string.h> int main() { char arr[] = "abcdef"; // 等价于 //char arr[] = { 'a','b','c','d','e','f','\0' }; printf("%d\n", sizeof(arr)); // 7 // arr表示真个数组 数组大小为 6×1=7byte printf("%d\n", sizeof(arr + 0)); // 4/8 // arr+0=arr 表示数组首元素地址 // 作为地址 大小为 4/8 byte printf("%d\n", sizeof(*arr)); // 1 // arr表示数组首元素地址 // *arr 表示数组首元素'a' 大小为1byte printf("%d\n", sizeof(arr[1])); // 1 // arr[1] 表示数组第二个元素'b' 大小为1byte printf("%d\n", sizeof(&arr)); // 4/8 // &arr 获取真个数组arr的地址 // 作为地址 大小为 4/8byte printf("%d\n", sizeof(&arr + 1)); // 4/8 // &arr 获取真个数组arr的地址 // &arr+1 跳过一个 char [7] 数组 // 数组arr后面第一个位置的地址 printf("%d\n", sizeof(&arr[0] + 1)); // 4/8 // arr[0] 数组首元素 // &arr[0] 数组首元素地址 // &arr[0]+1 数组第二个元素地址 // 作为地址 大小为 4/8byte printf("%d\n", strlen(arr)); // 6 // arr数组首元素'a'地址 // 从该地址开始向后找'\0' // strlen 获取从'a'到'\0'的字符串长度6 printf("%d\n", strlen(arr + 1)); // 5 // arr数组首元素'a'地址 // arr+1 数组第二个元素'b'地址 // strlen 获取从'b'到'\0'的字符串长度5 //printf("%d\n", strlen(*arr)); // error // arr 数组首元素地址 // *arr 数组首元素 // strlen 参数为地址 //printf("%d\n", strlen(arr[1])); // error // arr[1] 数组第二个元素 // strlen 参数为地址 printf("%d\n", strlen(&arr)); // 6 // &arr 获取整个数组地址 等于数组首元素'a'地址 // 从该地址开始向后找'\0' // strlen函数 从'a'到'\0'的字符串长度6 printf("%d\n", strlen(&arr + 1)); // 随机值 // &arr 获取整个数组地址 类型为数组指针 // &arr+1 跳过一个 char [7] 数组 --> 数组arr后第一个位置地址 // strlen函数 从该位置开始向后找'\0' 而后面的区域未知 // 所以结果为随机值 printf("%d\n", strlen(&arr[0] + 1)); // 5 // &arr[0] 等价于 &*(arr+0) 等价于 arr // arr+1 表示数组第二个元素'b'地址 // strlen 从'b'到'\0'的字符串长度5 return 0; }
-
char* p = "abcdef";
#include <string.h> int main() { char* p = "abcdef"; printf("%d\n", sizeof(p)); // 4/8 // p 字符指针 字符串首字符 'a' 的地址 // 大小为 4/8byte printf("%d\n", sizeof(p + 1)); // 4/8 // p 字符指针 字符串首字符 'a' 的地址 // p+1 跳过一个字符 字符串第二个字符 'b' 的地址 // 大小为 4/8byte printf("%d\n", sizeof(*p)); // 1 // p 字符指针 字符串首字符 'a' 的地址 // 解引用得到字符'a' 大小为 1byte printf("%d\n", sizeof(p[0])); // 1 // p[0] 等价于 *(p+0) // p+0 字符串第1个字符'a'的地址 // *(p+0) 字符串第1个字符'a' 大小为1byte printf("%d\n", sizeof(&p)); // 4/8 // p 字符指针 字符串首字符 'a' 的地址 // &p 获取字符指针的地址 二级指针 // 大小为 4/8byte printf("%d\n", sizeof(&p + 1)); // 4/8 // &p 获取字符指针的地址 二级指针 // &p+1 作为指针 大小为 4/8byte printf("%d\n", sizeof(&p[0] + 1)); // 4/8 // p[0] 字符串第1个字符'a' // &p[0] 字符串第1个字符 'a' 的地址 // &p[0]+1 跳过1个字符 --> 字符串第2个字符'b'地址 printf("%d\n", strlen(p)); // 6 // p 字符指针 字符串第1个字符'a'地址 // strlen 获取从'a'到'\0'字符串长度 printf("%d\n", strlen(p + 1)); // 5 // p+1 字符串第2个字符'b'地址 // strlen 获取从'b'到'\0'字符串长度 //printf("%d\n", strlen(*p)); // error // *p 字符'a' strlen函数的参数为指针 //printf("%d\n", strlen(p[0])); // error // p[0] 字符'a' strlen函数的参数为指针 printf("%d\n", strlen(&p)); // 随机值 // &p 字符指针p的地址 从p的地址开始向后查找'\0' // '\0' 可能再字符指针p中,也可能在其后面 // 所以 strlen(&p) 为随机值 printf("%d\n", strlen(&p + 1)); // 随机值 // &p+1 字符指针p后面一个位置的地址 // 从该位置向后查找 '\0' 后面的区域是未知的 // 所以 strlen(&p+1) 为随机值 printf("%d\n", strlen(&p[0] + 1)); // 5 // &p[0] 相当于 &*(p+0) = p // p+1 字符串第2个字符'b'地址 // 从该地址向后找 '\0' // strlen 获取从'b'到'\0'字符串长度 return 0; }

-
int a[3][4] = { 0 };
#include <stdio.h>
int main()
{
int a[3][4] = { 0 };
printf("%d\n", sizeof(a)); // 48
// a表示整个数组 大小为 3×4×4=48(byte)
printf("%d\n", sizeof(a[0][0])); // 4
// a[0][0] 表述数组第一行第一列的元素 int类型 大小为4byte
printf("%d\n", sizeof(a[0])); // 16
// a[0]表示二维数组a的第一行,大小为4×4=16(byte)
printf("%d\n", sizeof(a[0] + 1)); // 4/8
// a[0]表示二维数组a的第一行
// a[0]+1 a[0]为二维数组第一行数组首元素地址
// +1 跳过1字节 --> 二维数组第一行数组第二个元素地址
// 地址大小为 4/8byte
printf("%d\n", sizeof(*(a[0] + 1))); // 4
// *(a[0] + 1) 等价于a[0][1]
// 表示为二维数组a第一行第二列的元素
// int类型 大小为 4byte
printf("%d\n", sizeof(a + 1)); // 4/8
// a 表示数组首元素地址,二维数组的第一行数组的地址
// a+1 跳过 1个int [4] 类型一维数组 --> 二维数组的第二行地址
// 大小为 4/8(byte)
printf("%d\n", sizeof(*(a + 1))); // 16
// a 表示二维数组第一行地址
// a+1 跳过 1个int [4] 类型一维数组 --> 二维数组第二行地址
// *(a+1) 解引用得到二维数组第二行
// 大小为 4×4=16(byte)
printf("%d\n", sizeof(&a[0] + 1)); // 4/8
// a[0] 二维数组第一行
// &a[0] 二维数组第一行地址
// &a[0]+1 跳过一个 int [4] 类型一维数组 --> 二维数组第二行地址
// 大小为 4/8(byte)
printf("%d\n", sizeof(*(&a[0] + 1))); // 16
// &a[0]+1 二维数组第二行地址
// *(&a[0]+1) 解引用得到二维数组第二行
// 大小为 4×4=16(byte)
printf("%d\n", sizeof(*a)); // 16
// a 二维数组第一行地址
// *a 二维数组第一行
// 大小为 4×4=16(byte)
printf("%d\n", sizeof(a[3])); // 16
// a[3] 类型为int [4]
// 大小为 4×4=16(byte)
// sizeof并不会计算括号内的表达式,而是通过类型来确定大小
// 所以a[3]并不会越界访问
return 0;
}
2.9.2 练习1
struct Test
{
int Num;
char* pcName;
short sDate;
char cha[2];
short sBa[4];
}*p;
// 假设p的值位0x100000
// 已知结构体Test的大小位20字节
int main()
{
printf("%p\n", p + 0x1); // 0x100014 结构体指针+1
printf("%p\n", (unsigned long)p + 0x1); // 0x100001 整形+1
printf("%p\n", (unsigned int*)p + 0x1); // 0x100004 整形指针+1
}
2.9.3 练习2
int main()
{
int a[5] = { 1,2,3,4,5 };
int* ptr = (int*)(&a + 1);
// &a 获取真个数组的地址
// &a+1 跳过 int [5] 数组 --> 数组后第一个位置的地址
// (int*)(&a + 1) 强制类型转换为整形指针 赋值给ptr
printf("%d %d\n", *(a + 1), *(ptr - 1)); // 2 5
// *(a+1) 等价于 a[1] 等于2
// ptr-1 向前跳过 int 整形 --> 数组a最后一个元素地址
// *(ptr-1) 数组a最后一个元素
}
2.9.4 练习3
int main()
{
int a[4] = { 1,2,3,4 };
int* ptr1 = (int*)(&a + 1);
// ptr1 整形指针
// 其中 a 表示整个数组,&a 获取整个数组的地址
// &a+1 是数组a之后第一个位置的地址
// (int*)(&a+1) 将数组指针强制类型转换为整形指针,并赋值给ptr1
// ptr1[-1] 等价于 *(ptr1-1),
// ptr1-1 将指针向前移动一个int类型的大小(4byte)
// 即,*(ptr-1) 的值为a[3] = 4;
int* ptr2 = (int*)((int)a + 1);
// ptr2 整形指针
// 其中 a 表示数组首元素的地址,(int)a 将地址强制类型转换为int
// (int)a+1 则地址数值加1,而内存中每个地址代表1byte
// 比如:地址是0xFF9A8C11 地址数值 +1 后变为 0xFF9A8C12
// 即,指向原来指向的地址的后一个字节的地址
// 所以 (int)a+1 指向如图所示的位置
// (int*)((int)a+1) 将int型数值强制类型转换为整形指针,并赋值给ptr2
// 所以 对ptr2解引用 *ptr2 值为 (int*)((int)a+1) 开始向后四个字节的内容
printf("%x, %x\n", ptr1[-1], *ptr2);
// 输出结果(输出时将数字前无用的零去除):4,2000000
return 0;
}

2.9.5 练习4
int main()
{
int a[3][2] = { (0,1), (2,3),(4,5) };
// 注意:{}中是(),所以是逗号表达式
// 赋值语句相当于 int a[3][2]={ 1,3,5 };
int* p;
// 声明整形指针
p = a[0];
// 将数组a首元素赋值给p
// 数组a为二维数组,其首元素为第一行的数组
// 并赋值给整形指针p,则p的值为第一行数组的首元素的地址
printf("%d\n", p[0]); // 1
// p[0] 等价于 *(p+0) 对第一行数组首元素地址解引用
// 值为第一行数组的首元素
return 0;
}
2.9.6 练习5
int main()
{
int a[5][5];
int(*p)[4];
// 定义数组指针 指向对象类型为 int [4]
p = a;
// 将数组 a 首元素地址赋值给 p
printf("%p, %d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
// &p[4][2] 等价于 *(p+4)+2
// p+4 跳过 4×4=16 个元素
// *(p+4) 对数组指针解引用得到一维数组 p[4]
// *(p+4)+2 跳过2个整形型元素,即第18个元素的地址
//
// &a[4][2] 等价于 *(a+4)+2
// a+4 跳过 4×5=20 个元素
// *(a+4) 解引用得到一维数组 a[4]
// *(a+4)+2 跳过2个整形元素,即第22个元素的地址
//
// &p[4][2] - &a[4][2] 地址相减是两个地址间元素的个数
// &p[4][2] 第18个元素的地址
// &a[4][2] 第22个元素的地址
// 相减结果为 -4
// 在内存中十六进制表示为 FFFFFFFC
// %p 指针形式打印为 FFFFFFFC
// %d 将内存中二进制再转换为整形 打印为 -4
// FFFFFFFC, -4
return 0;
}
// 另外:运行时警告 “int (*)[4]”和“int (*)[5]”数组的下标不同

2.9.7 练习6
int main()
{
int aa[2][5] = { 1,2,3,4,5,6,7,8,9,10 };
int* ptr1 = (int*)(&aa + 1);
// &aa 其中 aa 表示二维数组 获取二维数组的地址
// &aa+1 跳过整个二维数组 指向二维数组之后第一个位置
// (int*)(&aa+1)强制类型转换为整形指针 并赋值给 ptr1
int* ptr2 = (int*)(*(aa + 1));
// aa 表示二维数组首元素地址--第一行地址
// aa+1 跳过 int [5] 大小,--第二行地址
// *(aa+1) 得到第二行数组--第二行数组首元素地址
// (int*)(*(aa+1)) 强制类型转换为整形指针 并赋值给 ptr2
// (不过,本来已经是整形指针,此转换没有必要)
printf("%d, %d\n", *(ptr1 - 1), *(ptr2 - 1));
// *(ptr1-1) 向前跳 一个整形大小 并解引用 得到二维数组的最后一个元素
// *(ptr2-1) 向前跳 一个整形大小 并解引用 得到二维数组第一行最后一个元素
// 输出结果为:10, 5
return 0;
}
2.9.8 练习7
int main()
{
char* a[] = { "work", "at", "home" };
char** pa = a;
// 数组 a 中元素的类型时 char*
// a 表示数组首元素的地址 所以a是二级指针 并赋值给pa
pa++;
// pa指向的类型为 char* 自增之后 跳过 1个地址的大小
// 指向数组a第二个元素的地址
printf("%s\n", *pa);
// 对 pa 解引用,得到数组 a 第二个元素(是一个地址)
// %s 打印 从该地址*pa开始向后查找'/0' 并打印字符串
// 输出结果为 "at"
return 0;
}
// 初始化数组a和二级指针pa之后的内存如下:

2.9.9 练习8
int main()
{
char* c[] = { "ENTER", "NEW", "POINT", "FIRST" };
char** cp[] = { c + 3, c + 2, c + 1, c };
char*** cpp = cp;
// 初始化之后的内存情况如果0所示
printf("%s\n", **++cpp); // POINT
printf("%s\n", *-- * ++cpp + 3); // ER
printf("%s\n", *cpp[-2] + 3); // ST
printf("%s\n", cpp[-1][-1] + 1); // EW
return 0;
}

-
第一个printf
printf("%s\n", **++cpp); // POINT // ++cpp cpp下移动如图1所示 // 第一次解引用 得到c+2 // 第二次解引用得到指向字符串"POINT"的字符指针 // 则打印字符串 "POINT"

-
第二个printf
printf("%s\n", *-- * ++cpp + 3); // ER // ++cpp cpp下移如图2所示 // 第一次解引用得到 c+1 // *-- 得到指向"ENTER"的字符指针 // +3 跳过3个字符,--> 字符'E'的地址 // 则打印字符串"ER"

-
第三个printf
printf("%s\n", *cpp[-2] + 3); // ST // *cpp[-2] 相当于 **(cpp-2),cpp-2 指向如图3所示 // *(cpp-2) 得到 c+3 // **(cpp-2) 得到 指向"FIRST"的字符指针 // *cpp[-2] + 3 跳过3个字符,--> 字符'S'的地址

-
第四个printf
printf("%s\n", cpp[-1][-1] + 1); // EW // cpp[-1][-1] 相当于 *(*(cpp-1)-1) // cpp-1 指向如图4所示 *(cpp-1) 得到c+2 // *(*(cpp-1)-1) 向后跳一个指针大小 得到指向"NEW"的字符指针 // cpp[-1][-1] + 1 向后跳过1个字符,--> 字符'E'的地址 // 则打印字符串 "EW"

- 2. 指针进阶
- 2.1 字符指针
* - 2.2 数组指针
* - 2.3 指针数组
* - 2.4 数组传参和指针传参
* - 2.5 函数指针
* - 2.6 函数指针数组
* - 2.7 指向函数指针数组的指针(了解)
* - 2.8 回调函数
* - 2.9 习题
*
- 2.1 字符指针