C语言 - 语法和难点汇总

目录

一:语法介绍

[1. C90 标准语法特性介绍](#1. C90 标准语法特性介绍)

[2. C99 标准语法特性介绍](#2. C99 标准语法特性介绍)

[3. C11 标准语法特性介绍](#3. C11 标准语法特性介绍)

二:使用函数指针

三:使用数组与指针

四:使用C库函数注意事项:

五:使用C语言宏定义时的注意事项


一:语法介绍

1. C90 标准语法特性介绍

1.1 :C90 定义了几种基本数据类型,如 intcharfloatdouble

cpp 复制代码
#include <stdio.h>

int main() {
    int a = 5;
    char c = 'A';
    float f = 3.14f;
    double d = 2.718281;

    printf("int: %d, char: %c, float: %.2f, double: %.6f\n", a, c, f, d);
    return 0;
}

1.2 : C90 支持定义结构体,用于组合不同类型的数据。

cpp 复制代码
#include <stdio.h>

struct Person {
    char name[50];
    int age;
};

int main() {
    struct Person john = {"Alice", 30};
    printf("Name: %s, Age: %d\n", john.name, john.age);
    return 0;
}

1.3 : C90 提供了指针的使用,可以通过指针直接操作内存地址。

cpp 复制代码
#include <stdio.h>

int main() {
    int a = 10;
    int *ptr = &a;

    printf("Value: %d, Address: %p\n", *ptr, (void*)ptr);
    return 0;
}

1.4: C90 允许定义数组,支持一维和多维数组。

cpp 复制代码
#include <stdio.h>

int main() {
    int arr[3] = {1, 2, 3};
    for (int i = 0; i < 3; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
    return 0;
}

1.5: C90 允许函数的定义和调用,支持参数传递。

cpp 复制代码
#include <stdio.h>

void greet() {
    printf("Hello, World!\n");
}

int main() {
    greet();
    return 0;
}

1.6: C90 使用预处理器指令,如 #define#include

cpp 复制代码
#include <stdio.h>

#define PI 3.14

int main() {
    printf("PI: %.2f\n", PI);
    return 0;
}

1.7: C90 包含基本的控制结构,如 ifforwhile

cpp 复制代码
#include <stdio.h>

int main() {
    for (int i = 0; i < 5; i++) {
        printf("%d ", i);
    }
    printf("\n");
    return 0;
}

1.8 :可以使用 typedef 定义新类型,以简化代码。

cpp 复制代码
#include <stdio.h>

typedef unsigned long ulong;

int main() {
    ulong num = 1000000;
    printf("Number: %lu\n", num);
    return 0;
}

1.9 : C90 引入了块作用域的概念,局部变量在其所在的块内有效。

cpp 复制代码
#include <stdio.h>

int main() {
    int x = 10;
    {
        int x = 20; // 新的 x 变量在这个块内
        printf("Inner x: %d\n", x);
    }
    printf("Outer x: %d\n", x); // 访问外部 x
    return 0;
}

2. C99 标准语法特性介绍

2.1: C99 允许在任何代码块内声明变量,而不是只能在函数的开头。

cpp 复制代码
#include <stdio.h>

int main() {
    for (int i = 0; i < 5; i++) {
        int square = i * i; // 在 for 循环内声明
        printf("Square of %d is %d\n", i, square);
    }
    return 0;
}

2.2: C99 引入复合字面量,允许直接在初始化时创建复杂类型的值。

cpp 复制代码
#include <stdio.h>

struct Point {
    int x;
    int y;
};

int main() {
    struct Point p = (struct Point){10, 20}; // 复合字面量
    printf("Point: (%d, %d)\n", p.x, p.y);
    return 0;
}

2.3: C99 引入了 inline 关键字,允许定义内联函数以提高效率。

cpp 复制代码
#include <stdio.h>

inline int square(int x) {
    return x * x;
}

int main() {
    printf("Square of 5: %d\n", square(5));
    return 0;
}

2.4: C99 允许使用 // 开头的单行注释,提供了更简洁的注释方式。

cpp 复制代码
#include <stdio.h>

int main() {
    int a = 5; // 这是一个单行注释
    printf("Value: %d\n", a);
    return 0;
}

2.5: C99 支持可变参数宏,允许宏接受不定数量的参数。

cpp 复制代码
#include <stdio.h>

#define PRINT(fmt, ...) printf(fmt, __VA_ARGS__)

int main() {
    PRINT("Values: %d, %f\n", 10, 3.14);
    return 0;
}

2.6: C99 允许通过指定成员名来初始化结构体和数组。

cpp 复制代码
#include <stdio.h>

struct Person {
    char name[50];
    int age;
};

int main() {
    struct Person john = {.age = 30, .name = "Alice"}; // 指定初始化
    printf("Name: %s, Age: %d\n", john.name, john.age);
    return 0;
}

2.7: C99 引入了新的数据类型,例如 long long int_Bool

cpp 复制代码
#include <stdio.h>

int main() {
    long long int bigNum = 12345678901234LL; // long long int
    _Bool flag = 1; // _Bool 类型

    printf("Big Number: %lld, Flag: %d\n", bigNum, flag);
    return 0;
}

2.8: C99 引入了变长数组,允许数组的大小在运行时决定。

cpp 复制代码
#include <stdio.h>

int main() {
    int n;
    printf("Enter size: ");
    scanf("%d", &n);

    int arr[n]; // 变长数组
    for (int i = 0; i < n; i++) {
        arr[i] = i * i;
    }

    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
    return 0;
}

2.9: restrict 关键字用于指针,以提高编译器优化的潜力。

cpp 复制代码
#include <stdio.h>

void add(int *restrict a, int *restrict b, int *restrict result) {
    *result = *a + *b;
}

int main() {
    int x = 10, y = 20, sum;
    add(&x, &y, &sum);
    printf("Sum: %d\n", sum);
    return 0;
}

3. C11 标准语法特性介绍

3.1: C11 引入了对多线程的支持,包括线程创建、互斥量和条件变量等。

cpp 复制代码
#include <stdio.h>
#include <threads.h>

int threadFunction(void* arg) {
    int* num = (int*)arg;
    printf("Hello from thread! Number: %d\n", *num);
    return 0; // 返回 0 表示线程结束
}

int main() {
    thrd_t thread;
    int num = 42;

    if (thrd_create(&thread, threadFunction, &num) == thrd_success) {
        thrd_join(thread, NULL); // 等待线程结束
    } else {
        printf("Failed to create thread\n");
    }

    return 0;
}


#include <stdio.h>
#include <threads.h>

mtx_t mutex; // 定义互斥量
int sharedResource = 0;

int threadFunction(void* arg) {
    for (int i = 0; i < 5; i++) {
        mtx_lock(&mutex); // 上锁
        sharedResource++;
        printf("Thread %d: sharedResource = %d\n", *(int*)arg, sharedResource);
        mtx_unlock(&mutex); // 解锁
    }
    return 0;
}

int main() {
    thrd_t threads[2];
    int ids[2] = {1, 2};

    mtx_init(&mutex, mtx_plain); // 初始化互斥量

    // 创建线程
    for (int i = 0; i < 2; i++) {
        thrd_create(&threads[i], threadFunction, &ids[i]);
    }

    // 等待线程结束
    for (int i = 0; i < 2; i++) {
        thrd_join(threads[i], NULL);
    }

    mtx_destroy(&mutex); // 销毁互斥量
    return 0;
}



#include <stdio.h>
#include <threads.h>

mtx_t mutex; // 定义互斥量
cnd_t cond; // 定义条件变量
int ready = 0;

int threadFunction(void* arg) {
    mtx_lock(&mutex); // 上锁
    while (!ready) {
        cnd_wait(&cond, &mutex); // 等待条件变量
    }
    printf("Thread %d: Ready!\n", *(int*)arg);
    mtx_unlock(&mutex); // 解锁
    return 0;
}

int main() {
    thrd_t thread;
    int id = 1;

    mtx_init(&mutex, mtx_plain); // 初始化互斥量
    cnd_init(&cond); // 初始化条件变量

    thrd_create(&thread, threadFunction, &id); // 创建线程

    // 模拟一些工作
    printf("Main thread is doing some work...\n");
    ready = 1; // 设置条件
    cnd_signal(&cond); // 唤醒等待的线程

    thrd_join(thread, NULL); // 等待线程结束

    cnd_destroy(&cond); // 销毁条件变量
    mtx_destroy(&mutex); // 销毁互斥量
    return 0;
}



#include <stdio.h>
#include <stdatomic.h>
#include <threads.h>

atomic_int counter; // 定义原子整型

int threadFunction(void* arg) {
    for (int i = 0; i < 1000; i++) {
        atomic_fetch_add(&counter, 1); // 原子加法
    }
    return 0;
}

int main() {
    thrd_t threads[5];

    atomic_init(&counter, 0); // 初始化原子变量

    // 创建线程
    for (int i = 0; i < 5; i++) {
        thrd_create(&threads[i], threadFunction, NULL);
    }

    // 等待线程结束
    for (int i = 0; i < 5; i++) {
        thrd_join(threads[i], NULL);
    }

    printf("Final counter value: %d\n", atomic_load(&counter)); // 输出最终计数值
    return 0;
}

3.2: C11 引入了对对齐的支持,可以使用 alignasalignof 来指定和查询类型的对齐要求

cpp 复制代码
#include <stdio.h>
#include <stdalign.h>

struct S {
    char c;
    int i;
};

int main() {
    printf("Alignment of int: %zu\n", alignof(int)); // 查询 int 的对齐要求
    return 0;
}

3.3: 可以使用静态断言检查数组大小是否符合预期。

cpp 复制代码
#include <stdio.h>

#define ARRAY_SIZE 10
int arr[ARRAY_SIZE];

// 确保数组大小为 10
_Static_assert(ARRAY_SIZE == 10, "Array size must be 10");

int main() {
    printf("Array size: %zu\n", sizeof(arr) / sizeof(arr[0]));
    return 0;
}

二:使用函数指针

  1. 函数指针声明
cpp 复制代码
原型:

    return_type (*pointer_name)(parameter_types);

例子:

    int (*func_ptr)(int, int);
  1. 函数指针赋值
cpp 复制代码
int add(int a, int b) {
    return a + b;
}

func_ptr = add;  // 赋值函数地址
  1. 函数指针使用
cpp 复制代码
int result = (*func_ptr)(arg1, arg2);

或者

int result = func_ptr(arg1, arg2);

完整例子:

#include <stdio.h>

// 函数声明
int add(int a, int b) {
    return a + b;
}

int subtract(int a, int b) {
    return a - b;
}

int main() {
    // 声明函数指针
    int (*operation)(int, int);

    // 指向 add 函数
    operation = add;
    printf("Add: %d\n", operation(5, 3));  // 输出: Add: 8

    // 指向 subtract 函数
    operation = subtract;
    printf("Subtract: %d\n", operation(5, 3));  // 输出: Subtract: 2

    return 0;
}
  1. 函数指针注意事项:
cpp 复制代码
1. 函数指针的参数类型和返回类型必须与所指向的函数匹配。
2. 使用函数指针时要小心,确保指向有效的函数地址,避免空指针或未定义行为。

三:使用数组与指针

  1. 数组指针与指针数组的区别:

数组指针的定义: 数组指针是指向整个数组的指针,通常用于多维数组或动态数组的情况。

cpp 复制代码
声明:
    type (*pointer_name)[size];

示例:
    int (*arr_ptr)[3];

完整例子:
    
#include <stdio.h>

int main() {
    int arr[3] = {1, 2, 3};
    int (*arr_ptr)[3] = &arr; // arr_ptr 指向 arr

    printf("%d\n", (*arr_ptr)[1]); // 输出: 2
    return 0;
}

指针数组的定义: 指针数组是一个数组,其中每个元素都是指向某种类型的指针。它通常用于存储多个指向不同变量或动态分配内存的指针。

cpp 复制代码
声明:
    type *pointer_name[size];

例子:
    int *ptr_arr[3];

示例:

#include <stdio.h>

int main() {
    int a = 1, b = 2, c = 3;
    int *ptr_arr[3]; // 声明一个指针数组

    ptr_arr[0] = &a; // 第一个指针指向 a
    ptr_arr[1] = &b; // 第二个指针指向 b
    ptr_arr[2] = &c; // 第三个指针指向 c

    for (int i = 0; i < 3; i++) {
        printf("%d\n", *ptr_arr[i]); // 输出: 1 2 3
    }
    return 0;
}
  1. 指向数组的指针:
cpp 复制代码
int arr[3] = {1, 2, 3};
int *p = arr;
p[0] = 10;  // 可以修改 arr[0]
// arr = p;  // 错误: 数组名不可修改
  1. 指针算数运算: 指针加法和减法会根据类型进行移动,初学者常常会混淆
cpp 复制代码
int arr[3] = {1, 2, 3};
int *p = arr;  // p 指向 arr[0]
printf("%d\n", *(p + 1)); // 输出: 2 (移动到 arr[1])
  1. 多维数组与指针
cpp 复制代码
#include <stdio.h>

int main() {
    int arr[2][3] = {{1, 2, 3}, {4, 5, 6}};
    int (*ptr)[3] = arr; // ptr 是指向包含 3 个 int 的数组的指针

    printf("%d\n", (*ptr)[1]); // 输出: 2
    printf("%d\n", ptr[1][0]); // 输出: 4
    return 0;
}
  1. 指针的指针(多维数组的另一种表示)
cpp 复制代码
#include <stdio.h>
#include <stdlib.h>

int main() {
    int rows = 2, cols = 3;
    int **arr = malloc(rows * sizeof(int *)); // 动态分配指针数组
    for (int i = 0; i < rows; i++) {
        arr[i] = malloc(cols * sizeof(int)); // 每个指针指向一个数组
    }

    // 赋值
    int value = 1;
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            arr[i][j] = value++;
        }
    }

    // 打印
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            printf("%d ", arr[i][j]);
        }
        printf("\n");
    }

    // 释放内存
    for (int i = 0; i < rows; i++) {
        free(arr[i]);
    }
    free(arr);

    return 0;
}
  1. 指针与字符数组:字符串在 C 中以字符数组形式存在,指针操作时要注意空字符 '\0'
