C语言快速回顾(二)
前言
在Android音视频开发中,网上知识点过于零碎,自学起来难度非常大,不过音视频大牛Jhuster提出了《Android 音视频从入门到提高 - 任务列表》,结合我自己的工作学习经历,我准备写一个音视频系列blog。C/C++是音视频必备编程语言,我准备用几篇文章来快速回顾C语言。本文是音视频系列blog的其中一个, 对应的要学习的内容是:快速回顾C语言的字符串,指针,指针和数组,预处理器。
音视频系列blog
音视频系列blog: 点击此处跳转查看
目录
1 字符串
1.1 字符串字面量
C语言字符串字面量是指直接在代码中写入的字符串,通常用双引号 "
括起来的字符序列。字符串字面量在C语言中用于表示文本数据。
例如,以下是一些字符串字面量的示例:
"This is a string literal."
"Hello, World!"
"12345"
"Special characters: !@#$%^&*"
需要注意的是,字符串字面量在存储时会自动在末尾添加一个空字符 '\0'
,用于表示字符串的结束。因此,字符串字面量的长度比其包含的字符数量多一个。
你可以将字符串字面量赋值给字符数组或字符指针变量,如下所示:
char str1[] = "Hello, World!"; // 字符数组
char *str2 = "This is a string."; // 字符指针
在上面的示例中,str1
是一个字符数组,会自动分配足够的空间来存储字符串 “Hello, World!”,并自动添加空字符。str2
是一个字符指针,它指向存储字符串 “This is a string.” 的位置。注意,str2
指向的是一个字符串字面量,这意味着该字符串的内容是固定的,不能直接修改。
字符串字面量在C语言中广泛使用,用于表示文本、消息、错误信息等。它们在输入输出、赋值、函数调用等情境中都是常见的。
1.2 字符串变量
C语言中的字符串变量通常是字符数组或字符指针,用于存储和处理字符串数据。字符串变量允许你在程序中操作文本和字符数据。
以下是使用字符数组和字符指针来创建字符串变量的示例:
-
字符数组(Character Array): 字符数组是一种固定大小的数组,用于存储字符串。可以初始化为一个字符串字面量,也可以逐个字符赋值。
char str1[20] = "Hello, World!"; // 初始化为字符串 char str2[10]; // 声明一个字符数组 strcpy(str2, "Welcome"); // 复制字符串到字符数组
-
字符指针(Character Pointer): 字符指针指向字符串的首字符,可以指向字符数组或字符串字面量。
char *ptr1 = "Hello, World!"; // 指向字符串字面量 char *ptr2; // 声明一个字符指针 ptr2 = "Welcome"; // 指向字符串字面量
字符串变量的操作包括初始化、赋值、连接、拷贝、比较等。你可以使用字符串库函数来处理这些操作。例如:
#include <stdio.h>
#include <string.h>int main() {char str1[20] = "Hello";char str2[20] = "World";// 字符串连接strcat(str1, " ");strcat(str1, str2);// 字符串长度printf("Length of str1: %lu\n", strlen(str1));// 字符串比较if (strcmp(str1, "Hello World") == 0) {printf("Strings are equal.\n");} else {printf("Strings are not equal.\n");}return 0;
}
在上面的示例中,我们演示了如何初始化字符数组、使用字符指针,以及如何使用字符串库函数来连接字符串、计算字符串长度和比较字符串。
无论使用字符数组还是字符指针,字符串变量都允许你在C程序中处理文本数据,进行各种操作和处理。
1.3 字符串的读和写
在C语言中,可以使用标准库函数来读取和写入字符串。主要的函数是 printf()
和 scanf()
用于输出和输入字符串,以及 gets()
和 fgets()
用于从用户输入中读取字符串。
以下是这些函数的使用示例:
-
输出字符串: 使用
printf()
函数输出字符串。#include <stdio.h>int main() {char str[] = "Hello, World!";printf("String: %s\n", str);return 0; }
-
输入字符串: 使用
scanf()
函数输入字符串。但是,scanf()
在读取字符串时可能会有问题,因为它会在遇到空格、制表符或换行符时停止读取。#include <stdio.h>int main() {char str[100];printf("Enter a string: ");scanf("%s", str);printf("You entered: %s\n", str);return 0; }
-
安全读取字符串: 为了安全地读取字符串,推荐使用
fgets()
函数。#include <stdio.h>int main() {char str[100];printf("Enter a string: ");fgets(str, sizeof(str), stdin);printf("You entered: %s", str);return 0; }
-
从文件中读取字符串: 使用
fgets()
函数从文件中读取字符串。#include <stdio.h>int main() {char str[100];FILE *file = fopen("text.txt", "r");if (file) {fgets(str, sizeof(str), file);printf("Read from file: %s", str);fclose(file);} else {printf("Failed to open file.\n");}return 0; }
在使用这些函数时,请注意字符数组的大小,以避免缓冲区溢出。特别是使用 scanf()
时要小心,它可能会导致缓冲区溢出问题。推荐使用 fgets()
进行安全的字符串输入。
需要注意的是,C语言中没有内置的字符串类型,字符串实际上是以字符数组或字符指针的形式表示的。这使得字符串处理在某些情况下需要特别小心,以确保内存和缓冲区的正确管理。
1.4 访问字符串中的字符
在C语言中,可以使用下标(索引)来访问字符串中的单个字符。C语言中的字符串实际上是字符数组,每个字符都是数组中的一个元素。字符串中的字符使用从0开始的索引进行访问。
以下是访问字符串中字符的示例:
#include <stdio.h>int main() {char str[] = "Hello, World!";// 使用下标访问字符串中的字符printf("Character at index 0: %c\n", str[0]);printf("Character at index 7: %c\n", str[7]);return 0;
}
在上述示例中,我们使用 str[0]
访问了字符串的第一个字符(H),使用 str[7]
访问了字符串的第八个字符(W)。
需要注意的是,C语言的字符串以空字符 '\0'
结尾,表示字符串的结束。你可以使用循环来遍历字符串中的每个字符,直到遇到空字符为止。
例如,下面的示例演示了如何遍历整个字符串并打印每个字符:
#include <stdio.h>int main() {char str[] = "Hello";// 遍历字符串并打印每个字符for (int i = 0; str[i] != '\0'; i++) {printf("%c ", str[i]);}return 0;
}
在上述示例中,循环遍历字符串中的每个字符,直到遇到空字符为止。这样可以逐个打印字符串中的字符。
1.5 使用C语言的字符串库
C语言的标准库(也称为C标准库或C库)提供了许多用于处理字符串的函数。这些函数被定义在头文件 <string.h>
中,你可以通过包含该头文件来使用这些函数。以下是一些常见的C语言字符串库函数:
-
strlen()
: 计算字符串的长度(不包括空字符)。size_t strlen(const char *str);
-
strcpy()
: 复制一个字符串到另一个字符串。char *strcpy(char *dest, const char *src);
-
strncpy()
: 从源字符串复制指定数量的字符到目标字符串。char *strncpy(char *dest, const char *src, size_t n);
-
strcat()
: 连接两个字符串,将一个字符串附加到另一个字符串的末尾。char *strcat(char *dest, const char *src);
-
strncat()
: 将指定数量的字符从源字符串附加到目标字符串。char *strncat(char *dest, const char *src, size_t n);
-
strcmp()
: 比较两个字符串,返回一个整数表示比较结果。int strcmp(const char *str1, const char *str2);
-
strncmp()
: 比较两个字符串的指定数量字符,返回一个整数表示比较结果。int strncmp(const char *str1, const char *str2, size_t n);
-
strstr()
: 在一个字符串中查找另一个子字符串,返回子字符串的第一个匹配位置。char *strstr(const char *haystack, const char *needle);
-
strchr()
: 在字符串中查找指定字符的第一个匹配位置。char *strchr(const char *str, int c);
-
strtok()
: 分割字符串为多个子字符串,使用指定的分隔符。
char *strtok(char *str, const char *delimiters);
-
sprintf()
: 将格式化的数据写入字符串。int sprintf(char *str, const char *format, ...);
-
sscanf()
: 从字符串中按照指定格式读取数据。int sscanf(const char *str, const char *format, ...);
-
memset()
: 将指定值设置给一段内存。void *memset(void *s, int c, size_t n);
-
memcpy()
: 复制一段内存内容到另一段内存。void *memcpy(void *dest, const void *src, size_t n);
-
memmove()
: 安全地复制一段内存内容到另一段内存,避免重叠问题。void *memmove(void *dest, const void *src, size_t n);
这些函数是C语言字符串操作的基础工具,它们使得处理字符串变得更加简单和高效。可以根据需要使用这些函数来完成不同的字符串操作任务。
1.6 字符串常见用法
C语言字符串在编程中有许多常见的用法,涵盖了从字符串操作到字符串输入输出的多个方面。以下是一些常见的C语言字符串用法示例:
-
字符串赋值和初始化: 可以使用字符数组或字符指针来初始化字符串变量,也可以将字符串字面量赋值给字符串变量。
char str1[] = "Hello, World!"; // 使用字符数组初始化 char *str2 = "Welcome"; // 使用字符指针初始化 char str3[20]; // 未初始化的字符数组 strcpy(str3, "C programming"); // 复制字符串到字符数组
-
字符串连接: 使用
strcat()
函数将一个字符串连接到另一个字符串的末尾。char str1[20] = "Hello"; char str2[] = " World!"; strcat(str1, str2); // 连接字符串
-
字符串长度: 使用
strlen()
函数计算字符串的长度(不包括空字符)。char str[] = "Hello"; int length = strlen(str); // 计算字符串长度
-
字符串比较: 使用
strcmp()
函数比较两个字符串是否相等。char str1[] = "Hello"; char str2[] = "World"; int result = strcmp(str1, str2); // 比较字符串
-
字符串输入输出: 使用
printf()
输出字符串,使用scanf()
或fgets()
输入字符串。char str[] = "C programming"; printf("String: %s\n", str); // 输出字符串char input[50]; printf("Enter a string: "); scanf("%s", input); // 使用 scanf 输入字符串char buffer[100]; printf("Enter a string: "); fgets(buffer, sizeof(buffer), stdin); // 使用 fgets 安全输入字符串
-
遍历字符串: 使用循环遍历字符串中的每个字符。
char str[] = "Hello"; for (int i = 0; str[i] != '\0'; i++) {printf("%c ", str[i]); }
-
将字符串转换为数字: 使用
atoi()
或strtol()
将字符串转换为整数,使用atof()
将字符串转换为浮点数。char numStr[] = "12345"; int num = atoi(numStr); // 转换为整数
-
从字符串中提取子字符串: 使用
strtok()
函数将字符串分割为子字符串。char str[] = "apple,banana,cherry"; char *token = strtok(str, ","); while (token != NULL) {printf("%s\n", token);token = strtok(NULL, ","); }
这些只是C语言字符串的一些常见用法示例。字符串在C编程中扮演着重要角色,用于处理文本和字符数据,进行各种操作和处理。
1.7 字符串数组
C语言字符串数组是一种数组,其中的每个元素都是字符串(字符数组)。字符串数组允许你同时存储和操作多个字符串。每个字符串都是以字符数组的形式存储,其中的每个字符占据一个数组元素的位置,以空字符 '\0'
结尾表示字符串的结束。
以下是声明和初始化字符串数组的基本语法:
char string_array[num_strings][max_length];
在上述语法中,num_strings
是字符串数组中的字符串数量,max_length
是每个字符串的最大长度(包括空字符 '\0'
)。
以下是一个示例,展示如何声明、初始化和访问字符串数组:
#include <stdio.h>int main() {char names[3][20] = {"Alice","Bob","Charlie"};// 输出字符串数组for (int i = 0; i < 3; i++) {printf("Name %d: %s\n", i + 1, names[i]);}return 0;
}
在这个示例中,我们声明了一个包含3个字符串,每个字符串最大长度为20的字符串数组。然后通过循环遍历输出了字符串数组的内容。
字符串数组非常适用于存储和处理一组相关的字符串,例如名字列表、词汇表等。与一维字符数组不同,字符串数组允许你一次存储和处理多个字符串,使代码更加模块化和易于维护。
2 指针
2.1 指针定义与使用
C语言中的指针是一种变量,用于存储内存地址。指针可以指向不同类型的数据,例如整数、字符、浮点数、数组、结构体等。指针允许你直接访问内存中的数据,进行动态内存分配、传递函数参数等操作。
以下是指针的定义和使用示例:
-
定义指针: 指针变量存储某个变量的内存地址。
int x = 10; // 整数变量 int *ptr; // 整数指针变量 ptr = &x; // 指针指向 x 的地址
-
访问指针指向的值: 使用解引用运算符
*
可以访问指针所指向的值。int value = *ptr; // 获取指针指向的值 printf("Value: %d\n", value);
-
修改指针指向的值: 通过指针可以修改所指向内存的值。
*ptr = 20; // 修改指针指向的值 printf("New value: %d\n", x);
-
指针的算术运算: 指针可以进行加法、减法等运算,以在内存中移动。
int arr[5] = {1, 2, 3, 4, 5}; int *arrPtr = arr; // 指向数组的第一个元素 arrPtr++; // 指向下一个元素
-
指针和函数: 可以将指针作为参数传递给函数,以在函数中修改变量的值。
void modifyValue(int *ptr) {*ptr = 100; // 修改指针指向的值 }int main() {int num = 50;modifyValue(&num);printf("Modified value: %d\n", num);return 0; }
-
动态内存分配: 使用
malloc()
函数动态分配内存,返回一个指向分配内存的指针。int *dynamicPtr = (int *)malloc(sizeof(int)); *dynamicPtr = 100; free(dynamicPtr); // 释放动态分配的内存
-
指向数组: 数组名本身就是指向数组第一个元素的指针。
int arr[3] = {1, 2, 3}; int *arrPtr = arr; // 指向数组的第一个元素
-
指向字符串: 字符串可以使用字符指针或字符数组来表示。
char *str = "Hello"; // 字符指针 char strArr[] = "World"; // 字符数组
指针在C语言中非常重要,它提供了对内存的底层访问,可以实现灵活的数据处理和操作。同时,指针也需要小心使用,避免野指针(指向无效内存)和内存泄漏等问题。
2.2 指针大小,野指针和空指针
在C语言中,指针是一种变量,用于存储内存地址。指针的大小取决于编译器和系统的架构,通常在32位系统上为4字节,在64位系统上为8字节。这表示指针变量本身存储了一个内存地址,该地址指向存储的数据。
-
野指针(Wild Pointer): 野指针是指没有正确初始化的指针,或者指向无效内存地址的指针。使用野指针可能导致程序崩溃或产生不可预测的结果。
int *wildPtr; // 野指针,未初始化 int x; int *wildPtr2 = &x; // 野指针,指向未分配的内存
-
空指针(Null Pointer): 空指针是指不指向任何有效内存地址的指针。在C语言中,空指针通常用宏
NULL
表示,它的值为0。int *nullPtr = NULL; // 空指针
空指针可以用于表示指针变量没有有效值,也可以用于初始化指针变量。
int *ptr = NULL; if (ptr == NULL) {printf("Pointer is NULL\n"); }
正确使用指针是很重要的,应该始终初始化指针并确保它指向有效的内存。避免使用野指针,对于未初始化的指针,最好将其设置为NULL。
2.3 泛型指针
在C语言中,泛型指针(Generic Pointer)通常指的是 void
指针,它是一种通用的指针类型,可以指向任何数据类型。void
指针可以存储任何类型的内存地址,但它不知道所指向的内存内容的数据类型,因此在使用时需要进行类型转换。
以下是泛型指针的基本用法示例:
#include <stdio.h>int main() {int x = 10;float y = 3.14;char ch = 'A';// 使用 void 指针存储不同类型的地址void *ptr;ptr = &x;printf("Value at int pointer: %d\n", *(int *)ptr);ptr = &y;printf("Value at float pointer: %f\n", *(float *)ptr);ptr = &ch;printf("Value at char pointer: %c\n", *(char *)ptr);return 0;
}
在上述示例中,我们声明了一个 void
指针 ptr
,然后将它分别指向不同类型的变量(整数、浮点数、字符)。在使用 printf
时,我们需要使用类型转换将 void
指针转换为正确的指针类型,并使用解引用操作符 *
获取所指向内存的值。
请注意,使用 void
指针需要格外小心,因为它无法提供编译时的类型检查,容易导致类型不匹配的错误。在进行类型转换时,务必确保转换的类型与实际内存中的数据类型匹配,以避免未定义的行为和错误。
2.4 指针类型以及使用
在C语言中,指针类型是指指针所指向的数据类型。指针类型决定了指针的操作和解引用方式。以下是C语言中常见的一些指针类型以及如何使用它们:
-
整型指针(int pointer): 指向整数类型的指针。
int x = 10; int *ptr; // 声明整型指针 ptr = &x; // 指针指向整数变量 x 的地址
解引用整型指针:
int value = *ptr; // 获取指针所指向的整数值
-
字符型指针(char pointer): 指向字符类型的指针。
char ch = 'A'; char *chPtr; // 声明字符型指针 chPtr = &ch; // 指针指向字符变量 ch 的地址
解引用字符型指针:
char character = *chPtr; // 获取指针所指向的字符值
-
浮点型指针(float pointer): 指向浮点数类型的指针。
float y = 3.14; float *fPtr; // 声明浮点型指针 fPtr = &y; // 指针指向浮点数变量 y 的地址
解引用浮点型指针:
float number = *fPtr; // 获取指针所指向的浮点数值
-
指向指针的指针(pointer to pointer): 指向另一个指针的指针。
int x = 10; int *ptr1 = &x; int **ptrPtr; // 声明指向指针的指针 ptrPtr = &ptr1; // 指向指针变量 ptr1 的地址
解引用指向指针的指针:
int value = **ptrPtr; // 获取指向指针的指针所指向的整数值
-
数组指针(array pointer): 指向数组的指针。
int arr[5] = {1, 2, 3, 4, 5}; int *arrPtr; // 声明数组指针 arrPtr = arr; // 指向数组的第一个元素
使用数组指针来遍历数组:
for (int i = 0; i < 5; i++) {printf("%d ", *(arrPtr + i)); // 输出数组元素 }
-
函数指针(function pointer): 指向函数的指针,可以用于调用函数。
int add(int a, int b) {return a + b; }int (*funcPtr)(int, int); // 声明函数指针 funcPtr = add; // 指向函数 add int result = funcPtr(3, 4); // 通过函数指针调用函数
-
void
指针(void pointer): 通用指针,可以指向任何数据类型,但需要进行强制类型转换后才能使用。int x = 10; float y = 3.14; void *ptr; // 声明 void 指针 ptr = &x; // 指向整数变量 x 的地址 ptr = &y; // 重新指向浮点数变量 y 的地址
指针类型是C语言中非常重要的概念,它们允许你在程序中操作内存中的数据。不同类型的指针允许你处理不同类型的数据,同时也可以用于传递参数、动态内存分配、数据结构等操作。需要小心使用指针,避免出现指针错误(如空指针、野指针等),并始终确保指针指向有效的内存位置。
2.5 指向指针的指针
上面提到过这个指向指针的指针,现在再详细的介绍一下。C语言中的指向指针的指针(Pointer to Pointer)是一种非常有用的概念,它允许你在程序中操作指针变量的指针。指向指针的指针通常用于多级间接访问,例如动态数组、多维数组、链表等数据结构。
以下是指向指针的指针的基本用法示例:
#include <stdio.h>int main() {int x = 10;int *ptr1 = &x; // 指向整数的指针int **ptr2 = &ptr1; // 指向指针的指针printf("Value of x: %d\n", x);printf("Value at ptr1: %d\n", *ptr1);printf("Value at ptr2 (using double indirection): %d\n", **ptr2);return 0;
}
在上述示例中,我们首先声明一个整数变量 x
和一个指向整数的指针 ptr1
,然后声明一个指向指针的指针 ptr2
,将其指向 ptr1
。通过不同级别的间接访问,我们可以访问到 x
的值。
指向指针的指针特别在以下情况下很有用:
- 多级间接访问: 可以通过多级间接访问来访问嵌套的数据结构,如链表中的节点。
- 动态数组: 在动态内存分配时,使用指向指针的指针来管理数组。
- 多维数组: 用于表示和操作多维数组,如指向指针的指针数组。
- 传递指针: 可以在函数间传递指向指针的指针,以实现对指针变量的修改。
以下是一个示例,演示如何使用指向指针的指针来创建一个动态二维数组:
#include <stdio.h>
#include <stdlib.h>int main() {int rows = 3, cols = 4;// 动态分配二维数组int **matrix = (int **)malloc(rows * sizeof(int *));for (int i = 0; i < rows; i++) {matrix[i] = (int *)malloc(cols * sizeof(int));}// 初始化并输出二维数组for (int i = 0; i < rows; i++) {for (int j = 0; j < cols; j++) {matrix[i][j] = i * cols + j;printf("%2d ", matrix[i][j]);}printf("\n");}// 释放内存for (int i = 0; i < rows; i++) {free(matrix[i]);}free(matrix);return 0;
}
在这个示例中,我们使用指向指针的指针来创建一个动态二维数组,然后对其进行初始化和输出,最后释放内存。指向指针的指针让我们能够动态创建多维数据结构,非常有用。
2.6 指针作为参数与指针作为返回值
上面简单的提到过指针作为参数,下面详细的总结一下。在C语言中,指针既可以作为函数的参数,也可以作为函数的返回值。这些用法使得可以在函数间传递指针,实现对变量的修改,以及在函数内部动态分配内存并返回指向该内存的指针。
以下是指针作为参数和作为返回值的示例:
-
指针作为参数: 可以将指针作为参数传递给函数,这允许在函数内部修改指针所指向的数据。
#include <stdio.h>void modifyPointer(int *ptr) {*ptr = 100; // 修改指针所指向的值 }int main() {int num = 50;printf("Before: %d\n", num);modifyPointer(&num);printf("After: %d\n", num);return 0; }
-
指针作为返回值: 函数可以返回指针,通常用于动态分配内存,并返回指向该内存的指针。
#include <stdio.h> #include <stdlib.h>int *createArray(int size) {int *arr = (int *)malloc(size * sizeof(int));for (int i = 0; i < size; i++) {arr[i] = i + 1;}return arr; }int main() {int *arr = createArray(5);for (int i = 0; i < 5; i++) {printf("%d ", arr[i]);}free(arr); // 释放动态分配的内存return 0; }
在上述示例中,modifyPointer
函数接受一个指针作为参数,通过解引用修改了指针所指向的值。createArray
函数动态分配了一个整数数组,并返回指向该数组的指针。
指针作为参数和作为返回值使得函数能够更灵活地处理数据,并在需要时进行修改或动态分配内存。但是在使用指针时,需要小心避免野指针、内存泄漏和越界访问等问题。
3 指针和数组
3.1 指针的算术运算
C语言中,指针的算术运算(Pointer Arithmetic)允许你对指针进行加法、减法等运算,以便在内存中移动指针位置。这在处理数组、字符串和数据结构等情况下非常有用。
以下是指针的算术运算示例:
#include <stdio.h>int main() {int arr[] = {10, 20, 30, 40, 50};int *ptr = arr; // 指向数组的第一个元素printf("Array elements: ");for (int i = 0; i < 5; i++) {printf("%d ", *(ptr + i)); // 输出数组元素}printf("\n");// 指针加法ptr = ptr + 2; // 指向第三个元素printf("Third element: %d\n", *ptr);// 指针减法ptr = ptr - 1; // 回到第二个元素printf("Second element: %d\n", *ptr);return 0;
}
在上述示例中,我们首先声明了一个整数数组 arr
,然后将一个指向数组第一个元素的指针 ptr
初始化为数组的起始地址。使用指针的算术运算,我们可以遍历数组元素并进行加法、减法等操作。
指针算术的规则:
- 指针加法和减法的结果是一个新的指针,指向移动后的内存位置。
- 指针加法会根据指针所指向数据类型的大小移动指针位置。例如,整型指针加1,实际上会移动4字节(在32位系统上)或8字节(在64位系统上)。
- 指针减法会倒退指针位置,也遵循相同的数据类型大小。
- 指针可以与整数值进行加法和减法运算,这将会移动指针多个单位。
需要注意的是,指针算术要小心避免越界访问,确保在合法范围内进行操作,以避免访问无效内存。
3.2 指针用于数组处理
C语言中,指针在数组处理中扮演着非常重要的角色,可以通过指针来访问和操作数组的元素,以及实现动态内存分配和释放。以下是指针在数组处理中的一些常见用法:
-
遍历数组元素: 可以使用指针来遍历数组的元素,以便访问和操作数组中的数据。
#include <stdio.h>int main() {int arr[] = {10, 20, 30, 40, 50};int *ptr = arr; // 指向数组的第一个元素for (int i = 0; i < 5; i++) {printf("%d ", *(ptr + i)); // 输出数组元素}printf("\n");return 0; }
-
传递数组给函数: 可以将数组传递给函数,并在函数内部使用指针来操作数组。
#include <stdio.h>void printArray(int *arr, int size) {for (int i = 0; i < size; i++) {printf("%d ", arr[i]);}printf("\n"); }int main() {int arr[] = {10, 20, 30, 40, 50};int size = sizeof(arr) / sizeof(arr[0]);printArray(arr, size);return 0; }
-
动态内存分配: 使用指针和动态内存分配函数(如
malloc
)可以动态创建数组。#include <stdio.h> #include <stdlib.h>int main() {int size;printf("Enter the size of the array: ");scanf("%d", &size);int *arr = (int *)malloc(size * sizeof(int));if (arr == NULL) {printf("Memory allocation failed.\n");return 1;}for (int i = 0; i < size; i++) {arr[i] = i + 1;}for (int i = 0; i < size; i++) {printf("%d ", arr[i]);}printf("\n");free(arr);return 0; }
-
多维数组处理: 指针可以用于处理多维数组,可以通过适当的指针算术来访问多维数组的元素。
#include <stdio.h>int main() {int matrix[3][4] = {{1, 2, 3, 4},{5, 6, 7, 8},{9, 10, 11, 12}};int *ptr = &matrix[0][0];for (int i = 0; i < 3; i++) {for (int j = 0; j < 4; j++) {printf("%2d ", *(ptr + i * 4 + j));}printf("\n");}return 0; }
指针在数组处理中具有灵活性和效率,它允许你直接访问数组元素并进行操作,同时也能够处理动态内存分配和多维数组。然而,在使用指针处理数组时,需要小心越界访问和内存泄漏等问题。
3.3 用数组名作为指针
在C语言中,数组名可以被视为指向数组第一个元素的指针。这是一种方便的用法,允许你通过数组名来访问数组元素,或者将数组名传递给函数来操作数组。
以下是使用数组名作为指针的示例:
-
访问数组元素: 可以使用数组名加索引的方式来访问数组元素。
#include <stdio.h>int main() {int arr[] = {10, 20, 30, 40, 50};printf("First element: %d\n", arr[0]);printf("Second element: %d\n", arr[1]);return 0; }
-
传递数组给函数: 可以将数组名作为指针参数传递给函数,并在函数内部操作数组。
#include <stdio.h>void printArray(int *arr, int size) {for (int i = 0; i < size; i++) {printf("%d ", arr[i]);}printf("\n"); }int main() {int arr[] = {10, 20, 30, 40, 50};int size = sizeof(arr) / sizeof(arr[0]);printArray(arr, size);return 0; }
-
数组指针算术: 可以使用数组名作为指针,并进行指针算术来遍历数组元素。
#include <stdio.h>int main() {int arr[] = {10, 20, 30, 40, 50};int *ptr = arr; // 数组名作为指针for (int i = 0; i < 5; i++) {printf("%d ", *(ptr + i));}printf("\n");return 0; }
需要注意的是,虽然数组名可以被视为指针,但是数组名并不是真正的指针变量。数组名的值是数组的首地址,但是数组名不能进行赋值和修改。例如,以下代码是无效的:
int arr[] = {10, 20, 30, 40, 50};
int *ptr = arr; // 有效
arr = ptr; // 无效,数组名不能被重新赋值
使用数组名作为指针可以简化代码并提高可读性,但也要注意在使用时避免越界访问和其他指针相关的问题。
3.4 指针和多维数组
C语言中,指针在处理多维数组时发挥了重要作用。多维数组实际上是数组的数组,因此在处理多维数组时,可以使用指针来访问和操作数组的元素。以下是指针在多维数组处理中的一些常见用法:
-
使用指针遍历多维数组: 可以使用指针来遍历多维数组的元素,通过适当的指针算术来访问数组的不同维度。
#include <stdio.h>int main() {int matrix[3][4] = {{1, 2, 3, 4},{5, 6, 7, 8},{9, 10, 11, 12}};int *ptr = &matrix[0][0]; // 指向数组的第一个元素for (int i = 0; i < 3; i++) {for (int j = 0; j < 4; j++) {printf("%2d ", *(ptr + i * 4 + j)); // 使用指针算术访问元素}printf("\n");}return 0; }
-
传递多维数组给函数: 可以将多维数组传递给函数,并在函数内部使用指针来操作数组。
#include <stdio.h>void printMatrix(int (*matrix)[4], int rows, int cols) {for (int i = 0; i < rows; i++) {for (int j = 0; j < cols; j++) {printf("%2d ", matrix[i][j]);}printf("\n");} }int main() {int matrix[3][4] = {{1, 2, 3, 4},{5, 6, 7, 8},{9, 10, 11, 12}};printMatrix(matrix, 3, 4);return 0; }
在传递多维数组给函数时,需要使用指针来声明函数参数,以便正确传递数组的维度信息。
需要注意的是,多维数组的内存布局是连续的,但在C语言中多维数组与指针的关系是复杂的。例如,二维数组可以被视为指向一维数组的指针数组,但它不是真正的指针数组。在处理多维数组时,要小心越界访问和内存布局问题。
4 预处理器
4.1 预处理器的工作原理
C语言预处理器是C编译器的一个重要组成部分,它在实际的编译过程之前对源代码进行预处理,执行一系列的文本替换和宏展开操作,以及处理条件编译等任务。预处理器的主要任务是对源代码进行预处理,生成经过处理的中间代码,然后再由编译器进一步处理生成目标代码。
预处理器的工作原理如下:
-
文本替换和宏展开: 预处理器会根据预处理指令对源代码进行文本替换和宏展开。例如,通过
#define
指令定义的宏会被展开为相应的文本。#define PI 3.14159 float area = PI * radius * radius;
在上述代码中,预处理器会将
PI
宏展开为3.14159
,生成实际的表达式。 -
头文件包含: 预处理器可以通过
#include
指令将外部文件(头文件)的内容插入到源代码中。这有助于模块化和代码重用。#include <stdio.h> int main() {printf("Hello, World!\n");return 0; }
在上述代码中,
#include <stdio.h>
会将标准输入输出库的内容插入到源代码中。 -
条件编译: 预处理器可以根据条件指令,如
#ifdef
、#ifndef
、#if
、#else
、#elif
和#endif
,在编译时决定是否包含某些代码块。#define DEBUG 1 #ifdef DEBUGprintf("Debug mode is enabled.\n"); #endif
在上述代码中,如果
DEBUG
宏被定义,那么printf
语句会被包含在编译中。 -
其他预处理指令: 预处理器还支持其他指令,如
#undef
(取消宏定义)、#line
(设置行号和文件名)、#error
(产生错误消息)、#pragma
(编译器指示)等。#line 42 "mycode.c" #error This is an error message. #pragma warning(disable: 1234)
预处理器在编译之前处理源代码,将源代码转换为经过宏展开和文本替换后的中间代码。这些经过处理的中间代码会交给编译器进行实际的编译,生成目标代码和最终的可执行文件。这种分阶段的处理使得C语言具有灵活性和可维护性。
4.2 预处理指令
C语言预处理器指令是在编译过程之前对源代码进行预处理的指令,用于进行文本替换、宏展开、条件编译等操作。以下是一些常用的C语言预处理指令:
-
#define: 定义宏,用于进行简单的文本替换和宏展开。
#define PI 3.14159
-
#include: 包含外部文件的内容,通常用于包含头文件。
#include <stdio.h>
-
#ifdef / #ifndef / #endif: 条件编译,根据条件是否定义了宏来决定是否编译代码块。
#ifdef DEBUGprintf("Debug mode is enabled.\n"); #endif
-
#if / #elif / #else: 在条件为真时编译代码块,类似于if语句。
#if defined(X) && (Y > 0)// code to be compiled if X is defined and Y is positive #elif defined(Z)// code to be compiled if Z is defined #else// code to be compiled if none of the above conditions are true #endif
-
#undef: 取消宏定义。
#undef PI
-
#pragma: 发送编译器特定的指示,如关闭警告、设定对齐方式等。
#pragma warning(disable: 1234)
-
#error: 产生编译错误并输出自定义错误消息。
#ifdef DEBUG#error Debug mode is not supported in this version. #endif
-
#line: 设置行号和文件名,用于调试和错误报告。
#line 42 "mycode.c"
这些预处理指令在编译之前会被预处理器处理,将源代码中的宏展开、文件包含、条件编译等操作转换成中间代码,然后再由编译器进行实际的编译。预处理器指令使得C语言具有更高的灵活性和可维护性,能够根据不同的需求来进行定制化的编译过程。
4.3 宏定义
C语言宏定义是一种预处理器指令,用于在源代码中进行文本替换和宏展开,以便在编译阶段生成相应的代码。宏定义可以用于定义常量、函数宏、条件编译等,以提高代码的可读性和维护性。宏定义使用#define
关键字进行定义。
以下是一些常见的C语言宏定义用法:
-
定义常量: 使用宏定义来创建常量,以便在代码中使用。
#define PI 3.14159 #define MAX_SIZE 100
-
定义函数宏: 创建函数宏来实现简单的代码替换。
#define SQUARE(x) ((x) * (x))
-
带参数的函数宏: 创建带参数的函数宏,可以根据传入的参数进行展开。
#define MAX(a, b) ((a) > (b) ? (a) : (b))
-
条件编译: 使用宏定义来进行条件编译,根据不同的宏定义决定是否编译某部分代码。
#define DEBUG #ifdef DEBUGprintf("Debug mode is enabled.\n"); #endif
-
宏与字符串: 可以使用宏定义来创建字符串常量。
#define MESSAGE "Hello, World!"
-
多行宏: 可以使用反斜杠
\
在多行上定义宏。#define ADD(a, b) \do { \printf("Adding %d and %d\n", a, b); \(a) + (b); \} while (0)
-
取消宏定义: 可以使用
#undef
取消已定义的宏。#undef PI
宏定义在预处理阶段被展开为实际的代码,这意味着宏定义并不是在编译阶段进行类型检查或其他语法分析,而是简单的文本替换。因此,在使用宏定义时要小心确保正确的使用方式,避免由于展开引发意想不到的问题。
4.4 条件编译
C语言条件编译是一种预处理器功能,允许你根据预定义的宏或条件来选择性地编译代码块。条件编译在不同平台、不同编译选项或不同情况下可以选择性地包含或排除代码,以便在不同环境中实现代码的灵活性和可移植性。
条件编译的关键指令包括:#ifdef
、#ifndef
、#else
、#elif
和 #endif
。
以下是条件编译的一些用法示例:
-
#ifdef
和#endif
: 如果某个宏已经定义,则编译下面的代码块。#define DEBUG #ifdef DEBUGprintf("Debug mode is enabled.\n"); #endif
-
#ifndef
和#endif
: 如果某个宏未定义,则编译下面的代码块。#ifndef RELEASEprintf("This is not a release version.\n"); #endif
-
#else
: 如果前面的条件不成立,则编译下面的代码块。#ifdef DEBUGprintf("Debug mode is enabled.\n"); #elseprintf("Debug mode is not enabled.\n"); #endif
-
#elif
: 与#else
类似,但可以用于在多个条件之间进行选择。#ifdef DEBUGprintf("Debug mode is enabled.\n"); #elif defined(TESTING)printf("Testing mode is enabled.\n"); #elseprintf("No special mode is enabled.\n"); #endif
条件编译的主要目的是根据编译时的不同条件在代码中进行选择,从而允许在不同情况下使用不同的代码块,而不需要修改源代码。这对于实现跨平台兼容性、调试功能、开发和生产环境切换等方面非常有用。需要注意的是,条件编译在编译过程中会影响代码的可读性,因此应该谨慎使用,避免过度复杂的条件分支。
相关文章:

