目录
一.字符指针变量
在C语言中,字符串通常通过字符数组来存储,但实际上,我们经常使用字符指针来操作字符串,因为字符指针提供了更多的灵活性和便利性。字符指针变量指向的是字符串的首个字符的地址。
初始化字符指针变量的两种方式
指向字符串常量:
这里,ptr
指向了一个由 "Hello, World!"
组成的字符串常量,存储在只读内存区域。注意,字符串常量本身在内存中是自动以 \0
(空字符)结尾的。
指向字符数组:
这里,arr
是一个字符数组,它存储了 "Hello, World!"
字符串,并且自动包含了结尾的空字符 \0
。ptr
被初始化为指向 arr
的首地址,即字符串的第一个字符 'H'
的地址。
下面我们来研究这样一段代码,以帮助我们更好的理解字符指针变量
下面我们来分析这段代码,理解出现这样的结果的原因
str1
和 str2
的比较:
str1
和 str2
是两个独立的字符数组,它们各自在内存中占据不同的空间来存储字符串 "hello bit."
。
当在 if
语句中使用 str1 == str2
进行比较时,实际上比较的是这两个数组首元素的地址。由于 str1
和 str2
是两个独立的数组,它们的首元素地址自然不同。
因此,输出将是 "str1 and str2 are not same\n"
。
str3
和 str4
的比较:
str3
和 str4
是两个指向常量字符串 "hello bit."
的指针。在 C 语言中,相同的字符串常量可能会被编译器优化为在内存中只存储一份副本,然后让多个指向该字符串的指针指向同一地址。
如果编译器进行了这种优化,str3
和 str4
将指向相同的地址,比较结果将为真。
所以我们可以得到结论:这里str3和str4指向的是⼀个同⼀个常量字符串。C/C++会把常量字符串存储到单独的⼀个内存区域, 当几个指针指向同⼀个字符串的时候,他们实际会指向同⼀块内存。从而str3和str4相同。
二.数组指针变量
2.1 数组指针的定义
数组指针是指向整个数组的指针,而不是指向数组中的单个元素。其定义方式稍有不同,需要在指针类型中明确指定数组的大小(在C99及以后的版本中,对于变长数组(VLA),这个大小可以是变量)。但在大多数情况下,特别是当使用固定大小的数组时,我们并不需要在指针类型中指定数组的大小,因为编译器通常通过上下文来确定这一点。
不过,为了理解数组指针的概念,我们可以这样声明一个指向整型数组的指针(假设数组大小为5):
这里,ptr
是一个指针,它指向一个包含5个整数的数组。注意括号的使用非常重要,它们确保了*ptr
首先被计算为一个指向数组的指针,而不是ptr
被当作一个指针数组。
2.2 如何使用数组指针
一旦你有了指向数组的指针,你就可以通过它来访问数组中的元素了。但是,由于数组指针是指向整个数组的,你不能像使用普通指针那样通过解引用(即*ptr
)来直接访问数组的第一个元素;相反,你需要通过指针和索引的组合来访问元素。
假设arr
是一个包含5个整数的数组,并且我们有一个指向该数组的指针ptr
,如下所示:
要访问arr
中的元素,你可以这样做:
或者,由于ptr
已经是指向数组的指针,你可以省略最外层的解引用操作符,直接通过索引访问:
注意,这里ptr[0]
实际上等同于*ptr
,它们都指向arr
这个数组。而[1]
则是访问该数组的第二个元素。
三.二维数组传参的本质
3.1 二维数组的内存布局
首先,理解二维数组在内存中的布局是基础。一个二维数组int arr[M][N];
在内存中连续存储,可以视为一个包含M个元素的一维数组,其中每个元素又是一个包含N个整数的数组。这意呀着,如果我们有一个int arr[3][4];
,那么内存中的布局将是连续的12个整数,但我们可以通过两个索引来访问它们,即arr[i][j]
。
3.2 数组名作为指针
在C/C++中,数组名在大多数情况下会被视为指向其首元素的指针。然而,这个规则在多维数组上有一个重要的例外。对于二维数组arr[M][N]
,arr
本身不是一个指向int
的指针,而是一个指向含有N个int
的数组的指针,即int (*)[N]
。 二维数组传参本质上也是传递了地址,传递的是第一行这个一维数组的地址。
3.3 二维数组作为参数传递
当我们将二维数组作为参数传递给函数时,我们实际上传递的是指向这个二维数组首元素的指针。但由于二维数组的首元素是一个一维数组,因此传递给函数的实际上是指向这个一维数组的指针的指针(或指向数组的指针)。但是,在函数声明和定义时,我们通常不直接使用指针的指针,而是采用数组语法来简化表示。
例如,下面是一个接受二维数组作为参数的函数声明:
注意这里N
必须是已知的,因为编译器需要知道每个内层数组的大小来正确计算内存地址。而M
(外层数组的大小)则作为参数传递,因为编译器不需要在编译时就知道这个值来计算内存地址。
3.4 二维数组传参的例子
总结:二维数组传参,形参的部分可以写成数组,也可以写成指针形式。
四.函数指针变量
4.1 函数指针的定义
函数指针变量是存储函数地址的变量。在C语言中,函数名在表达式中会被转换为指向该函数的指针。因此,你可以定义一个指针变量来存储这个地址,并通过这个指针来调用函数。
4.2 函数指针变量的声明
4.3 函数指针变量的使用
4.4 typedef 关键字
typedef
关键字在 C 语言中用于为现有的数据类型定义一个新的名称(别名)。这个特性在简化复杂类型声明、提高代码可读性和可维护性方面非常有用。下面通过几个例子来讲解 typedef
的用法。
基本类型别名
在这个例子中,typedef
用于为 int
类型定义了一个新的名称 Integer
。之后,在代码中就可以使用 Integer
来代替 int
声明整型变量了。
结构体别名
在这个例子中,typedef
与 struct
一起使用,为包含两个整型成员 x
和 y
的结构体定义了一个别名 Point
。这使得在声明结构体变量时不必每次都写 struct
关键字。
指针别名
在这个例子中,typedef
为指向 int
类型的指针定义了一个别名 IntPtr
。使用 IntPtr
可以使指针的声明更加简洁。
函数指针别名
在这个例子中,typedef
为函数指针定义了一个别名 FuncPtr
,该函数指针指向的函数接受两个 int
类型的参数并返回一个 int
类型的值。这使得在声明和使用函数指针时更加直观和方便。
五.函数指针数组
5.1 函数指针数组的定义
在C语言中,函数指针数组是一个数组,其元素是指向函数的指针。
简单地说,把函数的地址存到⼀个数组中,那这个数组就叫函数指针数组
5.2 函数指针数组的声明
首先,我们需要理解如何声明一个指向特定函数的指针。假设我们有一个函数原型如下:
那么,指向这个函数的指针可以这样声明:
接下来,我们可以声明一个数组,其元素是这种类型的指针:
注意这里的括号,它们对于正确地解析声明至关重要。上面的声明意味着 funcPtrArray
是一个数组,其元素是指向函数的指针,这些函数接受两个 int
类型的参数并返回一个 int
类型的值。
5.3 函数指针数组的使用
为了更具体地说明这一点,我们来看一个例子,其中我们定义了几个简单的计算函数,并将它们的地址存储在函数指针数组中,然后遍历这个数组来调用这些函数。
在这个例子中,我们定义了三个简单的函数 add
、subtract
和 multiply
,它们分别执行加法、减法和乘法操作。然后,我们声明了一个函数指针数组 funcPtrArray
,并使用花括号列表将其初始化为这三个函数的地址。
在 main
函数中,我们通过一个 for
循环遍历 funcPtrArray
数组,并使用数组索引来调用每个函数。注意,由于数组中的元素是函数指针,因此我们可以像调用函数一样使用 funcPtrArray[i](5, 3)
来调用它们,其中 i
是数组索引,5
和 3
是传递给函数的参数。
六.转移表
函数指针数组的用途之一是作为转移表(也称为分派表或跳转表),它允许程序根据输入或条件动态地选择并执行相应的函数。这种机制在处理多个条件分支时特别有用,因为它可以提高代码的可读性、可维护性和性能。下面将详细讲解函数指针数组作为转移表的用途,并通过一个具体的例子来说明。
具体例子:计算器程序
假设我们有一个简单的计算器程序,它支持加法、减法、乘法和除法运算。我们可以使用函数指针数组来实现一个转移表,根据用户输入的操作符来调用相应的数学运算函数。
步骤 1:定义数学运算函数
首先,我们定义四个数学运算函数:add
、subtract
、multiply
和divide
。
步骤 2:定义函数指针数组(转移表)
然后,我们定义一个函数指针数组,将上述四个函数的地址存储在其中。数组的索引可以与用户输入的操作符相对应。
步骤 3:主函数和菜单
在这个例子中,函数指针数组operationFunc
作为转移表,根据用户输入的操作符索引来调用相应的数学运算函数。这种方式使得代码更加清晰、易于扩展和维护。
后记
兄弟们点点赞,点点关注咯,谢谢各位大佬们!
共勉!!!