当前位置: 首页 > news >正文

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语言中的字符串变量通常是字符数组或字符指针,用于存储和处理字符串数据。字符串变量允许你在程序中操作文本和字符数据。

以下是使用字符数组和字符指针来创建字符串变量的示例:

  1. 字符数组(Character Array): 字符数组是一种固定大小的数组,用于存储字符串。可以初始化为一个字符串字面量,也可以逐个字符赋值。

    char str1[20] = "Hello, World!"; // 初始化为字符串
    char str2[10]; // 声明一个字符数组
    strcpy(str2, "Welcome"); // 复制字符串到字符数组
    
  2. 字符指针(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() 用于从用户输入中读取字符串。

以下是这些函数的使用示例:

  1. 输出字符串: 使用 printf() 函数输出字符串。

    #include <stdio.h>int main() {char str[] = "Hello, World!";printf("String: %s\n", str);return 0;
    }
    
  2. 输入字符串: 使用 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;
    }
    
  3. 安全读取字符串: 为了安全地读取字符串,推荐使用 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;
    }
    
  4. 从文件中读取字符串: 使用 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语言字符串库函数:

  1. strlen() 计算字符串的长度(不包括空字符)。

    size_t strlen(const char *str);
    
  2. strcpy() 复制一个字符串到另一个字符串。

    char *strcpy(char *dest, const char *src);
    
  3. strncpy() 从源字符串复制指定数量的字符到目标字符串。

    char *strncpy(char *dest, const char *src, size_t n);
    
  4. strcat() 连接两个字符串,将一个字符串附加到另一个字符串的末尾。

    char *strcat(char *dest, const char *src);
    
  5. strncat() 将指定数量的字符从源字符串附加到目标字符串。

    char *strncat(char *dest, const char *src, size_t n);
    
  6. strcmp() 比较两个字符串,返回一个整数表示比较结果。

    int strcmp(const char *str1, const char *str2);
    
  7. strncmp() 比较两个字符串的指定数量字符,返回一个整数表示比较结果。

    int strncmp(const char *str1, const char *str2, size_t n);
    
  8. strstr() 在一个字符串中查找另一个子字符串,返回子字符串的第一个匹配位置。

    char *strstr(const char *haystack, const char *needle);
    
  9. strchr() 在字符串中查找指定字符的第一个匹配位置。

    char *strchr(const char *str, int c);
    
  10. strtok() 分割字符串为多个子字符串,使用指定的分隔符。

   char *strtok(char *str, const char *delimiters);
  1. sprintf() 将格式化的数据写入字符串。

    int sprintf(char *str, const char *format, ...);
    
  2. sscanf() 从字符串中按照指定格式读取数据。

    int sscanf(const char *str, const char *format, ...);
    
  3. memset() 将指定值设置给一段内存。

    void *memset(void *s, int c, size_t n);
    
  4. memcpy() 复制一段内存内容到另一段内存。

    void *memcpy(void *dest, const void *src, size_t n);
    
  5. memmove() 安全地复制一段内存内容到另一段内存,避免重叠问题。

    void *memmove(void *dest, const void *src, size_t n);
    

这些函数是C语言字符串操作的基础工具,它们使得处理字符串变得更加简单和高效。可以根据需要使用这些函数来完成不同的字符串操作任务。


1.6 字符串常见用法

C语言字符串在编程中有许多常见的用法,涵盖了从字符串操作到字符串输入输出的多个方面。以下是一些常见的C语言字符串用法示例:

  1. 字符串赋值和初始化: 可以使用字符数组或字符指针来初始化字符串变量,也可以将字符串字面量赋值给字符串变量。

    char str1[] = "Hello, World!"; // 使用字符数组初始化
    char *str2 = "Welcome"; // 使用字符指针初始化
    char str3[20]; // 未初始化的字符数组
    strcpy(str3, "C programming"); // 复制字符串到字符数组
    
  2. 字符串连接: 使用 strcat() 函数将一个字符串连接到另一个字符串的末尾。

    char str1[20] = "Hello";
    char str2[] = " World!";
    strcat(str1, str2); // 连接字符串
    
  3. 字符串长度: 使用 strlen() 函数计算字符串的长度(不包括空字符)。

    char str[] = "Hello";
    int length = strlen(str); // 计算字符串长度
    
  4. 字符串比较: 使用 strcmp() 函数比较两个字符串是否相等。

    char str1[] = "Hello";
    char str2[] = "World";
    int result = strcmp(str1, str2); // 比较字符串
    
  5. 字符串输入输出: 使用 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 安全输入字符串
    
  6. 遍历字符串: 使用循环遍历字符串中的每个字符。

    char str[] = "Hello";
    for (int i = 0; str[i] != '\0'; i++) {printf("%c ", str[i]);
    }
    
  7. 将字符串转换为数字: 使用 atoi()strtol() 将字符串转换为整数,使用 atof() 将字符串转换为浮点数。

    char numStr[] = "12345";
    int num = atoi(numStr); // 转换为整数
    
  8. 从字符串中提取子字符串: 使用 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语言中的指针是一种变量,用于存储内存地址。指针可以指向不同类型的数据,例如整数、字符、浮点数、数组、结构体等。指针允许你直接访问内存中的数据,进行动态内存分配、传递函数参数等操作。