C语言快速回顾(二)
前言 在Android音视频开发中,网上知识点过于零碎,自学起来难度非常大,不过音视频大牛Jhuster提出了《Android 音视频从入门到提高 - 任务列表》,结合我自己的工作学习经历,我准备写一个音视频系列blog。C/C是音视频必…...

ADB连接安卓手机提示unauthorized
近期使用airtest进行自动化测试时,因为需要连接手机和电脑端,所以在使用adb去连接本人的安卓手机vivo z5时,发现一直提示unauthorized。后来经过一系列方法尝试,最终得以解决。 问题描述: 用数据线将手机接入电脑端&…...
【软件工程】内聚
概念 是指一个模块内部个成分之间相互关联程度的度量。也就是说,凝聚是对模块内各处理动作组合强度的一种度量。很显然,一个模块的内聚越大越好。 偶然凝聚 一个模块内的各处理元素之间没有任何联系,只是偶然地被凑到一起。这种模块也称为…...

支持对接鸿蒙系统的无线模块及其常见应用介绍
近距离的无线通信得益于万物互联网的快速发展,基于集成部近距离无线连接,为固定和移动设备建立通信的蓝牙技术也已经广泛应用于汽车领域、工业生产及医疗领域。为协助物联网企业终端产品能快速接入鸿蒙生态系统,SKYLAB联手国产芯片厂家研发推…...

