您当前的位置: 首页 > 

韩曙亮

暂无认证

  • 2浏览

    0关注

    1068博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

【C 语言】指针 与 数组 ( 指针 | 数组 | 指针运算 | 数组访问方式 | 字符串 | 指针数组 | 数组指针 | 多维数组 | 多维指针 | 数组参数 | 函数指针 | 复杂指针解读)

韩曙亮 发布时间:2018-01-04 11:36:59 ,浏览量:2

相关文章链接 : 1.【嵌入式开发】C语言 指针数组 多维数组 2.【嵌入式开发】C语言 命令行参数 函数指针 gdb调试 3.【嵌入式开发】C语言 结构体相关 的 函数 指针 数组 4.【嵌入式开发】gcc 学习笔记(一) - 编译C程序 及 编译过程 5.【C语言】 C 语言 关键字分析 ( 属性关键字 | 常量关键字 | 结构体关键字 | 联合体关键字 | 枚举关键字 | 命名关键字 | 杂项关键字) 6.【C 语言】编译过程 分析 ( 预处理 | 编译 | 汇编 | 链接 | 宏定义 | 条件编译 | 编译器指示字 ) 7.【C 语言】指针 与 数组 ( 指针 | 数组 | 指针运算 | 数组访问方式 | 字符串 | 指针数组 | 数组指针 | 多维数组 | 多维指针 | 数组参数 | 函数指针 | 复杂指针解读)