以下是指针的定义和使用示例:

  1. 定义指针: 指针变量存储某个变量的内存地址。

    int x = 10; // 整数变量
    int *ptr;   // 整数指针变量
    ptr = &x;   // 指针指向 x 的地址
    
  2. 访问指针指向的值: 使用解引用运算符 * 可以访问指针所指向的值。

    int value = *ptr; // 获取指针指向的值
    printf("Value: %d\n", value);
    
  3. 修改指针指向的值: 通过指针可以修改所指向内存的值。

    *ptr = 20; // 修改指针指向的值
    printf("New value: %d\n", x);
    
  4. 指针的算术运算: 指针可以进行加法、减法等运算,以在内存中移动。

    int arr[5] = {1, 2, 3, 4, 5};
    int *arrPtr = arr; // 指向数组的第一个元素
    arrPtr++; // 指向下一个元素
    
  5. 指针和函数: 可以将指针作为参数传递给函数,以在函数中修改变量的值。

    void modifyValue(int *ptr) {*ptr = 100; // 修改指针指向的值
    }int main() {int num = 50;modifyValue(&num);printf("Modified value: %d\n", num);return 0;
    }
    
  6. 动态内存分配: 使用 malloc() 函数动态分配内存,返回一个指向分配内存的指针。

    int *dynamicPtr = (int *)malloc(sizeof(int));
    *dynamicPtr = 100;
    free(dynamicPtr); // 释放动态分配的内存
    
  7. 指向数组: 数组名本身就是指向数组第一个元素的指针。

    int arr[3] = {1, 2, 3};
    int *arrPtr = arr; // 指向数组的第一个元素
    
  8. 指向字符串: 字符串可以使用字符指针或字符数组来表示。

    char *str = "Hello"; // 字符指针
    char strArr[] = "World"; // 字符数组
    

指针在C语言中非常重要,它提供了对内存的底层访问,可以实现灵活的数据处理和操作。同时,指针也需要小心使用,避免野指针(指向无效内存)和内存泄漏等问题。


2.2 指针大小,野指针和空指针

在C语言中,指针是一种变量,用于存储内存地址。指针的大小取决于编译器和系统的架构,通常在32位系统上为4字节,在64位系统上为8字节。这表示指针变量本身存储了一个内存地址,该地址指向存储的数据。

  1. 野指针(Wild Pointer): 野指针是指没有正确初始化的指针,或者指向无效内存地址的指针。使用野指针可能导致程序崩溃或产生不可预测的结果。

    int *wildPtr; // 野指针,未初始化
    int x;
    int *wildPtr2 = &x; // 野指针,指向未分配的内存
    
  2. 空指针(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语言中常见的一些指针类型以及如何使用它们:

  1. 整型指针(int pointer): 指向整数类型的指针。

    int x = 10;
    int *ptr; // 声明整型指针
    ptr = &x; // 指针指向整数变量 x 的地址
    

    解引用整型指针:

    int value = *ptr; // 获取指针所指向的整数值
    
  2. 字符型指针(char pointer): 指向字符类型的指针。

    char ch = 'A';
    char *chPtr; // 声明字符型指针
    chPtr = &ch; // 指针指向字符变量 ch 的地址
    

    解引用字符型指针:

    char character = *chPtr; // 获取指针所指向的字符值
    
  3. 浮点型指针(float pointer): 指向浮点数类型的指针。

    float y = 3.14;
    float *fPtr; // 声明浮点型指针
    fPtr = &y; // 指针指向浮点数变量 y 的地址
    

    解引用浮点型指针:

    float number = *fPtr; // 获取指针所指向的浮点数值
    
  4. 指向指针的指针(pointer to pointer): 指向另一个指针的指针。

    int x = 10;
    int *ptr1 = &x;
    int **ptrPtr; // 声明指向指针的指针
    ptrPtr = &ptr1; // 指向指针变量 ptr1 的地址
    

    解引用指向指针的指针:

    int value = **ptrPtr; // 获取指向指针的指针所指向的整数值
    
  5. 数组指针(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)); // 输出数组元素
    }
    
  6. 函数指针(function pointer): 指向函数的指针,可以用于调用函数。

    int add(int a, int b) {return a + b;
    }int (*funcPtr)(int, int); // 声明函数指针
    funcPtr = add; // 指向函数 add
    int result = funcPtr(3, 4); // 通过函数指针调用函数
    
  7. 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 的值。