cpp 复制代码
char str[] = "Hello";
char *p = str;  // 指向字符串的首字符
while (*p != '\0') {
    printf("%c ", *p); // 输出: H e l l o
    p++;
}

四:使用C库函数注意事项:

  1. 在使用字符串处理函数时,未正确分配缓冲区可能导致缓冲区溢出。
cpp 复制代码
#include <stdio.h>
#include <string.h>

int main() {
    char buffer[10];
    strcpy(buffer, "This string is too long!"); // 超出缓冲区长度,导致溢出
    printf("%s\n", buffer);
    return 0;
}
  1. 在使用某些库函数时,未初始化的变量可能导致未定义行为。
cpp 复制代码
#include <stdio.h>
#include <string.h>

int main() {
    char str[10];
    // 未初始化,调用 strlen 可能导致未定义行为
    printf("Length: %zu\n", strlen(str)); // 使用未初始化的字符串
    return 0;
}
  1. 许多库函数返回值用于指示成功与否,未检查返回值可能导致程序运行错误。
cpp 复制代码
#include <stdio.h>
#include <stdlib.h>

int main() {
    FILE *file = fopen("nonexistent.txt", "r");
    // 未检查 fopen 的返回值
    char buffer[100];
    fread(buffer, sizeof(char), 99, file); // 可能导致崩溃
    fclose(file);
    return 0;
}
  1. 使用浮点数时,直接比较可能会产生意外结果,因为浮点数精度有限。
