C Primer Plus疑难点

const static extern
函数指针
链表

转义序列

转义序列(Escape Sequence)是一组特殊字符,用于在字符串和字符常量中表示一些无法直接输入或显示的字符。在大多数编程语言中,转义序列以反斜杠(\)开头,后面跟着一个或多个字符。

以下是一些常见的转义序列及其含义:

  1. 换行符(Newline)

    • \n:表示换行符,用于在字符串中插入一个新行。
  2. 回车符(Carriage Return)

    • \r:表示回车符,用于将光标移动到当前行的开头。
  3. 制表符(Tab)

    • \t:表示水平制表符,用于在字符串中插入一个制表符。
  4. 退格符(Backspace)

    • \b:表示退格符,用于将光标向左移动一个位置。
  5. 换页符(Form Feed)

    • \f:表示换页符,用于在打印或显示时插入一个新页。
  6. 反斜杠(Backslash)

    • \\:表示一个反斜杠字符。
  7. 单引号(Single Quote)

    • \':表示一个单引号字符,用于在单引号字符常量中表示单引号。
  8. 双引号(Double Quote)

    • \":表示一个双引号字符,用于在双引号字符串中表示双引号。
  9. 八进制转义序列

    • \ooo:表示一个八进制数,其中ooo是一个最多三位数的八进制数,用于表示对应的ASCII字符。
  10. 十六进制转义序列

    • \xhh:表示一个十六进制数,其中hh是一个最多两位数的十六进制数,用于表示对应的ASCII字符。

例如,在C语言中,你可以使用以下代码来表示一个包含换行符的字符串:

printf("Hello\nWorld");

这将输出:

Hello
World

总结一下,转义序列是一种在字符串和字符常量中表示特殊字符的方法,通过使用反斜杠(\)和特定的字符组合来实现。

static

在C语言中,static关键字有多种用途,主要用于控制变量和函数的存储类型和作用域。以下是static关键字的几种常见用法:

1. 静态局部变量

在函数内部使用static定义的变量称为静态局部变量。静态局部变量在程序运行期间只初始化一次,且其值在函数调用结束后仍然保留。

#include <stdio.h>

void increment() {
    static int count = 0;  // 静态局部变量
    count++;
    printf("Count: %d\n", count);
}

int main() {
    increment();  // 输出: Count: 1
    increment();  // 输出: Count: 2
    increment();  // 输出: Count: 3
    return 0;
}

在这个示例中,count是一个静态局部变量,每次调用increment函数时,count的值会保留并累加。

2. 静态全局变量

在文件作用域(即函数外部)使用static定义的变量称为静态全局变量。静态全局变量的作用域仅限于定义它的文件,不能被其他文件访问。

// file1.c
#include <stdio.h>

static int global_var = 10;  // 静态全局变量

void print_global_var() {
    printf("Global Var: %d\n", global_var);
}

// file2.c
#include <stdio.h>

extern int global_var;  // 尝试访问file1.c中的global_var会报错

void print_global_var_from_file2() {
    printf("Global Var from file2: %d\n", global_var);  // 错误
}

在这个示例中,global_var是一个静态全局变量,只能在file1.c中访问,不能被file2.c访问。

3. 静态函数

在函数声明前使用static定义的函数称为静态函数。静态函数的作用域仅限于定义它的文件,不能被其他文件调用。

// file1.c
#include <stdio.h>

static void static_function() {  // 静态函数
    printf("This is a static function.\n");
}

void call_static_function() {
    static_function();
}

// file2.c
#include <stdio.h>

extern void static_function();  // 尝试调用file1.c中的static_function会报错

void call_static_function_from_file2() {
    static_function();  // 错误
}

在这个示例中,static_function是一个静态函数,只能在file1.c中调用,不能被file2.c调用。

4. 静态成员变量(C++)

在C++中,static还可以用于定义类的静态成员变量。静态成员变量属于类,而不是类的实例,所有类的实例共享同一个静态成员变量。

// C++示例
#include <iostream>