指向指针的指针特别在以下情况下很有用:

  1. 多级间接访问: 可以通过多级间接访问来访问嵌套的数据结构,如链表中的节点。
  2. 动态数组: 在动态内存分配时,使用指向指针的指针来管理数组。
  3. 多维数组: 用于表示和操作多维数组,如指向指针的指针数组。
  4. 传递指针: 可以在函数间传递指向指针的指针,以实现对指针变量的修改。

以下是一个示例,演示如何使用指向指针的指针来创建一个动态二维数组:

#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语言中,指针既可以作为函数的参数,也可以作为函数的返回值。这些用法使得可以在函数间传递指针,实现对变量的修改,以及在函数内部动态分配内存并返回指向该内存的指针。

以下是指针作为参数和作为返回值的示例:

  1. 指针作为参数: 可以将指针作为参数传递给函数,这允许在函数内部修改指针所指向的数据。

    #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;
    }
    
  2. 指针作为返回值: 函数可以返回指针,通常用于动态分配内存,并返回指向该内存的指针。

    #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语言中,指针在数组处理中扮演着非常重要的角色,可以通过指针来访问和操作数组的元素,以及实现动态内存分配和释放。以下是指针在数组处理中的一些常见用法:

  1. 遍历数组元素: 可以使用指针来遍历数组的元素,以便访问和操作数组中的数据。

    #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;
    }
    
  2. 传递数组给函数: 可以将数组传递给函数,并在函数内部使用指针来操作数组。

    #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;
    }
    
  3. 动态内存分配: 使用指针和动态内存分配函数(如 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;
    }
    
  4. 多维数组处理: 指针可以用于处理多维数组,可以通过适当的指针算术来访问多维数组的元素。

    #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语言中,数组名可以被视为指向数组第一个元素的指针。这是一种方便的用法,允许你通过数组名来访问数组元素,或者将数组名传递给函数来操作数组。

以下是使用数组名作为指针的示例:

  1. 访问数组元素: 可以使用数组名加索引的方式来访问数组元素。

    #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;
    }
    
  2. 传递数组给函数: 可以将数组名作为指针参数传递给函数,并在函数内部操作数组。

    #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;
    }
    
  3. 数组指针算术: 可以使用数组名作为指针,并进行指针算术来遍历数组元素。

    #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语言中,指针在处理多维数组时发挥了重要作用。多维数组实际上是数组的数组,因此在处理多维数组时,可以使用指针来访问和操作数组的元素。以下是指针在多维数组处理中的一些常见用法:

  1. 使用指针遍历多维数组: 可以使用指针来遍历多维数组的元素,通过适当的指针算术来访问数组的不同维度。

    #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;
    }
    
  2. 传递多维数组给函数: 可以将多维数组传递给函数,并在函数内部使用指针来操作数组。

    #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编译器的一个重要组成部分,它在实际的编译过程之前对源代码进行预处理,执行一系列的文本替换和宏展开操作,以及处理条件编译等任务。预处理器的主要任务是对源代码进行预处理,生成经过处理的中间代码,然后再由编译器进一步处理生成目标代码。