cpp 复制代码
#include <stdio.h>

int main() {
    float a = 0.1f + 0.2f;
    if (a == 0.3f) {
        printf("Equal\n"); // 可能不会打印,因为浮点数比较问题
    } else {
        printf("Not equal\n");
    }
    return 0;
}
  1. 某些库函数在多线程环境中可能不是线程安全的,导致数据竞争。
cpp 复制代码
#include <stdio.h>
#include <pthread.h>
#include <string.h>

char global_buffer[100];

void *thread_func(void *arg) {
    strcat(global_buffer, "Thread"); // 非线程安全
    return NULL;
}

int main() {
    pthread_t t1, t2;
    pthread_create(&t1, NULL, thread_func, NULL);
    pthread_create(&t2, NULL, thread_func, NULL);
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    printf("%s\n", global_buffer); // 输出结果不确定
    return 0;
}

五:使用C语言宏定义时的注意事项

  1. 使用宏时,尤其是涉及参数的宏,可能会导致意想不到的副作用。要用括号将参数包起来。
cpp 复制代码
#include <stdio.h>

#define SQUARE(x) (x * x)

int main() {
    int result = SQUARE(5 + 1); // 期望结果是 36,但展开后是 (5 + 1 * 5 + 1),结果是 11
    printf("Result: %d\n", result); // 输出: Result: 11
    return 0;
}
  1. 宏在展开时不会进行类型检查,可能导致类型不匹配的问题。