文章目录
  • 一. 指针
    • 1. 指针 简介
      • ( 1 ) 指针 概念 ( 本质 | 占用内存 ① 32位 4字节 ② 64 位 8 字节 | * ① 声明指针 ② 获取指向的值 )
      • ( 2 ) 指针 简单示例 ( * 的读写内存作用 | 指针相关类型大小)
    • 2. 传值 和 传址 调用
      • ( 1 ) 相关概念 ( 传值调用 复制实际值 | 传址调用 复制地址值 )
      • ( 2 ) 传址调用 ( 改变外部变量值 )
    • 3. 常量 和 指针
      • ( 1 ) 相关概念 ( 核心原则 左数右指 | 左数 ① const int* p ② int const* p 数据时常量 | 右指 int* const 指针是常量 )
      • ( 2 ) 验证 常量 指针 相关概念 ( 左数右指 )
  • 二. 数组
    • 1. 数组 简介
      • ( 1 ) 数组 概念 ( 数组地址 | 数组大小 显示 隐式 声明 | 数组初始化 [ 效率比后期赋值高 ] )
      • ( 2 ) 数组 示例 ( 定义 | 大小 | 初始化 )
    • 2. 数组地址与名称 概念
      • ( 1 ) 数组 概地址 ( 数组名 [ 数组首元素地址 ] 和 &数组名 [ 数组地址 ] | 数组名 类似于 常量指针 | 数组拷贝 )
      • ( 2 ) 数组 示例 ( 数组名 | 地址 | 数组拷贝禁止情况 )
    • 3. 数组 与 指针 区别
      • ( 1 ) 概念简介 ( ① 数组名就是首元素地址 不需要寻址 | ② 指针 中保存一个地址 指向首元素地址 需要寻址 | printf 打印 数组 或 指针 : 根据占位符自动判断打印地址还是打印内存中的具体内容 )
      • ( 2 ) 代码示例 ( 数组 | 指针 编译器处理上的区别 )
  • 三. 数组 指针 分析
    • 1. 指针 加减 运算方式
      • ( 1 ) 指针 加减法 运算 ( 指针指向的位置在同一个数组中改变才有意义 )
      • (2) 数组大小计算示例
      • ( 3 ) 指针 加法运算示例 ( 指针地址 + 4/8 * 被加数 )
      • ( 4 ) 指针 减法 运算示例
    • 2. 指针 比较 运算方式
      • ( 1 ) 指针 比较 运算 ( 大于 小于 大于等于 小于等于 运算的前提是 必须指向同一数组 中的元素 | 任意两指针只能进行 等于 不等于 的比较 )
      • ( 2 ) 指针 比较 运算代码示例 ( 用 指针 遍历数组 )
    • 3. 数组访问方式
      • ( 1 ) 下标 指针 访问 ( 推荐使用下标访问 )
      • ( 2 ) 下标 指针 访问 数组 性能 代码示例
    • 3. int array[]; array 和 &array 区别
      • ( 1 ) int array[] 中 array 和 &array 意义 ( ① array 数组首元素地址 | ② &array 数组地址 )
      • ( 2 ) array 和 &array 计算 代码示例
    • 4. 数组参数
      • ( 1 ) 数组参数 概念 ( 退化成指针 | 需要带上数组长度作为 附属参数 )
      • ( 2 ) 数组参数 代码示例 ( 数组大小 | 数组参数大小 )
    • 5. 数组 指针 对比 ( 内存分配 : ① 指针 分配 4 / 8 字节 ② 数组分配所有元素地址 | 作为参数 | 常量[ 数组 ] 变量[ 指针 ] 区别 )
  • 四. 字符串
    • 1. 字符串概念
      • ( 1 ) 概念 ( 本质 是 char[] 数组 | '\0' 结尾 | 存储位置 栈 堆 常量区 )
      • ( 2 ) 示例代码 ( 字符串概念 | 字符串 )
    • 2. 字符串 长度
      • ( 1 ) 字符串长度计算 ( 不包括 '\0' | 标准库中有该函数)
      • ( 2 ) 代码示例 ( 字符串长度计算示例 )
      • ( 3 ) 代码示例 ( 自己实现 strlen 方法 )
    • 3. 字符串函数 长度不受限制 情况
      • ( 1 ) 不受限制的字符串函数 ( 函数自动寻找 '\0' 确定字符串大小 | stpcpy | strcat | strcmp )
      • ( 2 ) 代码示例 ( 自己实现字符串拷贝函数 )
    • 4. 字符串函数 长度受限制 情况
      • ( 1 ) 受限制的字符串函数 ( 推荐使用 降低错误率 )
  • 五. 指针数组 与 数组指针
    • 1. 数组指针
      • ( 1 ) 数组类型介绍 ( 数组元素类型 | 数组大小 | 举例 int[8] )
      • (2) 数组指针简介 ( 指向数组的 一个 指针 | 数组指针类型定义方式 : 数组元素类型 ( * 指针名称 ) [数组大小] )
      • ( 3 ) 代码示例 ( 定义数组类型 | 数组指针用法 )
    • 2. 指针数组
      • ( 1 ) 指针数组简介 ( 数组中存储的元素是指针 | 数组指针 int (*array)[5] 本质是指针 | 指针数组 int* array[5] 本质是数组 )
      • ( 2 ) 代码示例 ( 指针数组使用案例 )
    • 3. main 函数参数 分析
      • ( 1 ) main 函数简介
      • (2) main 函数 代码示例
  • 六. 多维数组 和 多维指针
    • 1. 二维指针 ( 指向指针的指针 )
      • ( 1 ) 二维指针简介 ( 指向指针的指针 )
      • ( 2 ) 代码示例 ( 指针的传址调用 | 指向指针的指针 | 重置指针指向的空间 )
    • 2. 二维数组
      • ( 1 ) 二维数组 ( 存放方式 | 数组名 | 首元素类型 | 数组名 类似 常量指针 | )
      • (2) 代码示例 ( 以一维数组方式遍历二维数组 | 体现二维数组的数据排列 )
    • 3. 数组名
      • ( 1 ) 数组名 简介 ( 数组首元素地址 | &数组名 是 数组地址 )
      • ( 2 ) 代码示例 ( 数组名指针指向的内容 | 二维指针数组名对应的指针运算 )
      • ( 3 ) 代码示例 ( 一维数组遍历 | 二维数组遍历 )
      • ( 4 ) 代码示例 ( 为二维数组申请内存空间 )
  • 五. 数组参数 与 指针参数
    • 1. 数组参数退化为指针参数的意义
      • ( 1 ) 数组参数退化的相关概念 ( 指针退化成数组 )
      • ( 2 ) 代码示例 ( 二维数组参数 的指针退化 | 外层指针退化 | 内层数组指针没有退化 )
  • 六. 函数指针
    • 1. 函数类型 和 函数指针
      • (1) 相关概念 ( 函数类型要素 ① 返回值, ② 参数类型, ③ 参数个数, ④ 隐含要素 : 参数顺序 | 函数指针类型 返回值类型 (*变量名) (参数列表) )
      • ( 2 ) 代码示例 ( 定义函数指针 : ①typedef int(FUN)(int); FUN* p; 或者 ② void(*p1)(); | 给 函数指针 赋值 , 右值 可以直接使用 ① 函数名 或 ② &函数名 | 调用函数指针方法 : ① 函数指针变量名(参数) ② (*函数指针变量名)(参数) | 函数名 和 &函数名 是等价的 | 函数指针变量名(参数) 和 (*函数指针变量名)(参数) 也是等价的 )
    • 2. 回调函数
      • ( 1 ) 回调函数相关概念
      • ( 2 ) 代码示例 ( 回调函数示例 )
    • 3. 解读 复杂的 指针声明 ( 难点 重点 | ①找出中心标识符 ②先右 后左 看 确定类型 提取 ③ 继续分析 左右看 ... )