class MyClass {
public:
    static int static_var;  // 静态成员变量声明
};

int MyClass::static_var = 0;  // 静态成员变量定义

int main() {
    MyClass::static_var = 10;
    std::cout << "Static Var: " << MyClass::static_var << std::endl;
    return 0;
}

在这个示例中,static_var是一个静态成员变量,属于MyClass类,而不是类的实例。

总结

  • 静态局部变量:在函数内部使用static定义,只初始化一次,值在函数调用结束后保留。
  • 静态全局变量:在文件作用域使用static定义,作用域仅限于定义它的文件。
  • 静态函数:在函数声明前使用static定义,作用域仅限于定义它的文件。
  • 静态成员变量(C++):在类中使用static定义,属于类,而不是类的实例。

通过合理使用static关键字,可以控制变量和函数的作用域和生命周期,提高代码的模块化和可维护性。

const

在C语言中,const关键字用于定义常量,即其值在初始化后不能被修改的变量。使用const可以提高代码的安全性和可读性,防止意外修改导致的不一致或错误。以下是const关键字在C语言中的几种常见用法:

1. 常量变量

通过将变量声明为const,可以确保其值在初始化后不能被修改。

const int MAX_SIZE = 100;

在这个例子中,MAX_SIZE被定义为一个常量,其值为100,之后不能被修改。

2. 常量指针

const关键字也可以用于指针,有几种不同的用法:

  • 指针指向的值不能修改

    int value = 10;
    const int *ptr = &value;  // 指针指向的值不能修改
    
  • 指针本身不能修改

    int value = 10;
    int *const ptr = &value;  // 指针本身不能修改,地址的指向不能修改
    
  • 指针和指针指向的值都不能修改

    int value = 10;
    const int *const ptr = &value;  // 指针和指针指向的值都不能修改
    

3. 常量函数参数

在函数参数中使用const,可以确保函数内部不会修改传入的参数。

void print(const int value) {
    printf("%d\n", value);
}

在这个例子中,print函数接受一个const int类型的参数,确保函数内部不会修改value的值。

4. 常量结构体成员

const关键字还可以用于结构体成员,确保结构体成员的值在初始化后不能被修改。

struct Point {
    const int x;
    const int y;
};

struct Point p = {10, 20};

在这个例子中,Point结构体的成员xy被声明为const,确保它们的值在初始化后不能被修改。

总结

使用const关键字的主要目的是:

  1. 防止变量被意外修改:通过将变量声明为常量,可以防止在代码中意外修改其值,从而提高代码的安全性。
  2. 提高代码可读性:常量变量的命名通常采用全大写字母,这样可以清晰地表明这是一个常量,提高代码的可读性和可维护性。
  3. 优化性能:在某些编译器中,常量可以进行优化,例如在编译时进行常量折叠,从而提高程序的性能。

通过合理使用const关键字,可以编写出更安全、更易读、更高效的C代码。

指针函数

指针函数,即返回值为指针的函数,是C语言中的一种高级特性。它们允许函数返回指向某种数据类型的指针,从而提供了一种灵活的方式来处理动态内存分配、数据结构和复杂的数据操作。指针函数的主要作用包括:

1. 动态内存分配

指针函数可以用于动态分配内存,并返回指向该内存的指针。这在需要根据运行时条件分配不同大小的内存时非常有用。

例如,假设你需要一个函数来分配一个整数数组,并返回指向该数组的指针:

#include <stdio.h>
#include <stdlib.h>

// 指针函数:动态分配整数数组
int* create_array(size_t size) {
    int* array = (int*)malloc(size * sizeof(int));
    if (array == NULL) {
        printf("Memory allocation failed\n");
        return NULL;
    }
    for (size_t i = 0; i < size; i++) {
        array[i] = i + 1;
    }
    return array;
}

int main() {
    size_t size = 5;
    int* array = create_array(size);
    if (array != NULL) {
        printf("Array elements: ");
        for (size_t i = 0; i < size; i++) {
            printf("%d ", array[i]);
        }
        printf("\n");
        free(array);  // 释放内存
    }
    return 0;
}

