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 DEBUG
        printf("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 DEBUG
        printf("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 DEBUG
        printf("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 DEBUG
        printf("Debug mode is enabled.\n");
    #endif
    
  2. #ifndef#endif 如果某个宏未定义,则编译下面的代码块。

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

    #ifdef DEBUG
        printf("Debug mode is enabled.\n");
    #else
        printf("Debug mode is not enabled.\n");
    #endif
    
  4. #elif#else 类似,但可以用于在多个条件之间进行选择。

    #ifdef DEBUG
        printf("Debug mode is enabled.\n");
    #elif defined(TESTING)
        printf("Testing mode is enabled.\n");
    #else
        printf("No special mode is enabled.\n");
    #endif
    

条件编译的主要目的是根据编译时的不同条件在代码中进行选择,从而允许在不同情况下使用不同的代码块,而不需要修改源代码。这对于实现跨平台兼容性、调试功能、开发和生产环境切换等方面非常有用。需要注意的是,条件编译在编译过程中会影响代码的可读性,因此应该谨慎使用,避免过度复杂的条件分支。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/74386.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Github】SourceTree技巧汇总

sourceTree登录github账户 会跳转到浏览器端 按照Git Flow 初始化仓库分支 克隆远程仓库到本地 推送变更到远程仓库 合并分支 可以看到目前的本地分支&#xff08;main、iOS_JS&#xff09;和远程分支&#xff08;origin/main、origin/HEAD、origin/iOS_JS&#xff09;目前所处…

C++多线程场景中的变量提前释放导致栈内存异常

多线程场景中的栈内存异常 在子线程中尝试使用当前函数的资源&#xff0c;是非常危险的&#xff0c;但是C支持这么做。因此C这么做可能会造成栈内存异常。 正常代码 #include <iostream> #include <thread> #include <windows.h>// 线程函数&#xff0c;用…

消防态势标绘工具,为消防基层工作助力

背景介绍 无人机测绘技术在消防领域的应用越来越普及&#xff0c;高清的二维正射影像和倾斜摄影实景三维模型能为消防态势标绘提供高质量的素材&#xff0c;消防队急需一个简便易用的、能够基于这些二三维的高清地图成果进行态势标绘的工具软件&#xff0c;使得消防“六熟悉”…

Rust 重载运算符|复数结构的“加减乘除”四则运算

复数 基本概念 复数定义 由实数部分和虚数部分所组成的数&#xff0c;形如a&#xff0b;bi 。 其中a、b为实数&#xff0c;i 为“虚数单位”&#xff0c;i -1&#xff0c;即虚数单位的平方等于-1。 a、b分别叫做复数a&#xff0b;bi的实部和虚部。 当b0时&#xff0c;a&…

(二)结构型模式:2、桥接模式(Bridge Pattern)(C++实现示例)

目录 1、桥接模式&#xff08;Bridge Pattern&#xff09;含义 2、桥接模式应用场景 3、桥接模式的UML图学习 4、C实现桥接模式的示例 1、桥接模式&#xff08;Bridge Pattern&#xff09;含义 桥接模式是一种结构型设计模式&#xff0c;它将抽象部分与实现部分分离&#…

解决ubantu驱动掉了的问题

这里写自定义目录标题 解决ubuntu驱动掉了的问题 解决ubuntu驱动掉了的问题 首先确定是否有驱动&#xff1a; ls /usr/src | grep nvidia若有&#xff0c;则大概率是驱动版本与内核版本对应不上&#xff0c;则把内核版本切换为初始版本即可。参照&#xff1a;https://blog.cs…

【小梦C嘎嘎——启航篇】string介绍以及日常使用的接口演示

【小梦C嘎嘎——启航篇】string 使用&#x1f60e; 前言&#x1f64c;C语言中的字符串标准库中的string类string 比较常使用的接口对上述函数和其他函数的测试代码演示&#xff1a; 总结撒花&#x1f49e; &#x1f60e;博客昵称&#xff1a;博客小梦 &#x1f60a;最喜欢的座右…

【Spring专题】Bean的生命周期流程图

目录 前言阅读指引 流程图一、之前推测的简单流程图&#xff08;一点点参考&#xff09;*二、Bean生命周期流程图&#xff08;根据Spring源码自结&#xff09;*三、阶段源码流程图&#xff08;不断更新&#xff09; 前言 我向来不主张【通过源码】理解业务&#xff0c;因为每个…

idea报错:java: 程序包org.springframework.web.bind.annotation不存在

这个错误通常都是maven仓库的问题&#xff0c;试了网上很多方法&#xff0c;都没有解决&#xff0c;如果大家有遇到这个问题&#xff0c;且试了很多方法之后都没有解决&#xff0c;不妨可以试试我这个方法 先编译一下已经写好的代码&#xff0c;这时候会出现以上报错&#xff…

ssm基于ssm的人才招聘网站源码和论文

ssm基于ssm的人才招聘网站源码和论文020 开发工具&#xff1a;idea 数据库mysql5.7 数据库链接工具&#xff1a;navcat,小海豚等 技术&#xff1a;ssm 选题依据&#xff08;研究的背景、目的和意义等&#xff09; 在Internet飞速发展的今天&#xff0c;互联网成为人们快…

云安全攻防(十一)之 容器编排平台面临的风险

前言 容器技术和编排管理是云原生生态的两大核心部分——前者负责执行&#xff0c;后者负责控制和管理&#xff0c;共同构成云原生技术有机体&#xff0c;我们以 Kubernetes 为例&#xff0c;对容器编排平台可能面临的风险进行分析 容器编排平台面临的风险 作为最为流行的云…

c++11 标准模板(STL)(std::basic_stringbuf)(三)

定义于头文件 <sstream> template< class CharT, class Traits std::char_traits<CharT>, class Allocator std::allocator<CharT> > class basic_stringbuf : public std::basic_streambuf<CharT, Traits> std::basic_stringbuf…

【Git】 git push origin master Everything up-to-date报错

hello&#xff0c;我是索奇&#xff0c;可以叫我小奇 git push 出错&#xff1f;显示 Everything up-to-date 那么看看你是否提交了message 下面是提交的简单流程 git add . git commit -m "message" git push origin master 大多数伙伴是没写git commit -m "…

Rx.NET in Action 第三章学习笔记

3 C#函数式编程思想 本章内容包括 将 C# 与函数式技术相结合使用委托和 lambda 表达式使用 LINQ 查询集合 面向对象编程为程序开发提供了巨大的生产力。它将复杂的系统分解为类&#xff0c;使项目更易于管理&#xff0c;而对象则是一个个孤岛&#xff0c;你可以集中精力分别处理…

电脑屏幕闪烁?别慌!解决方法在这!

“我新买了一台电脑&#xff0c;还没用几天呢&#xff0c;就出现了电脑屏幕闪烁的情况&#xff0c;这让我感到很烦躁。有什么方法可以解决电脑屏幕闪烁的问题呢&#xff1f;” 使用电脑的过程中&#xff0c;我们不难发现电脑屏幕有时候会出现闪烁的情况&#xff0c;这会导致使用…

【考研数学】高等数学第三模块——积分学 | Part II 定积分(反常积分及定积分应用)

文章目录 前言三、广义积分3.1 敛散性概念&#xff08;一&#xff09;积分区间为无限的广义积分&#xff08;二&#xff09;积分区间有限但存在无穷间断点 3.2 敛散性判别法 四、定积分应用写在最后 前言 承接前文&#xff0c;梳理完定积分的定义及性质后&#xff0c;我们进入…

Datawhale Django后端开发入门Task01 Vscode配置环境

首先呢放一张运行成功的截图纪念一下&#xff0c;感谢众多小伙伴的帮助呀&#xff0c;之前没有配置这方面的经验 &#xff0c;但还是一步一步配置成功了&#xff0c;所以在此以一个纯小白的经验分享如何配置成功。 1.选择要建立项目的文件夹&#xff0c;打开文件找到目标文件夹…

论文阅读——Adversarial Eigen Attack on Black-Box Models

Adversarial Eigen Attack on Black-Box Models 作者&#xff1a;Linjun Zhou&#xff0c; Linjun Zhou 攻击类别&#xff1a;黑盒&#xff08;基于梯度信息&#xff09;&#xff0c;白盒模型的预训练模型可获得&#xff0c;但训练数据和微调预训练模型的数据不可得&#xff…

微信小程序data-item设置获取不到数据的问题

微信小程序data-item设置获取不到数据的问题 简单说明&#xff1a; 在微信小程序中&#xff0c;通过列表渲染使用wx:for根据数组中的每一项重复渲染组件。同时使用bindtap给每一项绑定点击事件clickItem&#xff0c;再通过data-item绑定数据。 **问题&#xff1a;**通过data-i…

【SQL应知应会】索引(二)• MySQL版

欢迎来到爱书不爱输的程序猿的博客, 本博客致力于知识分享&#xff0c;与更多的人进行学习交流 本文收录于SQL应知应会专栏,本专栏主要用于记录对于数据库的一些学习&#xff0c;有基础也有进阶&#xff0c;有MySQL也有Oracle 索引 • MySQL版 前言一、索引1.简介2.创建2.1 索引…