注意 : 博客中出现的关于指针的计算方式, 如果在 32 位电脑中, 指针的地址类型是 unsigned int 类型 , 占 4 字节 , 在 64 位电脑中 指针地址的类型是 unsigned long int , 占 8 个字节 ;

一. 指针 1. 指针 简介 ( 1 ) 指针 概念 ( 本质 | 占用内存 ① 32位 4字节 ② 64 位 8 字节 | * ① 声明指针 ② 获取指向的值 )

指针简介 :

  • 1.指针本质 : 指针本质也是一个变量 ;
  • 2.占用内存 : 指针变量也要在内存中占用一定大小的空间, 不同 类型的指针占用的内存大小都是 相同的 ;

32位系统 指针 占用内存大小 4 字节, 64位系统 指针 占用内存大小 8 字节;

  • 3.指针变量保存的值 : 指针变量中保存的是内存地址的值 ;

符号简介 :

  • 1.声明指针 : 在 声明指针变量时, * 表示声明一个指定类型变量的指针 ;
  • 2.使用指针 : 使用指针的时候, * 表示指针变量地址指向的内存中的值, 可以读取该地址的实际数据值 或者 向地址中写入实际数据值 ;
( 2 ) 指针 简单示例 ( * 的读写内存作用 | 指针相关类型大小)

指针简单示例 :

  • 1.代码示例 :
#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;
}
  • 2.运行结果 : 这里写图片描述
2. 传值 和 传址 调用 ( 1 ) 相关概念 ( 传值调用 复制实际值 | 传址调用 复制地址值 )

传值调用 :

  • 1.产生复制情况 : 传值调用时 会发生 实参数据值 复制到 形参中 ;

传址调用 :

  • 1.实现方式 : 将指针当做函数的参数, 因为指针也是变量, 可以当做参数使用 ;
  • 2.适用场景 : 如果需要在函数中修改实参的值, 并且执行函数完毕后保留下来, 这里就用到传址调用, 使用指针作为函数参数 ;
  • 3.适用场景2 : 参数数据类型较复杂, 如果参数很大, 传值调用需要实参到形参的复制, 会浪费性能 ;
( 2 ) 传址调用 ( 改变外部变量值 )

代码示例1 :

  • 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.执行结果 : 这里写图片描述

代码示例2 :

  • 1.代码 :
#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;
}
  • 2.执行结果 : 这里写图片描述
3. 常量 和 指针 ( 1 ) 相关概念 ( 核心原则 左数右指 | 左数 ① const int* p ② int const* p 数据时常量 | 右指 int* const 指针是常量 )

参考 : const 关键字 ;

const 修饰指针 : 需要符合下面的规则 :

声明特征const int* pp指针地址可变 p指针指向的内容不可变 (const 在 * 左边, 数据不可变)int const* pp指针地址可变 p指针指向的内容不可变 (const 在 * 左边, 数据不可变)int* const pp指针地址不可变 p指针指向的内容不可变 (const 在 * 右边, 地址不可变)const int* const pp指针地址不可变 p指针指向的内容不可变 (const 在 * 左边 和 右边, 数据和地址都不可变)

const 修饰指针规则 : ***左数 右指 (左边数据是常量, 右边指针是常量)***; 左数 : const 出现在 * 左边时, 指针指向的数据为常量, 指向的数据不可改变; 右指 : const 出现在 * 右边时, 指针地址本身是常量, 指针地址不可改变;

( 2 ) 验证 常量 指针 相关概念 ( 左数右指 )

参考 : const 关键字 ;