在这个例子中,create_array函数动态分配了一个整数数组,并返回指向该数组的指针。主函数中使用该指针访问和打印数组元素,最后释放分配的内存。

2. 返回复杂数据结构

指针函数可以用于返回复杂的数据结构,如结构体、链表、树等。这在需要返回多个相关值或复杂数据对象时非常有用。

例如,假设你需要一个函数来创建并返回一个链表节点:

#include <stdio.h>
#include <stdlib.h>

// 链表节点结构体
typedef struct Node {
    int data;
    struct Node* next;
} Node;

// 指针函数:创建链表节点
Node* create_node(int data) {
    Node* new_node = (Node*)malloc(sizeof(Node));
    if (new_node == NULL) {
        printf("Memory allocation failed\n");
        return NULL;
    }
    new_node->data = data;
    new_node->next = NULL;
    return new_node;
}

int main() {
    Node* head = create_node(1);
    if (head != NULL) {
        printf("Node created with data: %d\n", head->data);
        free(head);  // 释放内存
    }
    return 0;
}

在这个例子中,create_node函数动态分配了一个链表节点,并返回指向该节点的指针。主函数中使用该指针访问节点的数据,最后释放分配的内存。

3. 返回函数指针

指针函数还可以用于返回函数指针,这在需要根据条件或配置返回不同函数的情况下非常有用。

例如,假设你需要一个函数来根据操作符返回相应的计算函数:

#include <stdio.h>

// 函数原型
typedef int (*operation_func)(int, int);

// 加法函数
int add(int a, int b) {
    return a + b;
}

// 减法函数
int subtract(int a, int b) {
    return a - b;
}

// 指针函数:根据操作符返回相应的计算函数
operation_func get_operation(char op) {
    switch (op) {
        case '+':
            return add;
        case '-':
            return subtract;
        default:
            printf("Invalid operation\n");
            return NULL;
    }
}

int main() {
    char op = '+';
    int a = 10, b = 5;
    operation_func op_func = get_operation(op);
    if (op_func != NULL) {
        int result = op_func(a, b);
        printf("Result: %d\n", result);
    }
    return 0;
}

在这个例子中,get_operation函数根据操作符返回相应的计算函数指针。主函数中使用该指针调用相应的计算函数,并打印结果。

总结

指针函数在C语言中具有多种用途,包括:

  1. 动态内存分配:允许函数动态分配内存,并返回指向该内存的指针。
  2. 返回复杂数据结构:允许函数返回复杂的数据结构,如结构体、链表、树等。
  3. 返回函数指针:允许函数根据条件或配置返回不同的函数指针。

通过合理使用指针函数,可以编写出更灵活、更高效的C代码。

函数指针

函数指针是C语言中一个强大且灵活的特性,它允许你将函数作为参数传递给其他函数,或者将函数存储在数据结构中。函数指针的主要作用包括:

1. 回调函数

回调函数是一种通过函数指针调用的函数。当你将一个函数的指针传递给另一个函数时,后者可以在需要的时候调用前者。回调函数常用于事件处理、异步编程和库函数的设计中。

例如,假设你有一个排序函数,它接受一个比较函数的指针作为参数:

#include <stdio.h>

// 比较函数原型
typedef int (*compare_func)(const void *, const void *);

// 排序函数
void sort(int *array, size_t size, compare_func cmp) {
    // 简单的冒泡排序
    for (size_t i = 0; i < size - 1; i++) {
        for (size_t j = 0; j < size - 1 - i; j++) {
            if (cmp(&array[j], &array[j + 1]) > 0) {
                int temp = array[j];
                array[j] = array[j + 1];
                array[j + 1] = temp;
            }
        }
    }
}

// 比较函数示例:升序排序
int compare_asc(const void *a, const void *b) {
    return (*(int *)a - *(int *)b);
}

// 比较函数示例:降序排序
int compare_desc(const void *a, const void *b) {
    return (*(int *)b - *(int *)a);
}

