C 字符串

:::info 在 C 语言中,字符串实际上是使用空字符 \0 结尾的一维字符数组。因此,\0 是用于标记字符串的结束。
空字符(Null character)又称结束符,缩写 NUL,是一个数值为 0 的控制字符,\0 是转义字符,
意思是告诉编译器,这不是字符 0,而是空字符。 :::

C 中有大量操作字符串的函数, 可以在 C 标准库中找到更多字符串相关的函数。

序号 函数 & 目的
1 strcpy(s1, s2);复制字符串 s2 到字符串 s1。**
2 strcat(s1, s2);连接字符串 s2 到字符串 s1 的末尾。**
3 strlen(s1);返回字符串 s1 的长度。**
4 strcmp(s1, s2);如果 s1 和 s2 是相同的,则返回 0;如果 s1s2 则返回大于 0。**
5 strchr(s1, ch);返回一个指针,指向字符串 s1 中字符 ch 的第一次出现的位置。**
6 strstr(s1, s2);返回一个指针,指向字符串 s1 中字符串 s2 的第一次出现的位置。**
  1. #include <stdio.h> //输入输出库
  2. #include <string.h> //字符串库
  3. int main ()
  4. {
  5. char str1[10] = "hello";
  6. char str2[10] = "world";
  7. char str3[10];
  8. int len;
  9. /* 复制 str1 到 str3 */
  10. strcpy(str3, str1);
  11. printf("strcpy( str3, str1) : %s\n", str3 );
  12. /* 连接 str1 和 str2 */
  13. strcat( str1, str2);
  14. printf("strcat( str1, str2): %s\n", str1 );
  15. /* 连接后,str1 的总长度 */
  16. len = strlen(str1);
  17. printf("strlen(str1) : %d\n", len );
  18. }

C 数组

:::info C 语言支持数组数据结构,它可以存储一个固定大小的相同类型元素的顺序集合。
数组是用来存储一系列数据,但它往往被认为是一系列相同类型的变量。
数组中的特定元素可以通过索引访问,第一个索引值为 0
在 C 中要声明一个数组,需要指定元素的类型和元素的数量 :::

声明数组

type arrayName [ arraySize ]; 这叫做一维数组。arraySize 必须是一个大于零的整数常量,type 可以是任意有效的 C 数据类型 double balance[10]; int apple[10];

初始化数组

在 C 中,您可以逐个初始化数组,也可以使用一个初始化语句 double balance[5] = {1000.0, 2.0, 3.4, 7.0, 50.0}; 大括号 { } 之间的值的数目不能大于我们在数组声明时在方括号 [ ] 中指定的元素数目。 如果您省略掉了数组的大小,数组的大小则为初始化时元素的个数 double balance[] = {1000.0, 2.0, 3.4, 7.0, 50.0}; 所有的数组都是以 0 作为它们第一个元素的索引,也被称为基索引

:::success

多维数组

C 语言支持多维数组
type name[size1][size2]…[sizeN];
type arrayName [ x ][ y ];
其中,type 可以是任意有效的 C 数据类型,arrayName 是一个有效的 C 标识符。
一个二维数组可以被认为是一个带有 x 行和 y 列的表格。 :::

C enum(枚举)

:::success 枚举是 C 语言中的一种基本数据类型,它可以让数据更简洁,更易读
枚举语法定义格式为:
enum 枚举名 {枚举元素1,枚举元素2,……};
注意:第一个枚举成员的默认值为整型的 0,后续枚举成员的值在前一个成员上加 1。我们在这个实例中把第一个枚举成员的值定义为 1,第二个就为 2,以此类推。

enum season {spring, summer=3, autumn, winter};
没有指定值的枚举元素,其值为前一元素加 1。也就说 spring 的值为 0,summer 的值为 3,autumn 的值为 4,winter 的值为 5 ::: :::warning 在C 语言中,枚举类型是被当做 int 或者 unsigned int 类型来处理的,
所以按照 C 语言规范是没有办法遍历枚举类型的。
不过在一些特殊的情况下,枚举类型必须连续是可以实现有条件的遍历, 不连续,这种枚举无法遍历 :::

C 指针

:::tips 学习 C 语言的指针既简单又有趣。
通过指针,可以简化一些 C 编程任务的执行,还有一些任务,
如动态内存分配,没有指针是无法执行的。
所以,想要成为一名优秀的 C 程序员,学习指针是很有必要的。
每一个变量都有一个内存位置,每一个内存位置都定义了可使用 & 运算符访问的地址,
它表示了在内存中的一个地址。 :::

什么是指针

