目录
[1. C90 标准语法特性介绍](#1. C90 标准语法特性介绍)
[2. C99 标准语法特性介绍](#2. C99 标准语法特性介绍)
[3. C11 标准语法特性介绍](#3. C11 标准语法特性介绍)
一:语法介绍
1. C90 标准语法特性介绍
1.1 :C90 定义了几种基本数据类型,如 int
、char
、float
和 double
。
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 包含基本的控制结构,如 if
、for
和 while
。
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 引入了对对齐的支持,可以使用 alignas
和 alignof
来指定和查询类型的对齐要求
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;
}
二:使用函数指针
- 函数指针声明
cpp
原型:
return_type (*pointer_name)(parameter_types);
例子:
int (*func_ptr)(int, int);
- 函数指针赋值
cpp
int add(int a, int b) {
return a + b;
}
func_ptr = add; // 赋值函数地址
- 函数指针使用
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;
}
- 函数指针注意事项:
cpp
1. 函数指针的参数类型和返回类型必须与所指向的函数匹配。
2. 使用函数指针时要小心,确保指向有效的函数地址,避免空指针或未定义行为。
三:使用数组与指针
- 数组指针与指针数组的区别:
数组指针的定义: 数组指针是指向整个数组的指针,通常用于多维数组或动态数组的情况。
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;
}
- 指向数组的指针:
cpp
int arr[3] = {1, 2, 3};
int *p = arr;
p[0] = 10; // 可以修改 arr[0]
// arr = p; // 错误: 数组名不可修改
- 指针算数运算: 指针加法和减法会根据类型进行移动,初学者常常会混淆
cpp
int arr[3] = {1, 2, 3};
int *p = arr; // p 指向 arr[0]
printf("%d\n", *(p + 1)); // 输出: 2 (移动到 arr[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;
}
- 指针的指针(多维数组的另一种表示)
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;
}
- 指针与字符数组:字符串在 C 中以字符数组形式存在,指针操作时要注意空字符
'\0'
。
cpp
char str[] = "Hello";
char *p = str; // 指向字符串的首字符
while (*p != '\0') {
printf("%c ", *p); // 输出: H e l l o
p++;
}
四:使用C库函数注意事项:
- 在使用字符串处理函数时,未正确分配缓冲区可能导致缓冲区溢出。
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;
}
- 在使用某些库函数时,未初始化的变量可能导致未定义行为。
cpp
#include <stdio.h>
#include <string.h>
int main() {
char str[10];
// 未初始化,调用 strlen 可能导致未定义行为
printf("Length: %zu\n", strlen(str)); // 使用未初始化的字符串
return 0;
}
- 许多库函数返回值用于指示成功与否,未检查返回值可能导致程序运行错误。
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;
}
- 使用浮点数时,直接比较可能会产生意外结果,因为浮点数精度有限。
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;
}
- 某些库函数在多线程环境中可能不是线程安全的,导致数据竞争。
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语言宏定义时的注意事项
- 使用宏时,尤其是涉及参数的宏,可能会导致意想不到的副作用。要用括号将参数包起来。
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;
}
- 宏在展开时不会进行类型检查,可能导致类型不匹配的问题。
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;
}
- 如果不小心定义了多个宏或使用了相同的名称,可能导致宏冲突。
cpp
#include <stdio.h>
#define VALUE 10
#define VALUE 20 // 重新定义,会导致编译错误
int main() {
printf("Value: %d\n", VALUE);
return 0;
}
- 在宏定义中,逗号的使用可能会导致解析错误。
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;
}
- 预处理指令的顺序可能影响编译结果,特别是在包含头文件和宏定义时。
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;
}
- 使用
#define
定义常量可能会导致调试困难,建议使用const
或enum
。
cpp
#include <stdio.h>
#define PI 3.14 // 预处理器常量定义
int main() {
printf("Value of PI: %f\n", PI);
return 0;
}