预处理器的工作原理如下:

  1. 文本替换和宏展开: 预处理器会根据预处理指令对源代码进行文本替换和宏展开。例如,通过#define指令定义的宏会被展开为相应的文本。

    #define PI 3.14159
    float area = PI * radius * radius;
    

    在上述代码中,预处理器会将PI宏展开为3.14159,生成实际的表达式。

  2. 头文件包含: 预处理器可以通过#include指令将外部文件(头文件)的内容插入到源代码中。这有助于模块化和代码重用。

    #include <stdio.h>
    int main() {printf("Hello, World!\n");return 0;
    }
    

    在上述代码中,#include <stdio.h>会将标准输入输出库的内容插入到源代码中。

  3. 条件编译: 预处理器可以根据条件指令,如#ifdef#ifndef#if#else#elif#endif,在编译时决定是否包含某些代码块。

    #define DEBUG 1
    #ifdef DEBUGprintf("Debug mode is enabled.\n");
    #endif
    

    在上述代码中,如果DEBUG宏被定义,那么printf语句会被包含在编译中。

  4. 其他预处理指令: 预处理器还支持其他指令,如#undef(取消宏定义)、#line(设置行号和文件名)、#error(产生错误消息)、#pragma(编译器指示)等。

    #line 42 "mycode.c"
    #error This is an error message.
    #pragma warning(disable: 1234)
    

预处理器在编译之前处理源代码,将源代码转换为经过宏展开和文本替换后的中间代码。这些经过处理的中间代码会交给编译器进行实际的编译,生成目标代码和最终的可执行文件。这种分阶段的处理使得C语言具有灵活性和可维护性。


4.2 预处理指令

C语言预处理器指令是在编译过程之前对源代码进行预处理的指令,用于进行文本替换、宏展开、条件编译等操作。以下是一些常用的C语言预处理指令:

  1. #define: 定义宏,用于进行简单的文本替换和宏展开。

    #define PI 3.14159
    
  2. #include: 包含外部文件的内容,通常用于包含头文件。

    #include <stdio.h>
    
  3. #ifdef / #ifndef / #endif: 条件编译,根据条件是否定义了宏来决定是否编译代码块。

    #ifdef DEBUGprintf("Debug mode is enabled.\n");
    #endif
    
  4. #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
    
  5. #undef: 取消宏定义。

    #undef PI
    
  6. #pragma: 发送编译器特定的指示,如关闭警告、设定对齐方式等。

    #pragma warning(disable: 1234)
    
  7. #error: 产生编译错误并输出自定义错误消息。

    #ifdef DEBUG#error Debug mode is not supported in this version.
    #endif
    
  8. #line: 设置行号和文件名,用于调试和错误报告。

    #line 42 "mycode.c"
    

这些预处理指令在编译之前会被预处理器处理,将源代码中的宏展开、文件包含、条件编译等操作转换成中间代码,然后再由编译器进行实际的编译。预处理器指令使得C语言具有更高的灵活性和可维护性,能够根据不同的需求来进行定制化的编译过程。


4.3 宏定义

C语言宏定义是一种预处理器指令,用于在源代码中进行文本替换和宏展开,以便在编译阶段生成相应的代码。宏定义可以用于定义常量、函数宏、条件编译等,以提高代码的可读性和维护性。宏定义使用#define关键字进行定义。

以下是一些常见的C语言宏定义用法:

  1. 定义常量: 使用宏定义来创建常量,以便在代码中使用。

    #define PI 3.14159
    #define MAX_SIZE 100
    
  2. 定义函数宏: 创建函数宏来实现简单的代码替换。

    #define SQUARE(x) ((x) * (x))
    
  3. 带参数的函数宏: 创建带参数的函数宏,可以根据传入的参数进行展开。

    #define MAX(a, b) ((a) > (b) ? (a) : (b))
    
  4. 条件编译: 使用宏定义来进行条件编译,根据不同的宏定义决定是否编译某部分代码。

    #define DEBUG
    #ifdef DEBUGprintf("Debug mode is enabled.\n");
    #endif
    
  5. 宏与字符串: 可以使用宏定义来创建字符串常量。

    #define MESSAGE "Hello, World!"
    
  6. 多行宏: 可以使用反斜杠 \ 在多行上定义宏。

    #define ADD(a, b) \do { \printf("Adding %d and %d\n", a, b); \(a) + (b); \} while (0)
    
  7. 取消宏定义: 可以使用#undef取消已定义的宏。

    #undef PI
    

宏定义在预处理阶段被展开为实际的代码,这意味着宏定义并不是在编译阶段进行类型检查或其他语法分析,而是简单的文本替换。因此,在使用宏定义时要小心确保正确的使用方式,避免由于展开引发意想不到的问题。


4.4 条件编译

C语言条件编译是一种预处理器功能,允许你根据预定义的宏或条件来选择性地编译代码块。条件编译在不同平台、不同编译选项或不同情况下可以选择性地包含或排除代码,以便在不同环境中实现代码的灵活性和可移植性。