int main() {
    int array[] = {5, 2, 9, 1, 5, 6};
    size_t size = sizeof(array) / sizeof(array[0]);

    // 使用升序比较函数排序
    sort(array, size, compare_asc);
    printf("Ascending order: ");
    for (size_t i = 0; i < size; i++) {
        printf("%d ", array[i]);
    }
    printf("\n");

    // 使用降序比较函数排序
    sort(array, size, compare_desc);
    printf("Descending order: ");
    for (size_t i = 0; i < size; i++) {
        printf("%d ", array[i]);
    }
    printf("\n");

    return 0;
}

在这个例子中,sort函数接受一个比较函数的指针cmp,并使用它来决定元素的顺序。通过传递不同的比较函数,可以实现不同的排序顺序。

2. 动态函数调用

函数指针允许你在运行时决定调用哪个函数。这在需要根据条件或配置选择不同实现的情况下非常有用。

例如,假设你有一个计算器程序,可以根据用户输入选择不同的操作:

#include <stdio.h>

// 函数原型
typedef int (*operation_func)(int, int);

// 加法函数
int add(int a, int b) {
    return a + b;
}

// 减法函数
int subtract(int a, int b) {
    return a - b;
}

// 乘法函数
int multiply(int a, int b) {
    return a * b;
}

// 除法函数
int divide(int a, int b) {
    if (b == 0) {
        printf("Error: Division by zero\n");
        return 0;
    }
    return a / b;
}

int main() {
    operation_func op;
    int a = 10, b = 5;
    char choice;

    printf("Enter operation (+, -, *, /): ");
    scanf(" %c", &choice);

    switch (choice) {
        case '+':
            op = add;
            break;
        case '-':
            op = subtract;
            break;
        case '*':
            op = multiply;
            break;
        case '/':
            op = divide;
            break;
        default:
            printf("Invalid choice\n");
            return 1;
    }

    int result = op(a, b);
    printf("Result: %d\n", result);

    return 0;
}

在这个例子中,根据用户输入的操作符,选择不同的函数指针op,然后调用该函数进行计算。

3. 函数表

函数指针可以用于创建函数表,即一个包含多个函数指针的数组。这在需要根据索引或键值选择不同函数的情况下非常有用。

例如,假设你有一个菜单程序,可以根据用户输入选择不同的功能:

#include <stdio.h>

// 函数原型
typedef void (*menu_func)();

// 菜单项函数
void option1() {
    printf("You selected option 1\n");
}

void option2() {
    printf("You selected option 2\n");
}

void option3() {
    printf("You selected option 3\n");
}

int main() {
    menu_func menu[] = {option1, option2, option3};
    size_t menu_size = sizeof(menu) / sizeof(menu[0]);
    int choice;

    printf("Enter option (1-%zu): ", menu_size);
    scanf("%d", &choice);

    if (choice > 0 && choice <= menu_size) {
        menu[choice - 1]();
    } else {
        printf("Invalid choice\n");
    }

    return 0;
}

在这个例子中,menu数组包含多个函数指针,根据用户输入的选项,调用相应的函数。

总结

函数指针在C语言中具有多种用途,包括:

  1. 回调函数:允许将函数作为参数传递给其他函数,实现灵活的事件处理和异步编程。
  2. 动态函数调用:在运行时决定调用哪个函数,实现动态行为和配置。
  3. 函数表:创建包含多个函数指针的数组,根据索引或键值选择不同函数。

通过合理使用函数指针,可以编写出更灵活、更模块化的C代码。

链表

链表(Linked List)是C语言中一种常见的动态数据结构,它由一系列节点(Node)组成,每个节点包含数据部分和指向下一个节点的指针。链表的主要优点是可以在运行时动态地分配和释放内存,适用于需要频繁插入和删除元素的场景。

链表的基本结构

链表的基本结构包括:

  1. 节点(Node):链表的基本单元,包含数据部分和指向下一个节点的指针。
  2. 头指针(Head Pointer):指向链表的第一个节点的指针。
  3. 尾指针(Tail Pointer):指向链表的最后一个节点的指针(可选)。