:::info 指针也就是内存地址,指针变量是用来存放内存地址的变量。就像其他变量或常量一样,
必须在使用指针存储其他变量地址之前,对其进行声明。指针变量声明的一般形式为:
type *var_name;

在这里,type 是指针的基类型,它必须是一个有效的 C 数据类型,
var_name 是指针变量的名称。
用来声明指针的星号 * 与乘法中使用的星号是相同的。
但是,在这个语句中,星号是用来指定一个变量是指针。以下是有效的指针声明:

int ip; / 一个整型的指针 /
double
dp; / 一个 double 型的指针 /
float fp; / 一个浮点型的指针 /
char
ch; / 一个字符型的指针 /

所有实际数据类型,不管是整型、浮点型、字符型,还是其他的数据类型,
对应指针的值的类型都是一样的,都是一个代表内存地址的长的十六进制数
不同数据类型的指针之间唯一的不同是,指针所指向的变量或常量的数据类型不同

*指针就是指向变量和对象的地址,很多时候变量,特别是对象的数据量实在太大,程序员就会用指针来做形参,只需要传递一个地址就行,大大提高了效率。 :::

如何使用指针

使用指针时会频繁进行以下几个操作:
定义一个指针变量、
把变量地址赋值给指针、
访问指针变量中可用地址的值。
这些是通过使用一元运算符 * 来返回
位于操作数所指定地址的变量的值

  1. #include <stdio.h>
  2. int main ()
  3. {
  4. int var_runoob = 10;
  5. int *p; // 定义指针变量
  6. p = &var_runoob;
  7. printf("var_runoob 变量的地址: %p\n", p);
  8. return 0;
  9. }
  10. /*
  11. var 变量的地址: 0x7ffeeef168d8
  12. ip 变量存储的地址: 0x7ffeeef168d8
  13. *ip 变量的值: 20
  14. */

C 中的 NULL 指针

:::info 在变量声明的时候,如果没有确切的地址可以赋值,
为指针变量赋一个 NULL 值是一个良好的编程习惯。
赋为 NULL 值的指针被称为指针。
NULL 指针是一个定义在标准库中的值为零的常量

在大多数的操作系统上,程序不允许访问地址为 0 的内存,因为该内存是操作系统保留的。然而,内存地址 0 有特别重要的意义,它表明该指针不指向一个可访问的内存位置。但按照惯例,如果指针包含空值(零值),则假定它不指向任何东西。 :::

C 指针详解

在 C 中,有很多指针相关的概念,这些概念都很简单,但是都很重要。下面列出了 C 程序员必须清楚的一些与指针相关的重要概念

概念 描述
指针的算术运算 可以对指针进行四种算术运算:++、—、+、-
指针数组 可以定义用来存储指针的数组。
指向指针的指针 C 允许指向指针的指针。
传递指针给函数 通过引用或地址传递参数,使传递的参数在调用函数中被改变。
从函数返回指针 C 允许函数返回指针到局部变量、静态变量和动态内存分配。

指针的算术运算

:::info C 指针是一个用数值表示的地址。因此,您可以对指针执行算术运算。
可以对指针进行四种算术运算:++、—、+、-。
假设 ptr 是一个指向地址 1000 的整型指针,是一个 32 位的整数,让我们对该指针执行下列的算术运算:
ptr++

在执行完上述的运算之后,ptr 将指向位置 1004,因为 ptr 每增加一次,
它都将指向下一个整数位置,即当前位置往后移 4 字节。
这个运算会在不影响内存位置中实际值的情况下,移动指针到下一个内存位置。
如果 ptr 指向一个地址为 1000 的字符,上面的运算会导致指针指向位置 1001,因为下一个字符位置是在 1001。
概括一下:

  • 指针的每一次递增,它其实会指向下一个元素的存储单元。
  • 指针的每一次递减,它都会指向前一个元素的存储单元。
  • 指针在递增和递减时跳跃的字节数取决于指针所指向变量数据类型长度,比如 int 就是 4 个字节。 :::
  1. #include <stdio.h>
  2. const int MAX = 3;
  3. int main ()
  4. {
  5. int var[] = {10, 100, 200};
  6. int i, *ptr;
  7. /* 指针中的数组地址 */
  8. /* 指针中第一个元素的地址 */
  9. ptr = var;
  10. for ( i = 0; i < MAX; i++)
  11. {
  12. printf("存储地址:var[%d] = %p\n", i, ptr ); //输出指针的地址
  13. printf("存储值:var[%d] = %d\n", i, *ptr ); //输出指针的地址值
  14. /* 指向下一个位置 */
  15. ptr++;
  16. }
  17. return 0;
  18. }
  19. int main ()
  20. {
  21. int var[] = {10, 100, 200};
  22. int i, *ptr;
  23. /* 指针中最后一个元素的地址 */
  24. ptr = &var[MAX-1];
  25. for ( i = MAX; i > 0; i--)
  26. {
  27. printf("存储地址:var[%d] = %p\n", i-1, ptr );
  28. printf("存储值:var[%d] = %d\n", i-1, *ptr );
  29. /* 指向下一个位置 */
  30. ptr--;
  31. }
  32. return 0;
  33. }
  34. int main ()
  35. {
  36. int var[] = {10, 100, 200};
  37. int i, *ptr;
  38. /* 指针中第一个元素的地址 */
  39. ptr = var;
  40. i = 0;
  41. //取10的地址 与 200 的地址 顺序 判断
  42. while ( ptr <= &var[MAX - 1] )
  43. {
  44. printf("存储地址:var[%d] = %p\n", i, ptr );
  45. printf("存储值:var[%d] = %d\n", i, *ptr );
  46. /* 指向上一个位置 */
  47. ptr++;
  48. i++;
  49. }
  50. return 0;
  51. }