条件编译的关键指令包括:#ifdef#ifndef#else#elif#endif

以下是条件编译的一些用法示例:

  1. #ifdef#endif 如果某个宏已经定义,则编译下面的代码块。

    #define DEBUG
    #ifdef DEBUGprintf("Debug mode is enabled.\n");
    #endif
    
  2. #ifndef#endif 如果某个宏未定义,则编译下面的代码块。

    #ifndef RELEASEprintf("This is not a release version.\n");
    #endif
    
  3. #else 如果前面的条件不成立,则编译下面的代码块。

    #ifdef DEBUGprintf("Debug mode is enabled.\n");
    #elseprintf("Debug mode is not enabled.\n");
    #endif
    
  4. #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音视频开发中&#xff0c;网上知识点过于零碎&#xff0c;自学起来难度非常大&#xff0c;不过音视频大牛Jhuster提出了《Android 音视频从入门到提高 - 任务列表》&#xff0c;结合我自己的工作学习经历&#xff0c;我准备写一个音视频系列blog。C/C是音视频必…...

ADB连接安卓手机提示unauthorized

近期使用airtest进行自动化测试时&#xff0c;因为需要连接手机和电脑端&#xff0c;所以在使用adb去连接本人的安卓手机vivo z5时&#xff0c;发现一直提示unauthorized。后来经过一系列方法尝试&#xff0c;最终得以解决。 问题描述&#xff1a; 用数据线将手机接入电脑端&…...

【软件工程】内聚

概念 是指一个模块内部个成分之间相互关联程度的度量。也就是说&#xff0c;凝聚是对模块内各处理动作组合强度的一种度量。很显然&#xff0c;一个模块的内聚越大越好。 偶然凝聚 一个模块内的各处理元素之间没有任何联系&#xff0c;只是偶然地被凑到一起。这种模块也称为…...

支持对接鸿蒙系统的无线模块及其常见应用介绍

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

java项目打包运行报异常:Demo-1.0-SNAPSHOT.jar中没有主清单属性

检查后发现pom文件中有错误&#xff0c;需要添加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服务器&#xff08;此处采用yum安装…...

微信小程序实现图片多点裁剪

话不多说&#xff0c;直接上代码 1、页面布局 <view class"buttons" style"height: 50px;"><view class"upload btn" style"background-color: #d18118;"bindtap"uploadImage"> 上传图片 </view><vie…...

计算图片的均值和方差用图片的归一化取值

计算图片的均值和方差用图片的归一化取值 注意&#xff1a;使用这种方法的前提是进行了数据批量化操作&#xff0c;需要使用神经网络库&#xff0c;torch&#xff0c;DataLoader def getStat(data):print(len(data))loader torch.utils.data.DataLoader(data, batch_size1, …...

预测算法|改进粒子群算法优化极限学习机IDM-PSO-ELM

回归拟合&#xff1a; 分类 本文是作者的预测算法系列的第四篇&#xff0c;前面的文章中介绍了BP、SVM、RF及其优化&#xff0c;感兴趣的读者可以在作者往期文章中了解&#xff0c;这一篇将介绍——极限学习机 过去的几十年里基于梯度的学习方法被广泛用于训练神经网络&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开发方式 标准库开发&#xff1a;Standard Peripheral Libraries&#xff0c;STDHAL库开发&#xff1a;Hardware Abstraction Layer&#xff0c;硬件抽象层LL库开发&#xff1a;Low-layer&#xff0c;底层库 二、HAL库与LL库开发对比 ST在推行HAL库的时候&#xff0c;…...

标记垃圾,有三种色彩:四千长文带你深入了解三色标记算法

&#x1f52d; 嗨&#xff0c;您好 &#x1f44b; 我是 vnjohn&#xff0c;在互联网企业担任 Java 开发&#xff0c;CSDN 优质创作者 &#x1f4d6; 推荐专栏&#xff1a;Spring、MySQL、Nacos、Java&#xff0c;后续其他专栏会持续优化更新迭代 &#x1f332;文章所在专栏&…...

277/300 React+react-router-dom+Vite 二级页面刷新时,白屏问题解决

&#xff08;一&#xff09;方案 BrowserRouter 换为 HashRouter &#xff08;二&#xff09;代码 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 回档 记录一些在饥荒联机版开服中遇到过的问题。 参考&#xff1a;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的图像库的话&#xff0c;可能搜到的时PIL。实际上之前python发展的时候就是PIL&#xff0c;这个库比较好用&…...

