相关文章链接 :
1.【嵌入式开发】C语言 指针数组 多维数组
2.【嵌入式开发】C语言 命令行参数 函数指针 gdb调试
3.【嵌入式开发】C语言 结构体相关 的 函数 指针 数组
4.【嵌入式开发】gcc 学习笔记(一) - 编译C程序 及 编译过程
5.【C语言】 C 语言 关键字分析 ( 属性关键字 | 常量关键字 | 结构体关键字 | 联合体关键字 | 枚举关键字 | 命名关键字 | 杂项关键字)
6.【C 语言】编译过程 分析 ( 预处理 | 编译 | 汇编 | 链接 | 宏定义 | 条件编译 | 编译器指示字 )
7.【C 语言】指针 与 数组 ( 指针 | 数组 | 指针运算 | 数组访问方式 | 字符串 | 指针数组 | 数组指针 | 多维数组 | 多维指针 | 数组参数 | 函数指针 | 复杂指针解读)
注意 : 博客中出现的关于指针的计算方式, 如果在 32 位电脑中, 指针的地址类型是 unsigned int 类型 , 占 4 字节 , 在 64 位电脑中 指针地址的类型是 unsigned long int , 占 8 个字节 ;
指针简介 :
32位系统 指针 占用内存大小 4 字节, 64位系统 指针 占用内存大小 8 字节;
符号简介 :
指针简单示例 :
#include
int main()
{
//1. 指针简单使用, * 符号作用
int i = 666;
//声明 int 类型 指针, 使用 * 符号声明指针
int *p = &i;
//这里验证下 i 即存放在 p 地址的内容, * 用于读取 指针 p 地址中的数据
printf("%d, %x, %d\n", i, p, *p);
//等价于 i = 888, *p 代表指针指向的内容, p 是指针的地址, 之类 * 用于向 p 地址指向的内存中写入数据
*p = 888;
//改变一个变量的大小可以使用其地址来改变, 不一定必须使用变量名称
printf("%d, %x, %d\n", i, p, *p);
//2. 指针大小示例
//32位系统 指针 占用内存大小 4 字节, 64位系统 指针 占用内存大小 8 字节
int* p_int;
char* p_char;
//a. 打印 int* 类型指针 和 char* 类型指针的 指针变量本身大小
//b. 打印 指针指向的内容大小, int 指针指向 int 类型, 因此 sizeof(*p_int) 结果是 4, sizeof(*p_char) 结果是 1
printf("%ld, %ld, %ld, %ld\n", sizeof(p_int), sizeof(p_char), sizeof(*p_int), sizeof(*p_char));
//打印 int* 和 char* 类型大小, 打印 int 和 char 类型大小
printf("%ld, %ld, %ld, %ld\n", sizeof(int*), sizeof(char*), sizeof(int), sizeof(char));
return 0;
}
传值调用 :
传址调用 :
代码示例1 :
#include
//传值调用案例, 任意改变参数的值, 不影响传入的变量值
int fun_1(int a, int b)
{
a = 444;
b = 444;
}
//传址调用案例, 如果在函数中修改了地址指向的内存的值, 那么最终的值改变了
int fun_2(int* a, int* b)
{
*a = 444;
*b = 444;
}
int main()
{
int x = 666, y = 888;
//传值调用
fun_1(x, y);
printf("x = %d, y = %d\n", x, y);
//传址调用
fun_2(&x, &y);
printf("x = %d, y = %d\n", x, y);
return 0;
}
代码示例2 :
#include
//传址调用, 替换传入的变量值
int swap(int *a, int *b)
{
int tmp = *a;
*a = *b;
*b = tmp;
}
int main()
{
int x = 666, y = 888;
printf ("x = %d, y = %d\n", x , y);
swap(&x, &y);
printf ("x = %d, y = %d\n", x , y);
return 0;
}
参考 : const 关键字 ;
const 修饰指针 : 需要符合下面的规则 :
声明 | 特征 |
---|---|
const int* p | p指针地址可变 p指针指向的内容不可变 (const 在 * 左边, 数据不可变) |
int const* p | p指针地址可变 p指针指向的内容不可变 (const 在 * 左边, 数据不可变) |
int* const p | p指针地址不可变 p指针指向的内容不可变 (const 在 * 右边, 地址不可变) |
const int* const p | p指针地址不可变 p指针指向的内容不可变 (const 在 * 左边 和 右边, 数据和地址都不可变) |
const 修饰指针规则 : ***左数 右指 (左边数据是常量, 右边指针是常量)***;
左数 : const 出现在 * 左边时, 指针指向的数据为常量, 指向的数据不可改变;
右指 : const 出现在 * 右边时, 指针地址本身是常量, 指针地址不可改变;
参考 : const 关键字 ;
const 修饰指针规则 : 左数右指;
左数 : const 出现在 * 左边时, 指针指向的数据为常量, 指向的数据不可改变;
右指 : const 出现在 * 右边时, 指针地址本身是常量, 指针地址不可改变;
const 关键字 代码示例 : 修饰指针
#include
int main()
{
//定义普通的变量, 用于取地址用
int i = 666;
//定义一个 const 在 * 左边的例子, 意义是 指针指向的内容是常量
//按照规则, 指针地址可改变, 指针指向的数据不可变
const int* p = &i;
//指针指向的数据不可改变, 这里会报错
*p = 444;
return 0;
}
#include
int main()
{
//定义普通的变量, 用于取地址用
int i = 666;
//定义一个 const 在 * 左边的例子, 意义是 指针指向的内容是常量
//按照规则, 指针地址可改变, 指针指向的数据不可变
int const* p = &i;
//指针指向的数据不可改变, 这里会报错
*p = 444;
return 0;
}
#include
int main()
{
//定义普通的变量, 用于取地址用
int i = 666;
//定义一个 const 在 * 右边的例子, 意思是 地址是常量
//按照规则, 指针地址不可改变, 指针指向的内容可变
int* const p = &i;
//指针指向的数据不可改变, 这里会报错
p = NULL;
return 0;
}
#include
int main()
{
//定义普通的变量, 用于取地址用
int i = 666;
//定义 const 同时出现在 * 左边 和 右边, 则指针的地址 和 指向的数据都不可改变
const int* const p = &i;
//下面的两个操作, 一个是想修改指针地址, 一个是想修改指针值, 这两个都报错.
p = NULL;
*p = 444;
return 0;
}
数组 简介 :
int array[6];
定义数组 int array[6];
意义 : 数组中包含 6 个 int 类型的数据 , 数组中每个元素都是 int 类型的 ;
第一个元素地址 : array 是数组中第一个元素的起始地址;
下标 : 可以通过下标来获取数组中指定位置的元素, array[0] 是第一个元素的位置, array[5] 是第六个元素的位置 ;
数组大小 :
int array[5];
int array[5] = {1, 2, 3} ; //这个也是显示声明, 数组大小为 5, 但是只指定了 前三个元素的大小 ;
//隐式初始化, 该数组个数为 4
int array[] = {0, 1, 2, 3};
数组初始化 :
//这里只对数组的第一个元素进行初始化为0, 那么其余的元素默认也初始化为0, 初始化效率要远远高于依次赋值的效率
int array[5] = {0}
数组 大小 初始化 示例 :
#include
//数组大小 和 初始化 示例
//数组大小 :
//初始化 : 如果不初始化, 那么数组中就是随机值; 全部初始化, 部分初始化 : 其余默认为 0
int main()
{
//1. 显示声明数组大小, 其实际大小以中括号为准, 大小为 5, 5个元素只有 前3个初始化为 0, 1, 2
//初始化说明 :
int array_1[5] = {0, 1, 2};
int array_3[5];
int array_4[5] = {0};
//2. 隐式声明数组大小, 其实际大小为 3, 三个元素全部初始化
int array_2[] = {0, 1, 2};
printf("array_1 大小 : %ld, array_1 数组个数 : %ld\n", sizeof(array_1), sizeof(array_1)/sizeof(*array_1));
printf("array_2 大小 : %ld, array_2 数组个数 : %ld\n", sizeof(array_2), sizeof(array_2)/sizeof(*array_2));
//打印 array_2 数组结果, 其中数组元素内容是 初始化值
printf("打印 int array_1[5] = {0, 1, 2}; 数组结果 : \n");
int i = 0;
for(i = 0; i < sizeof(array_1)/sizeof(*array_1); i ++)
{
printf("array_1[%d] = %d\n", i, array_1[i]);
}
//打印 array_3 数组结果, 其中数组元素内容是随机值
printf("打印 int array_3[5]; 数组结果 : \n");
for(i = 0; i < sizeof(array_3)/sizeof(*array_3); i ++)
{
printf("array_3[%d] = %d\n", i, array_3[i]);
}
//打印 array_4 数组结果, 其中数组元素内容是随机值
printf("打印 int array_4[5] = {0}; 数组结果 : \n");
for(i = 0; i < sizeof(array_4)/sizeof(*array_4); i ++)
{
printf("array_4[%d] = %d\n", i, array_4[i]);
}
return 0;
}
数组地址名称 简介 :
数组名称 :
数组名称不作为常量指针的场合 : 数组名类似于常量, 但不是常量, 下面两种场合数组名与常量指针不同 ;
数组拷贝禁用数组名直接赋值 :
数组代码示例 :
#include
int main()
{
int array_1[8] = {0};
int array_2[] = {0, 1, 2, 3};
//array_1 的类型是 int *
//&array_1 的类型是 int*[8], 根据编译时的 warning 警告可以看到这两个类型
printf("array_1 : %x, &array_1 : %x \n", array_1, &array_1 );
//这种用法是错误的, array_1 类似于一个常量指针, 其不能当做左值
//数组除了地址信息之外, 还附带大小信息, 如果只是地址赋值, 大小信息无法带过去, 因此数组不能这样拷贝赋值
//C语言 不支持 这样的赋值
//array_1 = array_2;
return 0;
}
printf 打印 数组 与 指针 变量 :
下面这张图形象的说明了 指针 与 数组的 区别 :
指针的起始地址 和 数组的起始地址 :
printf 打印 数组 或 指针 的 内容 或 地址 : 针对 字符数组 和 字符指针, 根据占位符自动判断打印地址还是打印内存中的具体内容 ;
#include
int main()
{
char array[10] = {'H', 'e', 'l', 'l', 'o'};
char *str = "Hello";
//1. 针对数组打印
// ( 1 ) 如果检测到 占位符 为 %s, 则会将组名首地址内存中的数据, 并一直到 \0 都打印出来(注意 不寻址)
// ( 2 ) 如果检测到 占位符 为 %x, 则会自动将数组首地址打印出来
printf("array : %s\n", array);
printf("array : %x\n", array);
//2. 针对指针打印
// ( 1 ) 如果检测到 占位符 为 %s, 则会寻址查找指针地址指向的内存, 将该内存中的字符串打印出来
// ( 2 ) 如果检测到 占位符 为 %x, 则会将指针地址打印出来
printf("str : %s\n", str);
printf("str : %x\n", str);
return 0;
}
代码示例 :
#include
//编译器如何处理 数组 和 指针
//1. 外部文件定义 : 在另外一个文件定义 char 指针 : char *p = "Hello";
// ( 1 ) 编译器操作 : 在符号表中放置符号 p, 然后为符号 p 分配空间,
// 查询到 p 是指针, 给 p 符号分配 4 字节, 4 字节存放地址, 指向 "Hello" 字符串 地址;
// 当打印 p 指针时, 编译器会按照指针地址寻址, 将指针指向的内存中取值并打印出来 ;
//2. 本文件声明 : 在本文件声明 : extern char p[] ;
// ( 2 ) 编译器操作 : 声明引用外部文件变量 p, 到符号表中查找 p, 但是编译器认为 p 是数组,
// p 是数组名, 代表数组地址;
// 当打印 p 数组时, 编译器会直接将 p 当做数组地址 打印出来;
//printf 打印变量规则 :
//( 1 ) 打印指针 : 编译器会寻址, 查找指针指向的内容, 然后将指针指向的内容打印出来 ;
//( 2 ) 打印数组 : 编译器不会寻址, 直接将数组名代表的内存空间地址打印出来 ;
//代码遵循原则 : 声明指针 数组, 在外部声明时类型要一致 ;
extern char p[];
int main()
{
//1. 此时 p 是数组, 直接打印 p, 会将数组地址打印出来, 以 %s 打印一个数组地址 会出乱码
printf("*p = %s\n", p);
//2. 正确使用数组 p 打印字符串的方法(模仿编译器行为手工寻址) : p 是指针, 指向 "Hello", 但是本文件中声明为类数组, 数组与指针打印时编译器会做不同处理;
// ( 1 ) 首先 p 是地址 ( 数组首地址 ),
// ① 将 p 转为 unsigned int* 类型的指针 : (unsigned int*)p ;
// ② 说明 : 此处是将一个变量强制转为 指向 unsigned int 类型的 指针, 这个是一个二维指针, 是指向地址的指针
// 为了获取 p 的地址(其地址是 unsigned int 类型的), 使用 * 即可提取 p 地址 ;
// ( 2 ) 获取字符串地址 : 获取 (unsigned int*)p 指向的地址, 即 字符串的地址, 使用 *((unsigned int*)p) 获取字符串地址;
// ( 3 ) 将字符串地址强转为char*指针 : (char*) (*((unsigned int*)p)) 即指向字符串的指针, 打印这个指针会将字符串打印出来
printf("*p = %s\n", (char*) (*((unsigned int*)p)));
return 0;
}
char *p = "Hello";
指针运算规则 :
指针指向数组元素规则 :
前提 : 指针指向一个数组元素时有以下规则 :
加 : 指针 + 1 指向数组的下一个元素 ;
减 : 指针 - 1 指向数组的上一个元素 ;
数组运算规则 :
指针减法运算 :
指针减法的过程 : 指针1 - 指针2 = ( 指针1指向的地址 - 指针2指向的地址 ) / sizeof (指针1和指针2的相同类型)
数组大小计算代码示例 :
#include
int main()
{
int array[10] = {0};
//打印出数组整体占用的内存数, 以及数组元素个数
printf("sizeof(array) = %ld, size = %ld \n", sizeof(array), sizeof(array)/sizeof(*array));
return 0;
}
数组名 指针 加法示例 :
#include
int main()
{
int array[10] = {0};
//打印出数组首元素地址, 打印出数组名 + 1 的值
printf("array 地址 : %x, array + 1 地址 : %x\n", array, array + 1);
return 0;
}
指针运算 : int * p, p + 1 代表的地址是 p 的地址 加上 4, 即加上了 一个 int 类型大小的地址;
指针减法代码示例 :
#include
int main()
{
int array_1[] = {0, 1, 2, 3, 4, 5};
int array_2[] = {6, 7, 8, 9};
//1. 定义指针 p1_0 指向 array_1 数组中的第 0 个元素
int* p1_0 = array_1;
//2. 定义指针 p1_5 指向 array_1 数组中的第 5 个元素
int* p1_5 = &array_1[5];
//3. 定义指针 p2_0 指向 array_2 数组中的第 0 个元素
int* p2_0 = array_2;
char c = 'c';
//4. 定义了一个 char 类型指针
char *p_c = &c;
//1. 计算 p1_5 指针指向的元素 与 p1_0 指向的元素, 两个元素在数组中的下标差
printf("%d\n", p1_5 - p1_0);
//2. ( p1_5 - p1_0 ) 与 ( ( unsigned int ) p1_5 - ( unsigned int ) p1_0 ) / sizeof ( int ) ) 是等价的 ;
printf("%d\n", ( ( unsigned int ) p1_5 - ( unsigned int ) p1_0 ) / sizeof ( int ) );
//3. 指针之间不支持加法, 这个操作在编译时就会报错, 这里注释掉 ;
//printf("%d\n", p1_5 + p1_0);
//4. 两个指针间的计算倒是没毛病, 但是两个指针分别指向两个数组, 这个计算的结果没有实际意义 ;
printf("%d\n", p1_5 - p2_0);
//5. 指针间进行运算的前提是 : 两个指针的类型必须相同, 这两个指针类型不同, 一个 int* 一个 char* , 编译时报错 ;
//printf("%d\n", p1_5 - p_c);
//6. 指针之间 不能进行乘法 和 除法, 编译时会报错
//printf("%d\n", p1_5 * p1_0);
//7. 指针之间 不能进行乘法 和 除法, 编译时会报错
//printf("%d\n", p1_5 / p1_0);
return 0;
}
指针的比较运算 :
使用指针遍历数组代码示例 :
#include
int main()
{
char array_str[] = {'H', 'e', 'l', 'l', 'o'};
//1. 定义数组第一个元素的起始指针
char* p_start = array_str;
//2. 定义数组最后一个元素 之后的指针, 这个指针只是做比较用的, 不会真正的寻址
char* p_end = array_str + (sizeof(array_str) / sizeof(*array_str));
//3. 定义循环控制变量
char* p = NULL;
//4. 遍历数组
for(p = p_start; p < p_end; p ++)
{
printf("%c", *p);
}
//5.此处换行
printf("\n");
return 0;
}
下标访问数组 和 指针访问数组 的示例 : 这两种访问数组的方式是等价的 ;
int array[5] = {0};
array[1] = 1;
array[2] = 2;
int array[5] = {0};
*(array + 1) = 1;
*(array + 2) = 2;
下标访问 和 指针访问 对比 :
推荐使用方式 : 现在的编译器编译出来的代码, 性能上 指针访问 与 下标访问基本相同, 出于代码可读性考虑, 推荐使用下标访问数组的方式 ;
下标 指针访问数组性能分析 : 以 数组 中的元素互相赋值为例 ;
数组 int array[] 中 array 和 &array 意义 :
两种指针的运算 :
代码示例 :
#include
//注意 : 在 64 位电脑上, 计算指针时需要将指针地址墙砖为 unsigned long int 类型
int main()
{
int array[5] = {0, 1, 2, 3, 4};
//1. 计算 p1 指针指向 :
// ( 1 ) &array 相当于 数组的地址, & array + 1 等价于 (unsigned long int) &array + sizeof ( *&array )
// 等价于 (unsigned long int) &array + sizeof ( array ) , sizeof ( array ) 计算的是整个数组的长度
// ( 2 ) 经过上述计算, 该指针指向 数组的尾地址, 即最后一个元素的结尾处
int *p1 = ( int* )( &array + 1 );
//2. 计算 p2 指针指向 :
// ( 1 ) 单纯地址 : (unsigned long int)array 这个操作将一个有类型的指针 直接强转为单纯的地址;
// ( 2 ) 单纯地址增加 : (unsigned long int)array + 1 就是之前的地址单纯的加 1, 不再含有指针运算中 增加 数组元素 字节大小倍数 的意义;
// ( 3 ) 拆分int类型字节 : 这样如果计算 p2 指针指向的数据, 会将 4 个字节拆散计算, 可能从 元素1 中1取 3 个字节, 元素2 中取 1个字节
// ( 4 ) 大小端方式选择 : 计算方式注意大端模式 和 小端模式, 一版的电脑都是小端模式,
// ( 5 ) 小端模式计算方式 : 这里我们按照小端模式计算, 小端模式即 高地址存放高字节, 低地址存放低字节
int *p2 = ( int* )( (unsigned long int)array + 1 );
//3. 计算 p3 指针指向
// ( 1 ) array + 1 等价于 ( unsigned long int ) array + sizeof ( *array ), 该地址等价于 array[1] 地址
// ( 2 ) 经过上述计算, p3 指针指向了 第二个元素的首地址
int *p3 = ( int* )( array + 1 );
//1. p1[-1] : p1 指向数组的尾地址, 其 -1 下标 即指向了数组最后一个元素, 等价于 array[4];
//2. p2[0] : p2 指向了数组的第一个元素的 第二个字节地址,
// 那么 p2[0] 的值是 数组第一个元素的 2 , 3, 4 三个字节, 再加上 第二个元素的 第一个字节;
// 小端地址策略 : 高位地址存高位, 低位地址存低位, 那么 第二元素第一字节是 高位, 其次是第一数组元素的 4, 3, 2 字节
//3. p3[1] : p3 指向了数组的第二个元素首地址, p3[1] 等价于 array[3]
printf("%d, %d, %d\n", p1[-1], p2[0], p3[1]);
return 0;
}
数组参数相关概念 :
1.数组作为参数时编译器行为 : 数组作为参数时, 编译器会将数组 退化成 指针, 此时这个指针就没有数组的长度信息了 ;
示例 1 : ***void method(int array[]) 等价于 method(int p)**, 此时 *p 中是不包含数组的长度信息的 ;
示例 2 : *void method(int array[100]) 等价于 method(int p), 此时 *p 中是不包含数组的长度信息的 ;
2.数组作为参数时的最佳用法 : 数组作为参数时, 应该定义另一个 int 类型的参数, 作为数组的长度信息 ;
代码示例 :
#include
/*
编译器在编译时, 就将参数改为了 int* array 了
C 语言中不会有数组参数的, 即使有, 也在编译时被替换成指针了
*/
void function(int array[100])
{
printf("方法中数组参数 array 大小 : %ld\n", sizeof(array));
}
int main()
{
int array[100] = {0};
printf("main 方法中 array 数组大小 : array : %ld \n", sizeof(array));
function(array);
return 0;
}
内存空间分配区别 :
参数上的区别 ( 等价 ) : 作为参数时, 数组 和 指针 参数时等价的, 数组会退化为指针, 丢失长度信息 ;
指针 数组 的 性质 :
字符串相关概念 :
代码示例 :
#include
#include
int main()
{
//1. s1 字符数组不是以 '\0' 结尾, 不是字符串
char s1[] = {'H', 'e', 'l', 'l', 'o'};
//2. s2 是字符串, 其在 栈内存 中分配内存控件
char s2[] = {'H', 'e', 'l', 'l', 'o', '\0'};
//3. s3 定义的是字符串, 在 只读存储区 分配内存空间
// s3 指向的内容无法修改, 如果想要修改其中的数据, 会在执行时报段错误
char* s3 = "Hello";
//这个操作在执行时会报段错误, 因为 s3 指针指向只读存储区
//s3[0] = 'h';
//4. s4 是以 '\0' 结尾, 是字符串, 在 堆空间 中分配内存
char* s4 = (char*)malloc(2*sizeof(char));
s4[0] = 'H';
s4[1] = '\0';
return 0;
}
字符串长度 :
针对 C 标准库已有的函数 :
代码示例 :
#include
#include
int main()
{
//1. 字符串长度 : 以 '\0' 之前的个数为准, 不包括 '\0' , 字符串长度 5
// 即使后面有有效的字符, 那么也不属于字符串 c
//2. 数组长度 : 数组长度 666
char c[666] = {'H', 'e', 'l', 'l', 'o', '\0', 'w', 'o', 'r', 'l', 'd'};
printf("字符串长度 : %ld, 字符数组长度 : %ld\n", strlen(c), sizeof(c));
return 0;
}
实现 strlen 方法代码示例 ( 普通版本 ) :
#include
#include
size_t strlen(const char* s)
{
size_t len = 0;
//1. 如果 s 为 NULL, 直接中断程序
assert(s);
//2. 指针先自增, 在取指针内指向的数据值, 看看是否为'\0'
// 如果指向的数据为 '\0', 那么循环中断执行下面的内容
while(* s++)
{
len ++;
}
return len;
}
int main()
{
char * s1 = "0123";
char * s2 = NULL;
//1. 测试 s1 的实际长度, 返回 4
printf("s1 长度 : %u\n", strlen(s1));
//2. 测试空字符串长度, 运行时会中断
printf("s2 长度 : %u\n", strlen(s2));
return 0;
}
实现 strlen 方法代码示例 ( 递归版本 ) :
#include
#include
size_t strlen(const char* s)
{
//1. assert(s) 先验证是否为 NULL , 如果为 NULL 中断程序
//2. 递归退出条件 : *s 为 '\0' 时, 递归退出
//3. s + 1 即指向 字符串的 下一个 char 元素, 计算 下一个 char 到结尾的个数,
// 当指向 '\0' 时, 之后的字符串个数为0, 然后依次退出递归
return ( assert(s), ( *s ? (strlen(s + 1) + 1) : 0 ) );
}
int main()
{
char * s1 = "0123";
char * s2 = NULL;
//1. 测试 s1 的实际长度, 返回 4
printf("s1 长度 : %u\n", strlen(s1));
//2. 测试空字符串长度, 运行时会中断
printf("s2 长度 : %u\n", strlen(s2));
return 0;
}
不受限制的字符串函数相关概念 :
不受限制的字符串函数示例 :
char *stpcpy(char *dest, const char *src);//字符串拷贝
char *strcat(char *dest, const char *src);//字符串拼接
int strcmp(const char *s1, const char *s2);//字符串比较
不受限制字符串函数 的 相关注意事项 :
1.字符串必须以 ‘\0’ 结尾 : 此类函数相关的字符串必须以 ‘\0’ 结尾, 因为字符串长度是根据找到的 ‘\0’ 来计算的, 如果没有 ‘\0’ 会报错 ;
2.字符串长度改变相关 : strcpy ( 字符串拷贝 ) 和 strcat ( 字符串拼接 ) 必须保证 拷贝 或 拼接的 目标数组 有足够的空间来保存结果字符串 ;
3.字符串比较函数 : strcmp 两个字符串比较, 如果返回 0 , 表示两个字符串相等 ;
注意字符串要求 : strcmp 函数不会修改 s1 和 s2 字符串的值, 但是两个字符串必须符合要求 以 ‘\0’ 结尾 ;
实现拷贝字符串函数 :
#include
#include
//函数作用, 将 src 字符串 拷贝到 dst 指针指向的内存中, 同时将拷贝完的结果 dst 返回
char* strcmp ( char* dst, const char* src )
{
//1. 安全编程, 传入的两个值不能为 NULL
assert(dst && src);
//2. 将 src 指针指向的 字符 赋值给 dst 指针指向的值, 然后两个指针自增 1
// 如果赋值的指针不等于 '\0' , 那么继续赋值, 如果赋值的值为 '\0' 就退出循环
while( (* dst++ = * src++) != '\0' );
//3. 其返回值也是 dst 参数, 参数也可以当作返回值使用
return dst;
}
int main()
{
char dst[20];
printf("%s\n", strcpy(dst, "字符串拷贝"));
return 0;
}
长度受限制的字符串函数 :
长度受限字符串函数 举例说明 :
数组类型 :
数组类型定义 :
数组指针 : 本质是一个指针 ;
数组指针 和 数组首元素指针 大小打印 :
#include
int main()
{
//定义数组
int array[5] = {0};
//1. 普通指针, 普通指针类型是 int, 指向数组首元素首地址, 其指向内容大小为数组的首元素
int *p = array;
//2. 数组指针, 数组指针类型是 int[5], 指向数组首地址, 其指向的内容大小是整个数组
typedef int(ARRAY_INT_5)[5];
ARRAY_INT_5 *p1 = &array;
//3. 打印数字首元素指针 和 数组指针 指向的内存大小
// 数组首元素指针 大小 为 4
// 数组指针 大小 为 20
printf("%ld, %ld\n", sizeof(*p), sizeof(*p1));
return 0;
}
代码示例 :
#include
//1. 自定义数组类型 int[5] 类型为 ARRAY_INT_5, 包含信息 ①int 类型数组 ②数组包含5个元素
typedef int(ARRAY_INT_5)[5];
//2. 自定义数组类型 float[5] 类型为 ARRAY_FLOAT_5, 包含信息 ①float 类型数组 ②数组包含5个元素
typedef float(ARRAY_FLOAT_5)[5];
//3. 自定义数组类型 char[5] 类型为 ARRAY_CHAR_5, 包含信息 ①char 类型数组 ②数组包含5个元素
typedef char(ARRAY_CHAR_5)[5];
int main()
{
//1. 使用自定义数组类型定义数组 : 定义一个 int 类型数组, 个数为 5个, 等价于 int array_1[5];
ARRAY_INT_5 array_1;
//( 1 ) sizeof(ARRAY_INT_5) 打印 ARRAY_INT_5 类型大小,
// 该类型包含信息 ① int 类型数组 大小 5 个 ② 大小为 20 字节
//( 2 ) sizeof(array_1) 打印 array_1 数组大小
printf("%ld, %ld\n", sizeof(ARRAY_INT_5), sizeof(array_1));
//2. 常规方法定义数组
float array_2[5];
//3. 数组指针 : 定义一个数组指针, 指向数组地址, 使用 &数组名 来获取数组地址, 其指向的内存内容大小为 20 字节
// 注意区分数组首元素地址, array_2 是数组首元素地址, 指向内容大小为 4 字节
// 注意区分 float* p = array_2, 这个指针是指向数组首元素地址的指针
ARRAY_FLOAT_5* p = &array_2;
//( 1 ) (*p)[i] 解析 : p 是数组指针, 其地址是数组地址 &array_2,
// *p 就是数组地址中存放的数组内容 *(&array_2), 即 (*p)[i] 等价于 array_2[i]
// (*p)[i] = i 语句 等价于 array_2[i] = i ;
int i = 0;
for( i = 0; i < sizeof(array_2)/sizeof(*array_2); i ++)
{
(*p)[i] = i;
}
//( 2 ) 打印数组中每个值
for(i = 0; i < sizeof(array_2)/sizeof(*array_2); i ++)
{
printf("array_2[%d] = %f\n", i, array_2[i]);
}
//4. 使用自定义数组类型定义数组
ARRAY_CHAR_5 array_3;
//5. 常规方法定义数组指针 : char(*p1)[5] 等价于 ARRAY_CHAR_5* p1;
char(*p1)[5] = &array_3;
//6. 定义数组指针, 但是这个赋值过程中 左右两边类型不一致,
// array_3 会被强转为 char[6] 类型的数组指针
char(*p2)[6] = array_3;
//( 1 ) &array_3 : 是 array_3 数组的 数组地址, 绝对值 等于 其数组首元素地址
//( 2 ) p1 + 1 : p1 指针 是 array_3 的数组指针, 该指针指向一个数组, p1 + 1 增加的是一个数组的地址
//( 3 ) p2 + 1 : p2 指针 也是一个数组指针, 这个数组比 array_3 数组大一个, 因此 p2 + 1 地址可能比 p1 + 1 大1字节
printf("&array_3 地址值 : %x, p1 + 1 地址值 : %x, p2 + 1 地址值 : %x\n", &array_3, p1 + 1, p2 + 1);
return 0;
}
指针数组 相关概念 :
指针数组代码示例 :
#include
/*
1. 函数作用 : 传入一个字符串, 和 一个字符串数组, 找出字符串在字符串数组中的索引位置, 从 0 开始计数
2. const char* key 参数分析 :
( 1 ) 常量分析 : 左数右指(const 在 * 左边 数据是常量, const 在 * 右边 指针是常量), 这里数据是常量, 不可修改
( 2 ) 参数内容 : 字符串类型, 并且这个字符串内容不能修改
3. const char* month[] 参数分析 : 指针数组
( 1 ) 常量分析 : 左数右指, 指针指向的数组内容不能修改
( 2 ) 参数内容 : 指针数组, 每个指针指向一个字符数组, 这些字符数组都是字符串, 这些指针不可改变
*/
int find_month_index(const char* key, const char* month[], const int month_size)
{
int index = -1;
//4. 遍历指针数组中指向的每个字符串, 与传入的 key 进行对比, 如果相等, 那么返回字符串在指针数组的索引
// ( 1 ) 对比函数 : 注意 strcmp 函数, 对比两个字符串, 如果相等 则 返回 0 ;
int i = 0;
for(i = 0; i < month_size; i ++)
{
if(strcmp(key, month[i]) == 0)
{
index = i;
}
}
return index;
}
int main()
{
//1. 定义 指针数组, month 数组中, 每个元素都是一个指针, 每个指针指向字符串
const char* month[] = {
"January", "Febrary", "March", "April",
"May", "June", "July", "August",
"September", "October", "November", "December"
};
printf("查询 July 字符串索引 : %d\n", find_month_index("July", month, sizeof(month)/sizeof(*month)));
printf("查询 HanShuliang 字符串索引 : %d\n", find_month_index("HanShuliang", month, sizeof(month)/sizeof(*month)));
return 0;
}
main 函数分析 :
int main()
int main(int argc)
int main(int argc, char* argv[])
int main(int argc, char* argv[], char* env[])
main 函数代码示例 :
#include
int main(int argc, char* argv[], char* env[])
{
//1. 循环控制变量
int i = 0;
//2. 打印 main 函数参数
printf("########参数个数%d 开始打印参数\n", argc);
for(i = 0; i < argc; i ++)
{
printf("%s\n", argv[i]);
}
printf("########参数打印完毕\n");
printf("\n");
printf("\n");
//3. 打印环境变量
printf("########开始打印环境变量\n");
for(i = 0; env[i] != NULL; i ++)
{
printf("%s\n", env[i]);
}
printf("########环境变量打印完毕\n");
return 0;
}
指向 指针 的 指针 ( 二维指针 ) :
指针变量 的 传值 和 传址 调用 :
如 : void fun ( char *p ) , 这是相对于指针的传值调用, 相对于 char 类型数据的传址调用, 用于修改 p 指针指向的内存中的值 ;
如 : void fun(char ** pp) 该传址调用 即 传入的是 char* 指针的地址, 修改的是 pp 二维指针 指向的 char* 类型指针 ;
代码示例 :
#include
#include
/*
1. 方法作用 : 为参数 char **p 指向的 指针 重新分配内存空间
2. char **p 参数 : 需要在函数中修改函数外部的变量, 就需要传入一个 指向要修改的目标 的指针变量
需要修改的内容 是一个指针, 那么需要传入的参数就是 指向 指针变量 的指针
这样才能完成 传址调用, 用来修改函数外部的变量
3.
*/
int reset_memory(char **p, int size, int new_size)
{
//1. 定义函数中使用的变量
//( 1 ) 定义返回值
int ret = 0;
//( 2 ) 循环控制变量
int i = 0;
//( 3 ) 新空间的大小
int len = 0;
//( 4 ) 申请的新空间
char* p_new = NULL;
//( 5 ) 用于计算用的指向新空间的指针
char* p_new_tmp = NULL;
//( 6 ) 用于指向老空间指针, 使用 * 与 传入的二维指针 计算 得来
// char** p 是指向 char* 指针 的 指针, 使用 *p 即可获得 指向 char* 的指针
char* p_old = *p;
//2. 前置条件安全判定, 避免无意义崩溃
if(p == NULL || new_size <= 0)
{
return ret;
}
//3. 重新分配空间, 并拷贝内存中的内容
//( 1 ) 重新分配内存空间
p_new = (char*)malloc(new_size);
//( 2 ) 为计算使用的指针赋值, 之后赋值是需要使用指针的自增, 为了不改变指针内容, 这里我们设置一个临时指针
p_new_tmp = p_new;
//( 3 ) 获取 新空间 和 老空间 内存大小的最小值, 将老空间的内容拷贝到新空间中
// 1> 新空间大于老空间 : 只拷贝所有老空间中的内容到新空间中
// 2> 新空间小于老空间 : 只拷贝能将新空间填满的内容, 字符串可能丢失 '\0'
len = (size < new_size) ? size : new_size;
//( 4 ) 将老空间中的内容拷贝到新空间中
for(i = 0; i < len; i ++)
{
*p_new_tmp++ = *p_old++ ;
}
//4.释放原空间, 并修改传址调用的参数内容
free(*p);
*p = p_new;
ret = 1;
return ret;
}
int main(int argc, char* argv[], char* env[])
{
//1. 第一次为 char 类型指针分配 10 个字节空间
char* p = (char*)malloc(10);
// 打印指针 p 指向的内存地址, 即分配的内存空间地址
printf("p 第一次分配空间后指向的地址 : %x\n", p);
//2. 重置内存空间, 原来分配 10字节, 现在改为分配 8 字节
// 注意 : 在 reset_memory 函数中改变函数外部变量的值, 需要传址调用, 即将变量的地址传到函数中
reset_memory(&p, 10, 8);
// 打印重置空间后的指针指向的地址
printf("p 重置空间后指向的地址 : %x\n", p);
return 0;
}
二维数组 相关概念 : 二维数组 int array[5][5] ;
一些注意点 :
1.编译器没有二维数组概念 : C语言中没有二维数组改变, 编译器 都按照一维数组来处理, 数组的大小在编译时就确定了 ;
2.二维数组由来 : C 语言中的数组元素可以是任何类型, 即可以是一维数组, 这样就产生了二维数组 ;
3.首元素地址确定时间 : 在编译阶段确定的 除了 数组大小外, 数组的首元素也是在编译阶段确定的, 在程序运行阶段首元素地址不能被修改 (看做常量) ;
代码示例 :
#include
#include
/*
遍历一维数组
1. int *array 参数解析 : 传入的一维数组的首地址
2. int size 参数解析 : 用于限制数组大小, 数组传入后也会退化为指针,
数组是带有元素个数属性的, 因为数组类型是 int[9], 但是指针不包含元素个数 指针类型是 int*
*/
void array_traverse (int *array, int size)
{
int i = 0;
for(i = 0; i < size; i ++)
{
//使用数组递增的方式打印数组元素
printf("array[%d] = %d\n", i, *array++);
}
}
int main()
{
//1. 定义二维数组
int array[2][2] = {{0, 1}, {2, 3}};
//2. 获取二维数组首地址, 将其赋值给 一维数组 int* p
int *p = &array[0][0];
//3. 打印一维数组, 可以看到, 二维数组的数据排列
// 先将 {0, 1} 放入的内存, 然后紧接着存放 {2, 3} 数据
array_traverse(p, 4);
return 0;
}
代码分析 :
将二维数组的首地址赋值给 类型相同 的一维数组, 遍历该一维数组, 并且该数组的大小为 二维数组所有值得大小 , 由此可以看出, 二维数组的数据排布是按照索引, 先放入二维数组的第一个数组元素, 在按照索引依次将数组放入内存中 ;
数组名 相关概念 :
代码示例 :
#include
int main()
{
//1. 定义二维数组, array 代表了 数组首地址
// array 的本质是 指向 一个 int(*)[5] 类型的一维数组 的指针
int array[5][5];
//2. 定义数组指针, 指针 p 指向一个 int(*)[4] 一维数组
int(*p)[4];
//3. 注意了, 两个指针类型不同
// ( 1 ) array 指针 : 指向 int(*)[5] 类型的一维数组, array + 1 即内存中移动了 5 个 int 大小的内存
// ① 下标运算 : array[1] 即 array 指针 + 1 ;
// ( 2 ) p 指针 : 指向 int(*)[4] 类型的一维数组, p + 1 即内存中移动了 4 个 int 大小的内存
p = array;
/*
4. 指针计算过程 :
( 1 ) &array[4][2] 计算 : array 指针指向 int(*)[5] 一维数组,
array[4] 计算过程是 要加上 4 个 int(*)[5] 一维数组 ,
array[4] 指向 内存中(二维数组起始为0) 第 20 个元素, array[4][2] 指向第 22 个元素,
&array[4][2] 是一个指向 array[4][2] 的int 类型指针
( 2 ) &p[4][2] 计算 : p 指针指向 int(*)[4] 一维数组,
p[4] 计算过程是 要加上 4 个 int(*)[4] 一维数组 ,
p[4] 指向 内存中(二维数组起始为0) 第 16 个元素, p[4][2] 指向第 18 个元素,
&p[4][2] 是一个指向 p[4][2] 的int 类型指针
( 3 ) 一个指向第 22 个元素, 一个指向第 18 个元素, 其起始地址是一样的, 因此结果是 -4
*/
printf("%ld\n", &p[4][2] - &array[4][2]);
return 0;
}
代码示例 :
#include
//1. 宏定义, 使用该宏 计算数组大小
#define ARRAY_SIZE(a) (sizeof(a)/sizeof(*a))
int main()
{
//1. 遍历一维数组
int array_1d[5] = {0, 1, 2, 3, 4};
int i = 0;
for(i = 0; i < ARRAY_SIZE(array_1d); i++)
{
/*
*(array_1d + i) 执行过程 :
( 1 ) array_1d + i : 首元素指针 + i, 即获取指向第 i 个元素的指针
( 2 ) *(array_1d + i) : 获取第 i 个元素指向的数据, 该表达式等价于 array_1d[i]
*/
printf("array_1d[%d] = %d\n", i, *(array_1d + i));
}
//2. 遍历二维数组
int array_2d[3][3] = { {0, 1, 2}, {3, 4, 5}, {6, 7, 8} };
int j = 0;
for(i = 0; i < ARRAY_SIZE(array_2d); i ++)
{
for(j = 0; j < ARRAY_SIZE(array_2d[0]); j ++)
{
/*
*(*(array_2d + i) + j) 计算过程 :
( 1 ) array_2d : 是二维数组数组首元素, 本质是数组指针, 类型是 int(*)[3], 指向一个 int[3] 数组 ;
( 2 ) array_2d + i : 是指向数组 第 i 个元素, 其地址本质能上移动了 i 个 int[3] 数组所占的空间 ;
( 3 ) *(array_2d + i) : array_2d + i 是一个数组指针, 使用 * 即获得其指向的内容 一个 int[3] 数组;
( 4 ) *(array_2d + i) + j : 获取其指向的 int[3] 类型数组的 第 j 个指针 ;
( 5 ) *(*(array_2d + i) + j) : 即获取 int[3] 类型数组中 第 j 个指针指向的实际的 int 数据 ;
*/
printf("array_2d[%d][%d] = %d\n", i, j, *(*(array_2d + i) + j));
}
}
return 0;
}
算法思想 : 为 int[3][3] 申请内存空间 ;
代码示例 :
#include
#include
//1. 宏定义, 使用该宏 计算数组大小
#define ARRAY_SIZE(a) (sizeof(a)/sizeof(*a))
/*
为二维数组分配内存空间
1. 参数说明 :
( 1 ) int row : 二维数组的行数, 即 有多少 一维数组;
( 2 ) int column : 每个一维数组中的元素个数
*/
int** malloc_array_2d(int row, int column)
{
//1. 分配 数组指针 空间, 分配数组指针空间, 共有 row 个指针
int** ret = (int**)malloc(sizeof(int*) * row);
//2. 分配数据存放空间, 即 row * column 个 int 类型数据存放的空间
int * p = (int*)malloc(sizeof(int) * row * column);
if(ret && p)
{
//3_1. ret 和 p 内存分配成功, 那么开始 将 ret 数组指针 与 p 内存空间 连接起来
int i = 0;
for(i = 0; i < row; i ++)
{
/*
说明 :
( 1 ) ret 是分配的 数组指针 数组的 首元素, ret[i] 是第 i 个 数组指针 元素
( 2 ) p + i*column 是 具体到 int 类型元素的首地址
( 3 ) 这里将 int* 类型指针赋值给了 int(*)[column] 类型指针, 所幸是地址赋值, 将
int* 指针存放的地址 赋值给了了 int(*)[column] 类型指针的地址
*/
ret[i] = (p + i * column);
}
}
else
{
//3_2. 如果分配空间失败, 即 ret 或 p 有一个内存分配失败, 释放所有内存, 返回空
free(ret);
free(p);
ret = NULL;
}
return ret;
}
//释放申请的二维数组空间
void free_array_2d(int** array_2d)
{
//array_2d[0] 代表了 第一个数组 的收个 int 类型元素地址, 该操作释放了int类型数据空间内存
free(array_2d[0]);
//array_2d 代表了 数组指针 数组的 首元素地址, 该操作释放了数组指针空间的内存
free(array_2d);
}
int main()
{
//1. 申请二维数组空间
int** array_2d = malloc_array_2d(3, 3);
int i = 0, j = 0;
//2. 为二维数组赋值
for(i = 0; i < 3; i ++)
{
for(j = 0; j < 3; j ++)
{
*(*(array_2d + i) + j) = i * 3 + j;
}
}
//3. 打印二维数组内容
for(i = 0; i < 3; i ++)
{
for(j = 0; j < 3; j ++)
{
printf("array_2d[%d][%d] = %d\n", i, j, array_2d[i][j]);
}
}
return 0;
}
一维数组参数退化为指针 :
二维数组参数退化问题 :
下面列举数组参数与指针参数一些等价关系 : 去中括号 ( [] ), 变星号 ( * ) , 放左边;
数组参数 | 指针参数 |
---|---|
一维数组 int array[5] | 指针 *int array |
一维指针数组 int array[5]* | 指针 int* array* |
二维数组 int array[3][3] | 指针 *int (array)[3] |
注意事项 :
1.多维数组参数要求 : 传递多维数组参数时, 需要将除第一维之外的其它所有维度的大小都带上 , 否则无法确定数组大小 和 类型, 编译时会报错 ;
2.数组参数限制 :
( 1 ) 一维数组 : 可以不带数组长度, 但是必须指定数组的大小 ;
( 2 ) 二维数组 : 数组 第一维 长度可以不带 ( 即 数组指针 元素个数可以省略 ) , 但是数组指针 指向的 数组类型大小必须指定 ( 第二维的大小必须指定 ) ;
( 3 ) 三维数组 : 数组 第一维 长度可不带, 但是第二维 和 第三维 长度 必须带上 ;
代码分析 : 如 int array[3][3] ;
代码示例 :
#include
void traverse_array_2d(int array_2d[][2], int row)
{
/*
计算二维指针的列数
( 1 ) array_2d 是二维指针中的 数组指针 数组中的首元素
( 2 ) array_2d 是一个完整的数组指针, 该指针中包含着 其指向的数组的 类型 和 大小
( 3 ) 数组指针退化时, 退化的只是 array_2d 的 数组指针 数组 (最外层的一维数组) 大小,
其每个元素都是一个 数组指针, 这个数组指针 包含 数组类型 和 大小, 没有退化
*/
int column = sizeof(*array_2d) / sizeof(*array_2d[0]);
int i = 0, j = 0;
for(i = 0; i < row; i ++)
{
for(j = 0; j < column; j ++)
{
printf("array_2d[%d][%d] = %d\n", i, j, array_2d[i][j]);
}
}
}
int main()
{
int array_2d[2][2] = {{0, 1}, {2, 3}};
traverse_array_2d(array_2d, 2);
return 0;
}
函数类型 :
函数指针 :
代码示例 :
#include
//1. 定义函数类型 FUN, 其类型为 int(int)
typedef int(FUN)(int);
//2. 定义一个 int(int) 类型的函数
int fun_1(int i)
{
return i * i * i;
}
//3. 定义一个 void() 类型的函数
void fun_2()
{
printf("调用 fun_2 函数\n");
}
int main()
{
/*
1. 将 fun_1 函数赋值给 FUN 类型指针
( 1 ) FUN 是函数类型, 其类型是 int(int)
( 2 ) fun_1 函数名是函数体的入口地址, 可以直接赋值给指针 ;
*/
FUN* p = fun_1;
//2. 通过指针调用函数, 指针变量名(参数) 可以调用指针指向的函数 ;
printf("调用 p 指针结果 : %d\n", p(10));
/*
3. 定义 void() 类型的函数指针 p1
( 1 ) 方法返回值(*函数指针变量名)(参数列表) 是定义一个函数指针
( 2 ) &函数名 也可以获取函数的地址, 与 函数名 是等价的;
注意 : 这里与数组不同,
数组名 和 &数组名 是两种不同的概念
函数名 和 &函数名 是等价的
*/
void(*p1)() = &fun_2;
/*
4. 通过函数指针变量调用函数
( 1 ) 通过 函数指针变量名(参数) 和 (*函数指针变量名)(参数) 两种方法都可以调用函数指针变量指向的函数
( 2 ) 函数名 和 &函数名 是等价的,
函数指针变量名(参数) 和 (*函数指针变量名)(参数) 也是等价的
*/
p1();
(*p1)();
return 0;
}
回调函数简介 :
代码示例 :
#include
//1. 定义函数类型 FUN, 其类型为 int(int)
typedef int(FUN)(int);
//2. 定义一个 int(int) 类型的函数
int fun_1(int i)
{
return i * i * i;
}
//3. 定义一个 int(int) 类型的函数
int fun_2(int i)
{
return i + i + i;
}
//4. 定义一个 int(int) 类型的函数
int fun_3(int i)
{
return i + i * i;
}
/*
5. 此处参数 FUN function 是一个函数类型,
将 FUN 类型函数注册给 execute 函数
*/
int execute(int i, FUN function)
{
return i + function(i);
}
int main()
{
//1. 给 execute 注册 fun_1 函数, 具体事件发生时调用 fun_1 函数
printf("int i = 5; i * fun_1(i) = %d\n", execute(5, fun_1));
//2. 给 execute 注册 fun_2 函数, 具体事件发生时调用 fun_2 函数
printf("int i = 8; i * fun_2(i) = %d\n", execute(8, fun_2));
//3. 给 execute 注册 fun_3 函数, 具体事件发生时调用 fun_3 函数
printf("int i = 2; i * fun_3(i) = %d\n", execute(2, fun_3));
return 0;
}
指针 定义 复杂性来源 :
复杂指针阅读技巧 ( 主要是 区分 函数指针 和 数组指针 ) 右左法则 :
1.最里层标示符 : 先找到最里层的圆括号中的标示符;
数组指针和函数指针的标示符 ( 指针变量名 ) 都在中间的圆括号中, 因此该步骤先找到指针变量名
2.右左看 : 先往右看, 再往左看 ;
3.确定类型 : 遇到 圆括号 “()” 或者 方括号 “[]” 确定部分类型, 调转方向 ; 遇到 * 说明是指针 , 每次确定完一个类型 , 将该类型提取出来 , 分析剩下的 ;
一种可能性 :
int (*) [5] , 遇到中括号说明是数组指针类型,
int(*)(int, int) , 遇到圆括号 说明是函数指针类型 ;
4.重复 2 , 3 步骤 : 一直重复, 直到 指针 阅读结束 ;
指针阅读案例 :
/*
解读步骤 :
1. 研究第一个标示符 p
( 1 ) 先找最里层的圆括号中的 标示符 p
( 2 ) p 往右看, 是圆括号, 然后往左看, 是 * , 可以确定 p 是一个指针
( 3 ) 将 (*p) 拿出来, 然后看剩下的部分, 右看是 圆括号 (, 明显是个函数类型, int (int*, int (*f)(int*)) 很明显是一个 函数类型
2. 解读函数类型 int (int*, int (*f)(int*))
( 1 ) 函数类型 int (int*, int (*f)(int*)) 的返回值类型是 int 类型
( 2 ) 函数类型的第一个参数类型是 int* , 即 int 类型指针类型
( 3 ) 函数类型的 第二个参数是 int (*f)(int*) 也是一个函数类型指针
3. 解读 int (*f)(int*) 参数
( 1 ) 标示符是 f, 由看 是 圆括号, 坐看是 * , 因此 f 是一个指针;
( 2 ) 将(*f) 提取出来, int(int*) 是一个函数类型, 其返回值是 int 类型, 参数是 int* 指针类型
总结 :
指针 p 是一个指向 int(int*, int (*f)(int*)) 类型函数的指针,
函数返回值是 int 类型, 参数是 int* 指针类型 和 int (*)(int*) 函数指针 类型
指针 f 是一个指向 int(int*) 类型函数的指针, 其返回值是 int 类型, 参数是 int* 指针类型
*/
int (*p) (int*, int (*f)(int*));
/*
解读步骤 :
1. 确定 p1 的类型
( 1 ) 找出最中心圆括号中的标示符, p1;
( 2 ) 数组类型确定 : 右看发现中括号, 说明 p1 是一个数组, 数组中有 3 个元素, 数组的类型目前还不知道
( 3 ) 数组内容确定 : 左看发现 *, 说明数组中存储的是 指针类型, 这里就知道了 ;
目前知道了 数组 p1 的要素 : ① 数组中有 3 个元素, ② 数组元素类型是指针;
2. 确定数组指针类型 : 上面确定了 p1 的数组个数 和 元素是指针, 但是指针指向什么不确定
( 1 ) 将 (*p1[3]) 提取出来, int(int*) 明显是一个函数类型, 返回值是 int 类型, 参数是 int* 类型
总结 : p1 是一个数组, 数组中含有 3 个元素, 数组元素类型为 int(*)(int*) 函数指针, 即 指向 int(int*) 类型函数的指针
*/
int (*p1[3])(int*);
/*
解读步骤 :
1. 确定 p2 类型 :
( 1 ) 找出最中心的圆括号中的标示符, p4, 右看是圆括号 ), 掉头左看是 * , 说明 p2 是一个指针;
( 2 ) 将 (*p2) 提取出来, 分析int (*[5])(int*),
( 3 ) 右看是 [, 说明指针指向了一个数组, 该数组有 5 个元素
( 4 ) 左看是 * , 说明数组中的元素是指针, 下面分析指针指向什么
2. 确定指针数组中指针指向什么 :
( 1 ) 将 (*(*p2)[5]) 提取出来, 可以看到 指针指向 int(int*) 类型的函数
总结 : p2 是一个数组指针, 指向一个数组, 该数组有 5 个元素, 每个元素都是一个指针,
数组中的指针元素指向 int(int*) 类型的函数
*/
int (*(*p2)[5])(int*);
/*
解读步骤 :
1. 确定 p3 基本类型 :
( 1 ) p3 右看是圆括号 ), 左看是 * , 说明p3 是指针
( 2 ) 将 (*p3) 提取出来, int (*(int*))[5], 右看是 圆括号 (, 说明指针指向一个函数
( 3 ) 函数的参数是 int*, 返回值是一个指针, 指向一个类型
2. 确定返回值的类型
( 1 ) 将 (*(*p3)(int*)) 提取出来, 右看是 [, 说明是数组类型,
剩下 int[5] 类型, 返回值指针指向一个 int[5] 类型的数组,
那么返回值类型是 int(*)[5] 数组指针
总结 : p3 指向一个 函数, 函数的参数是 int* 指针, 返回值是 指向 int[5] 数组 的 数组指针
*/
int (*(*p3)(int*))[5];