C 指针数组

  1. #include <stdio.h>
  2. const int MAX = 3;
  3. int main ()
  4. {
  5. int var[] = {10, 100, 200};
  6. int i;
  7. for (i = 0; i < MAX; i++)
  8. {
  9. printf("Value of var[%d] = %d\n", i, var[i] );
  10. }
  11. return 0;
  12. int main ()
  13. {
  14. int var[] = {10, 100, 200};
  15. //定义指针数组
  16. int i, *ptr[MAX];
  17. for ( i = 0; i < MAX; i++)
  18. {
  19. ptr[i] = &var[i]; /* 赋值为整数的地址 */
  20. }
  21. for ( i = 0; i < MAX; i++)
  22. {
  23. //取出 地址的值
  24. printf("Value of var[%d] = %d\n", i, *ptr[i] );
  25. printf("%p\n", ptr[i]);
  26. }
  27. return 0;
  28. }

指向指针的指针

指向指针的指针是一种多级间接寻址的形式,或者说是一个指针链。
通常,一个指针包含一个变量的地址。
当我们定义一个指向指针的指针时,第一个指针包含了第二个指针的地址,第二个指针指向包含实际值的位置。
当一个目标值被一个指针间接指向到另一个指针时,访问这个值需要使用两个星号运算符

  1. #include <stdio.h>
  2. int main ()
  3. {
  4. int V;
  5. int *Pt1;
  6. int **Pt2;
  7. V = 100;
  8. /* 获取 V 的地址 */
  9. Pt1 = &V;
  10. /* 使用运算符 & 获取 Pt1 的地址 */
  11. Pt2 = &Pt1;
  12. /* 使用 pptr 获取值 */
  13. printf("var = %d\n", V );
  14. printf("Pt1 = %p\n", Pt1 );
  15. printf("*Pt1 = %d\n", *Pt1 );
  16. printf("Pt2 = %p\n", Pt2 );
  17. printf("**Pt2 = %d\n", **Pt2);
  18. return 0;
  19. }
  20. var = 100
  21. Pt1 = 0x7ffe3e597894
  22. *Pt1 = 100
  23. Pt2 = 0x7ffe3e597898
  24. **Pt2 = 100

C 传递指针给函数

:::info C 语言允许传递指针给函数,只需要简单地声明函数参数为指针类型即可 :::

  1. #include <stdio.h>
  2. #include <time.h>
  3. void getSeconds(unsigned long *par);
  4. int main ()
  5. {
  6. unsigned long sec;
  7. getSeconds( &sec );
  8. /* 输出实际值 */
  9. printf("Number of seconds: %ld\n", sec );
  10. return 0;
  11. }
  12. void getSeconds(unsigned long *par)
  13. {
  14. /* 获取当前的秒数 */
  15. *par = time( NULL );
  16. return;
  17. }
  1. #include <stdio.h>
  2. /* 函数声明 */
  3. double getAverage(int *arr, int size);
  4. int main ()
  5. {
  6. /* 带有 5 个元素的整型数组 */
  7. int balance[5] = {1000, 2, 3, 17, 50};
  8. double avg;
  9. /* 传递一个指向数组的指针作为参数 */
  10. avg = getAverage( balance, 5 ) ;
  11. /* 输出返回值 */
  12. printf("Average value is: %f\n", avg );
  13. return 0;
  14. }
  15. double getAverage(int *arr, int size)
  16. {
  17. int i, sum = 0;
  18. double avg;
  19. for (i = 0; i < size; ++i)
  20. {
  21. sum += arr[i];
  22. }
  23. avg = (double)sum / size;
  24. return avg;
  25. }

C 函数指针

:::info 函数指针是指向函数的指针变量
通常我们说的指针变量是指向一个整型、字符型或数组等变量,而函数指针是指向函数
函数指针可以像一般函数一样,用于调用函数、传递参数
函数指针变量的声明:
typedef int (*fun_ptr)(int,int); // 声明一个指向同样参数、返回值的函数指针类型 :::

  1. #include <stdio.h>
  2. int max(int x, int y)
  3. {
  4. return x > y ? x : y;
  5. }
  6. int main(void)
  7. {
  8. /* p 是指针函数 */
  9. int (* p)(int ,int) = &max // &可以省略
  10. int a, b, c, d;
  11. }

回调函数

:::info

函数指针作为某个函数的参数

函数指针变量可以作为某个函数的参数来使用的
回调函数就是一个通过函数指针调用的函数。
简单讲:回调函数是由别人的函数执行时调用你实现的函数。
以下是来自知乎作者常溪玲的解说:
你到一个商店买东西,刚好你要的东西没有货,于是你在店员那里留下了你的电话,过了几天店里有货了,店员就打了你的电话,然后你接到电话后就到店里去取了货。在这个例子里,你的电话号码就叫回调函数,你把电话留给店员就叫登记回调函数,店里后来有货了叫做触发了回调关联的事件,店员给你打电话叫做调用回调函数,你到店里去取货叫做响应回调事件。 :::

  1. #include <stdlib.h>
  2. #include <stdio.h>
  3. /**
  4. 定义三个参数
  5. 指针数组、 数组长度、 指针函数
  6. */
  7. void populate_array(int *array, size_t arraySize, int (*getNextValue)(void) )
  8. {
  9. for(size_t i = 0; i < arraySize; i++)
  10. array[i] = getNextValue();
  11. }
  12. /**
  13. 定义一个返回随机数的函数
  14. */
  15. int getNextRandomValue(void)
  16. {
  17. return rand();
  18. }
  19. /*主函数入口*/
  20. int main(void)
  21. {
  22. int myArray[10];
  23. /*
  24. getNextRandomValue 不能加括号,否则无法编译,
  25. 因为加上括号之后
  26. 相当于传入此参数时传入了 int
  27. 而不是函数指针
  28. */
  29. //调用函数、传入定义的数组、长度、 以及函数名
  30. populate_array(myArray, 10, getNextRandomValue);
  31. for (int i =0; i < 10; i++) {
  32. printf("%d ", myarray[i]);
  33. }
  34. printf("\n");
  35. return 0;
  36. }

:::info 指针总结

//定义普通指针
int *p ;
int (p1[5]) 等于 int p1[5]
int (*p2[5] //二维数组指针,不能去掉括号

指针数组和二维数组指针有着本质上的区别:
指针数组是一个数组,只是每个元素保存的都是指针,
以上面的 p1 为例,在32位环境下它占用 4×5 = 20 个字节的内存。
二维数组指针是一个指针,它指向一个二维数组,
以上面的 p2 为例,它占用 4 个字节的内存。 :::

指针总结

  1. //定义整数类型 var , ptr指针, ptr2指针的指针
  2. int var, *ptr, **ptr2;
  3. var = 60;
  4. //不带 &地址符号的情况下 ,var 60 直接赋值给 指针prt
  5. ptr = var;
  6. printf("%d, %p\n", ptr); //60
  7. printf("%d, %p\n", &(*ptr)); //60
  8. printf("%d, %p\n", &(var)); //0x7ffe525ed4fc
  9. printf("%d, %p\n", &(ptr)); //0x7ffe525ed500
  10. //带 & 地址符,地址引用赋值给 prt
  11. ptr = &var; 是直接赋值
  12. printf("%d, %p, %p \n", *ptr, &var, ptr);
  13. //60, 0x7ffdec340a4c,0x7ffdec340a4c

int i ;
*p ,
_j; // _ 是指针 是指向指针的指针
p = &i; //把 p 指向 i 的地址
*j = &p; // 把 指针p 指向 给 指针 j

输出 &i 是 i 的内存地址
输出 p 是 等同于 i 的内存地址
输出 *p 是 i 的值 6
输出 &(*p) 是 等同于 i 的内存地址

输出 J 是 新的内存地址
输出 &(J) 是 i 的内存地址
输出 j 是 i 的值
输出 p 就是值 &(p) 就是地址
我们可以把*p看成一个变量,这个变量指向谁就等价于谁
如:&i的值和&(*p)的值是相同的,说明i和指针*p是等价的。