java项目打包运行报异常:Demo-1.0-SNAPSHOT.jar中没有主清单属性
检查后发现pom文件中有错误,需要添加build内容才能恢复正常。 添加下面文件后再次启动恢复正常。 <build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactI…...
nginx+keepalived实现负载均衡和高可用
环境准备 IPVIP环境客户端192.168.134.174Master192.168.134.170192.168.134.100需要配置nginx负载均衡Backup192.168.134.172192.168.134.100需要配置nginx负载均衡web1服务器192.168.134.171 web2服务器 192.168.134.173 1、首先安装nginx服务器(此处采用yum安装…...
微信小程序实现图片多点裁剪
话不多说,直接上代码 1、页面布局 <view class"buttons" style"height: 50px;"><view class"upload btn" style"background-color: #d18118;"bindtap"uploadImage"> 上传图片 </view><vie…...
计算图片的均值和方差用图片的归一化取值
计算图片的均值和方差用图片的归一化取值 注意:使用这种方法的前提是进行了数据批量化操作,需要使用神经网络库,torch,DataLoader def getStat(data):print(len(data))loader torch.utils.data.DataLoader(data, batch_size1, …...

预测算法|改进粒子群算法优化极限学习机IDM-PSO-ELM
回归拟合: 分类 本文是作者的预测算法系列的第四篇,前面的文章中介绍了BP、SVM、RF及其优化,感兴趣的读者可以在作者往期文章中了解,这一篇将介绍——极限学习机 过去的几十年里基于梯度的学习方法被广泛用于训练神经网络&am…...

小黑子—JavaWeb:第六章 - Filter、Listener、AJAX与JSON
JavaWeb入门6.0 1. Filter1.1 Filter快速入门1.2 Filter执行流程1.3 Filter拦截路径配置1.4 Filter过滤器链1.5 案例登录验证 2. Listener2.1 ServletContextListener使用 3. AJAX3.1 AJAX 快速入门3.2 案例 验证用户名是否存在3.3 Axios 异步框架3.3.1 Axios 快速入门3.3.2 Ax…...

STM32 LL库开发
一、STM32开发方式 标准库开发:Standard Peripheral Libraries,STDHAL库开发:Hardware Abstraction Layer,硬件抽象层LL库开发:Low-layer,底层库 二、HAL库与LL库开发对比 ST在推行HAL库的时候,…...

标记垃圾,有三种色彩:四千长文带你深入了解三色标记算法
🔭 嗨,您好 👋 我是 vnjohn,在互联网企业担任 Java 开发,CSDN 优质创作者 📖 推荐专栏:Spring、MySQL、Nacos、Java,后续其他专栏会持续优化更新迭代 🌲文章所在专栏&…...
277/300 React+react-router-dom+Vite 二级页面刷新时,白屏问题解决
(一)方案 BrowserRouter 换为 HashRouter (二)代码 import routes from ./routes import {ReactElement, Suspense} from react import {createHashRouter, Navigate} from react-router-dom // 生成路由数据 const generateR…...
如何做线上监控
1、背景 软件的质量是需要全生命周期进行关注的,在生产环境下QA的活动就是测试右移,测试右移最关键的手段就是线上监控,也是至关重要的一个环节,可以通过技术的手段,提前感知到线上问题和风险,先于用户提前发现问题,提升服务可感知性,从而降低客户投诉。 2、通用原则…...
饥荒开服教程——游戏
饥荒开服教程——游戏 1. 开服环境2. 开服步骤2.1 创建集群2.2 安装服务端2.3 上传mod2.4 启动脚本2.5 上传地图2.6 设置访问令牌2.7 修改配置 3. 服务器命令3.1 关闭服务器3.2 回档 记录一些在饥荒联机版开服中遇到过的问题。 参考:3分钟创建你的饥荒联机专属服务…...
查询 npm/yarn 安装依赖的全局路径及路径修改
一、NPM 1.查询 npm 安装依赖的全局路径 npm prefix -g 2. 修改 npm 全局安装位置 npm config set prefix "D:\nodejs\node_modules\npm\node_modules" 3. 修改 npm 全局 cache 位置 npm config set cache "D:\nodejs\node_modules\npm\cache" 4. np…...

掌握Python的X篇_35_用Python为美女打码_图像库Pillow
本篇将会介绍python中的一个图像库Pillow。 文章目录 1. Pillow与PIL的关系2. 调整大小3. 加滤镜4. 剪裁5. 生成验证码 1. Pillow与PIL的关系 我们在网上搜python的图像库的话,可能搜到的时PIL。实际上之前python发展的时候就是PIL,这个库比较好用&…...

SpringBoot 异步、邮件任务
异步任务 创建一个Hello项目 创建一个类AsyncService 异步处理还是非常常用的,比如我们在网站上发送邮件,后台会去发送邮件,此时前台会造成响应不动,直到邮件发送完毕,响应才会成功,所以我们一般会采用多线…...

【LeetCode】45. 跳跃游戏 II - 贪婪算法
目录标题 2023-8-11 09:49:25 45. 跳跃游戏 II 2023-8-11 09:49:25 自己没做出来,废物Orz class Solution {public int jump(int[] nums) {int length nums.length;int end 0;int maxPosition 0;int steps 0;for (int i 0; i < length - 1; i) {maxPosit…...

[C初阶笔记]P1
什么是C语言 1、机器语言(二进制)>汇编语言(助记符)>高级语言(C、C等) 2、c语言擅长底层软件开发(操作系统、驱动程序),并不意味着不能开发其他。 C语言更贴近操作…...

业务系统对接大模型的基础方案:架构设计与关键步骤
业务系统对接大模型:架构设计与关键步骤 在当今数字化转型的浪潮中,大语言模型(LLM)已成为企业提升业务效率和创新能力的关键技术之一。将大模型集成到业务系统中,不仅可以优化用户体验,还能为业务决策提供…...

全球首个30米分辨率湿地数据集(2000—2022)
数据简介 今天我们分享的数据是全球30米分辨率湿地数据集,包含8种湿地亚类,该数据以0.5X0.5的瓦片存储,我们整理了所有属于中国的瓦片名称与其对应省份,方便大家研究使用。 该数据集作为全球首个30米分辨率、覆盖2000–2022年时间…...
【论文笔记】若干矿井粉尘检测算法概述
总的来说,传统机器学习、传统机器学习与深度学习的结合、LSTM等算法所需要的数据集来源于矿井传感器测量的粉尘浓度,通过建立回归模型来预测未来矿井的粉尘浓度。传统机器学习算法性能易受数据中极端值的影响。YOLO等计算机视觉算法所需要的数据集来源于…...

2025盘古石杯决赛【手机取证】
前言 第三届盘古石杯国际电子数据取证大赛决赛 最后一题没有解出来,实在找不到,希望有大佬教一下我。 还有就会议时间,我感觉不是图片时间,因为在电脑看到是其他时间用老会议系统开的会。 手机取证 1、分析鸿蒙手机检材&#x…...
Axios请求超时重发机制
Axios 超时重新请求实现方案 在 Axios 中实现超时重新请求可以通过以下几种方式: 1. 使用拦截器实现自动重试 import axios from axios;// 创建axios实例 const instance axios.create();// 设置超时时间 instance.defaults.timeout 5000;// 最大重试次数 cons…...

mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包
文章目录 现象:mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包遇到 rpm 命令找不到已经安装的 MySQL 包时,可能是因为以下几个原因:1.MySQL 不是通过 RPM 包安装的2.RPM 数据库损坏3.使用了不同的包名或路径4.使用其他包…...

数学建模-滑翔伞伞翼面积的设计,运动状态计算和优化 !
我们考虑滑翔伞的伞翼面积设计问题以及运动状态描述。滑翔伞的性能主要取决于伞翼面积、气动特性以及飞行员的重量。我们的目标是建立数学模型来描述滑翔伞的运动状态,并优化伞翼面积的设计。 一、问题分析 滑翔伞在飞行过程中受到重力、升力和阻力的作用。升力和阻力与伞翼面…...

Mac flutter环境搭建
一、下载flutter sdk 制作 Android 应用 | Flutter 中文文档 - Flutter 中文开发者网站 - Flutter 1、查看mac电脑处理器选择sdk 2、解压 unzip ~/Downloads/flutter_macos_arm64_3.32.2-stable.zip \ -d ~/development/ 3、添加环境变量 命令行打开配置环境变量文件 ope…...
【Java基础】向上转型(Upcasting)和向下转型(Downcasting)
在面向对象编程中,转型(Casting) 是指改变对象的引用类型,主要涉及 继承关系 和 多态。 向上转型(Upcasting) ⬆️ 定义 将 子类对象 赋值给 父类引用(自动完成,无需强制转换&…...

MySQL 数据库深度剖析:事务、SQL 优化、索引与 Buffer Pool
在当今数据驱动的时代,数据库作为数据存储与管理的核心,其性能与可靠性至关重要。MySQL 作为一款广泛使用的开源数据库,在众多应用场景中发挥着关键作用。在这篇博客中,我将围绕 MySQL 数据库的核心知识展开,涵盖事务及…...