SpringBoot 异步、邮件任务

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

【LeetCode】45. 跳跃游戏 II - 贪婪算法

目录标题 2023-8-11 09:49:25 45. 跳跃游戏 II 2023-8-11 09:49:25 自己没做出来&#xff0c;废物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、机器语言&#xff08;二进制&#xff09;>汇编语言&#xff08;助记符&#xff09;>高级语言&#xff08;C、C等&#xff09; 2、c语言擅长底层软件开发&#xff08;操作系统、驱动程序&#xff09;&#xff0c;并不意味着不能开发其他。 C语言更贴近操作…...

shell脚本--常见案例

1、自动备份文件或目录 2、批量重命名文件 3、查找并删除指定名称的文件&#xff1a; 4、批量删除文件 5、查找并替换文件内容 6、批量创建文件 7、创建文件夹并移动文件 8、在文件夹中查找文件...

MySQL 隔离级别:脏读、幻读及不可重复读的原理与示例

一、MySQL 隔离级别 MySQL 提供了四种隔离级别,用于控制事务之间的并发访问以及数据的可见性,不同隔离级别对脏读、幻读、不可重复读这几种并发数据问题有着不同的处理方式,具体如下: 隔离级别脏读不可重复读幻读性能特点及锁机制读未提交(READ UNCOMMITTED)允许出现允许…...

鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个医院查看报告小程序

一、开发环境准备 ​​工具安装​​&#xff1a; 下载安装DevEco Studio 4.0&#xff08;支持HarmonyOS 5&#xff09;配置HarmonyOS SDK 5.0确保Node.js版本≥14 ​​项目初始化​​&#xff1a; ohpm init harmony/hospital-report-app 二、核心功能模块实现 1. 报告列表…...

反射获取方法和属性

Java反射获取方法 在Java中&#xff0c;反射&#xff08;Reflection&#xff09;是一种强大的机制&#xff0c;允许程序在运行时访问和操作类的内部属性和方法。通过反射&#xff0c;可以动态地创建对象、调用方法、改变属性值&#xff0c;这在很多Java框架中如Spring和Hiberna…...

基于IDIG-GAN的小样本电机轴承故障诊断

目录 🔍 核心问题 一、IDIG-GAN模型原理 1. 整体架构 2. 核心创新点 (1) ​梯度归一化(Gradient Normalization)​​ (2) ​判别器梯度间隙正则化(Discriminator Gradient Gap Regularization)​​ (3) ​自注意力机制(Self-Attention)​​ 3. 完整损失函数 二…...

【Android】Android 开发 ADB 常用指令

查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...

LCTF液晶可调谐滤波器在多光谱相机捕捉无人机目标检测中的作用

中达瑞和自2005年成立以来&#xff0c;一直在光谱成像领域深度钻研和发展&#xff0c;始终致力于研发高性能、高可靠性的光谱成像相机&#xff0c;为科研院校提供更优的产品和服务。在《低空背景下无人机目标的光谱特征研究及目标检测应用》这篇论文中提到中达瑞和 LCTF 作为多…...

华为OD最新机试真题-数组组成的最小数字-OD统一考试(B卷)

题目描述 给定一个整型数组,请从该数组中选择3个元素 组成最小数字并输出 (如果数组长度小于3,则选择数组中所有元素来组成最小数字)。 输入描述 行用半角逗号分割的字符串记录的整型数组,0<数组长度<= 100,0<整数的取值范围<= 10000。 输出描述 由3个元素组成…...

32单片机——基本定时器

STM32F103有众多的定时器&#xff0c;其中包括2个基本定时器&#xff08;TIM6和TIM7&#xff09;、4个通用定时器&#xff08;TIM2~TIM5&#xff09;、2个高级控制定时器&#xff08;TIM1和TIM8&#xff09;&#xff0c;这些定时器彼此完全独立&#xff0c;不共享任何资源 1、定…...

二维FDTD算法仿真

二维FDTD算法仿真&#xff0c;并带完全匹配层&#xff0c;输入波形为高斯波、平面波 FDTD_二维/FDTD.zip , 6075 FDTD_二维/FDTD_31.m , 1029 FDTD_二维/FDTD_32.m , 2806 FDTD_二维/FDTD_33.m , 3782 FDTD_二维/FDTD_34.m , 4182 FDTD_二维/FDTD_35.m , 4793...