cpp 复制代码
#include <stdio.h>

#define MAX(a, b) ((a) > (b) ? (a) : (b))

int main() {
    printf("Max: %d\n", MAX(10, 20)); // 正常
    printf("Max: %d\n", MAX(3.5, 2.1)); // 可能产生警告或错误
    return 0;
}
  1. 如果不小心定义了多个宏或使用了相同的名称,可能导致宏冲突。
cpp 复制代码
#include <stdio.h>

#define VALUE 10
#define VALUE 20 // 重新定义,会导致编译错误

int main() {
    printf("Value: %d\n", VALUE);
    return 0;
}
  1. 在宏定义中,逗号的使用可能会导致解析错误。
cpp 复制代码
#include <stdio.h>

#define FUNC(x, y) (x + y)

int main() {
    int a = 5, b = 10;
    printf("Result: %d\n", FUNC(a, b)); // 正常
    printf("Result: %d\n", FUNC(a, 2 * b)); // 正常,但可能会引入歧义
    return 0;
}
  1. 预处理指令的顺序可能影响编译结果,特别是在包含头文件和宏定义时。
cpp 复制代码
#include <stdio.h>

#define DEBUG

#ifdef DEBUG
#define LOG(msg) printf("DEBUG: %s\n", msg)
#else
#define LOG(msg) // 没有输出
#endif