const 修饰指针规则 : 左数右指; 左数 : const 出现在 * 左边时, 指针指向的数据为常量, 指向的数据不可改变; 右指 : const 出现在 * 右边时, 指针地址本身是常量, 指针地址不可改变;

const 关键字 代码示例 : 修饰指针

  • 1.代码示例1 : const 出现在 * 左边, const int* p = &i;
#include 

int main()
{
	//定义普通的变量, 用于取地址用
	int i = 666;
	//定义一个 const 在 * 左边的例子, 意义是 指针指向的内容是常量
	//按照规则, 指针地址可改变, 指针指向的数据不可变
	const int* p = &i; 
	//指针指向的数据不可改变, 这里会报错
	*p = 444;
	 
	return 0;
}

这里写图片描述

  • 2.代码示例2 : const 出现在 * 左边, int const* p = &i;
#include 

int main()
{
	//定义普通的变量, 用于取地址用
	int i = 666;
	//定义一个 const 在 * 左边的例子, 意义是 指针指向的内容是常量
	//按照规则, 指针地址可改变, 指针指向的数据不可变
	int const* p = &i;
	//指针指向的数据不可改变, 这里会报错
	*p = 444;
	 
	return 0;
}

这里写图片描述

  • 3.代码示例3 : const 出现在 * 右边, int* const p = &i;
#include 

int main()
{
	//定义普通的变量, 用于取地址用
	int i = 666;
	//定义一个 const 在 * 右边的例子, 意思是 地址是常量
	//按照规则, 指针地址不可改变, 指针指向的内容可变
	int* const p = &i;
	//指针指向的数据不可改变, 这里会报错
	p = NULL;
	 
	return 0;
}

这里写图片描述

  • 4.代码示例4 : const 同时出现在 * 左边 和 右边, const int* const p = &i;
#include 

int main()
{
	//定义普通的变量, 用于取地址用
	int i = 666;
	//定义 const 同时出现在 * 左边 和 右边, 则指针的地址 和 指向的数据都不可改变
	const int* const p = &i;
	//下面的两个操作, 一个是想修改指针地址, 一个是想修改指针值, 这两个都报错.
	p = NULL;
	*p = 444;
	 
	return 0;
}

这里写图片描述

二. 数组 1. 数组 简介 ( 1 ) 数组 概念 ( 数组地址 | 数组大小 显示 隐式 声明 | 数组初始化 [ 效率比后期赋值高 ] )

数组 简介 :

  • 1.概念 : 数组 是 相同类型 的 变量 的 有序集合 ;
  • 2.数组示例 :
 int array[6];

定义数组 int array[6]; 意义 : 数组中包含 6 个 int 类型的数据 , 数组中每个元素都是 int 类型的 ; 第一个元素地址 : array 是数组中第一个元素的起始地址; 下标 : 可以通过下标来获取数组中指定位置的元素, array[0] 是第一个元素的位置, array[5] 是第六个元素的位置 ;

数组大小 :

  • 1.数组定义时必须声明大小 : 数组在定义时, 必须显示 或 隐式 的声明数组的大小 ;
  • 2.显示声明数组大小 : 定义数组时, 在数组名称后的中括号中声明数组大小 ;
int array[5]; 
int array[5] = {1, 2, 3} ; //这个也是显示声明, 数组大小为 5, 但是只指定了 前三个元素的大小 ; 
  • 3.隐式声明数组大小 : 声明数组时, 不在中括号中声明数组大小, 只在初始化中初始化指定个数的元素, 那么元素的个数就是数组的大小 ;
//隐式初始化, 该数组个数为 4
int array[] = {0, 1, 2, 3};

数组初始化 :

  • 1.完全初始化 : 数组大小为5, 将 5 个元素都在定义时指定位置 ;
  • 2.部分初始化 : 数组大小为5, 如果初始化前 1 ~ 4 个元素, 剩余的元素默认初始化为 0 ;
  • 3.初始化效率 : 初始化效率很高, 远远比依次赋值要高, 因此建议定义数组时最好初始化 ;
  • 4.最佳实践 :
//这里只对数组的第一个元素进行初始化为0, 那么其余的元素默认也初始化为0, 初始化效率要远远高于依次赋值的效率
int array[5] = {0}
( 2 ) 数组 示例 ( 定义 | 大小 | 初始化 )

数组 大小 初始化 示例 :

  • 1.代码 :
#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             
关注
打赏
1663594092
查看更多评论
0.0536s