单向链表的实现

以下是一个简单的单向链表的实现示例:

#include <stdio.h>
#include <stdlib.h>

// 定义链表节点结构体
typedef struct Node {
    int data;           // 数据部分
    struct Node* next;  // 指向下一个节点的指针
} Node;

// 创建新节点
Node* create_node(int data) {
    Node* new_node = (Node*)malloc(sizeof(Node));
    if (new_node == NULL) {
        printf("Memory allocation failed\n");
        return NULL;
    }
    new_node->data = data;
    new_node->next = NULL;
    return new_node;
}

// 在链表末尾插入节点
void append_node(Node** head, int data) {
    Node* new_node = create_node(data);
    if (new_node == NULL) {
        return;
    }
    if (*head == NULL) {
        *head = new_node;
    } else {
        Node* temp = *head;
        while (temp->next != NULL) {
            temp = temp->next;
        }
        temp->next = new_node;
    }
}

// 打印链表
void print_list(Node* head) {
    Node* temp = head;
    while (temp != NULL) {
        printf("%d -> ", temp->data);
        temp = temp->next;
    }
    printf("NULL\n");
}

// 释放链表内存
void free_list(Node* head) {
    Node* temp;
    while (head != NULL) {
        temp = head;
        head = head->next;
        free(temp);
    }
}

int main() {
    Node* head = NULL;  // 初始化头指针

    // 在链表末尾插入节点
    append_node(&head, 1);
    append_node(&head, 2);
    append_node(&head, 3);

    // 打印链表
    print_list(head);

    // 释放链表内存
    free_list(head);

    return 0;
}

双向链表的实现

双向链表(Doubly Linked List)每个节点包含指向前一个节点和后一个节点的指针。以下是一个简单的双向链表的实现示例:

#include <stdio.h>
#include <stdlib.h>

// 定义双向链表节点结构体
typedef struct Node {
    int data;           // 数据部分
    struct Node* prev;  // 指向前一个节点的指针
    struct Node* next;  // 指向下一个节点的指针
} Node;

// 创建新节点
Node* create_node(int data) {
    Node* new_node = (Node*)malloc(sizeof(Node));
    if (new_node == NULL) {
        printf("Memory allocation failed\n");
        return NULL;
    }
    new_node->data = data;
    new_node->prev = NULL;
    new_node->next = NULL;
    return new_node;
}

// 在链表末尾插入节点
void append_node(Node** head, int data) {
    Node* new_node = create_node(data);
    if (new_node == NULL) {
        return;
    }
    if (*head == NULL) {
        *head = new_node;
    } else {
        Node* temp = *head;
        while (temp->next != NULL) {
            temp = temp->next;
        }
        temp->next = new_node;
        new_node->prev = temp;
    }
}

// 打印链表
void print_list(Node* head) {
    Node* temp = head;
    while (temp != NULL) {
        printf("%d -> ", temp->data);
        temp = temp->next;
    }
    printf("NULL\n");
}

// 释放链表内存
void free_list(Node* head) {
    Node* temp;
    while (head != NULL) {
        temp = head;
        head = head->next;
        free(temp);
    }
}

int main() {
    Node* head = NULL;  // 初始化头指针

    // 在链表末尾插入节点
    append_node(&head, 1);
    append_node(&head, 2);
    append_node(&head, 3);

    // 打印链表
    print_list(head);

    // 释放链表内存
    free_list(head);

    return 0;
}

链表的操作

链表常见的操作包括:

  1. 插入节点:在链表的头部、尾部或中间插入新节点。
  2. 删除节点:从链表中删除指定节点。
  3. 查找节点:在链表中查找具有特定值的节点。
  4. 遍历链表:依次访问链表中的每个节点。

总结

链表是C语言中一种灵活的动态数据结构,适用于需要频繁插入和删除元素的场景。通过合理使用链表,可以编写出高效、灵活的程序。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值