int main() {
    LOG("This is a debug message."); // 如果没有 #define DEBUG,则会导致无输出
    return 0;
}
  1. 使用 #define 定义常量可能会导致调试困难,建议使用 constenum
cpp 复制代码
#include <stdio.h>

#define PI 3.14 // 预处理器常量定义

int main() {
    printf("Value of PI: %f\n", PI);
    return 0;
}
相关推荐
wdxylb14 分钟前
Linux下编写第一个bash脚本
开发语言·chrome·bash
幽兰的天空17 分钟前
Python实现的简单时钟
开发语言·python
这题怎么做?!?25 分钟前
模板方法模式
开发语言·c++·算法
幽兰的天空1 小时前
简单的Python爬虫实例
开发语言·爬虫·python
TeYiToKu1 小时前
笔记整理—linux驱动开发部分(1)驱动梗概
linux·c语言·arm开发·驱动开发·嵌入式硬件
冷眼看人间恩怨1 小时前
【Java】揭秘网络编程:深入探索其无尽奥秘与魅力
java·开发语言·tcp/ip·udp·tcp
※※冰馨※※1 小时前
Unity3D 鼠标移动到按钮上显示信息
开发语言·unity·c#
Algorithm15762 小时前
JVM是什么,与Java的关系是什么,以及JVM怎么实现的跨平台性
java·开发语言·jvm
Gnevergiveup2 小时前
2024网鼎杯青龙组Web+Misc部分WP
开发语言·前端·python
DdddJMs__1352 小时前
C语言 | Leetcode C语言题解之第517题超级洗衣机
c语言·leetcode·题解