【C语言】章节练习+注意点
·
练习题目
题目1:数据类型验证
#include <stdio.h>
#include <stdbool.h>
int main(void)
{
printf("sizeof(short) = %zu\n", sizeof(short));
printf("sizeof(int) = %zu\n", sizeof(int));
printf("sizeof(long) = %zu\n", sizeof(long));
printf("sizeof(float) = %zu\n", sizeof(float));
printf("sizeof(double) = %zu\n", sizeof(double));
printf("sizeof(char) = %zu\n", sizeof(char));
printf("sizeof(bool) = %zu\n", sizeof(bool));
return 0;
}
qq@ubuntu:~/c$ ./a.out
sizeof(short) = 2
sizeof(int) = 4
sizeof(long) = 8
sizeof(float) = 4
sizeof(double) = 8
sizeof(char) = 1
sizeof(bool) = 1
思考:运行后分析输出结果是否与理论值一致?哪些类型受操作系统影响?
题目2:混合类型计算
#include <stdio.h>
int main(void)
{
int a = 0;
float b = 3.5;
char c = 'A';
float result1 = a + b * c;
int result2 = (int)(b) + c;
printf("result1: %.2f\n", result1);
printf("result2: %d\n", result2);
return 0;
}
qq@ubuntu:~/c$ ./a.out
result1: 227.50
result2: 68
关键点:
- 1.
b * c中char先转为int还是float? - 2.强制转换
(int)b会丢失什么信息?
题目3:运算符优先级验证
#include <stdio.h>
int main(void)
{
int x = 5, y = 3, z = 2;
int result1 = x * y / z + 2;
int result2 = x * (y / z) + 2;
int result3 = x = y = z + 1;
printf("result1: %d\n", result1);
printf("result2: %d\n", result2);
printf("result3: %d\n", result3);
return 0;
}
qq@ubuntu:~/c$ ./a.out
result1: 9
result2: 7
result3: 3
陷阱解析:
- •
result1:*和/优先级相同,从左到右计算 - •
result3:赋值运算符从右向左结合
题目4:综合应用(圆计算)
#include <stdio.h>
#define PI 3.14
int main(void)
{
float radius = 0;
printf("enter circle radius: ");
scanf("%f", &radius);
float circumference = 2 * PI * radius;
float area = PI * radius * radius;
printf("circumference: %.2f\n", circumference);
printf("area: %.2f\n", area);
int intarea = (int)area;
printf("truncted area: %d\n", intarea);
return 0;
}
qq@ubuntu:~/c$ ./a.out
enter circle radius: 2
circumference: 12.56
area: 12.56
truncted area: 12
qq@ubuntu:~/c$ ./a.out
enter circle radius: 3
circumference: 18.84
area: 28.26
truncted area: 28
题目1:基本输入输出
#include <stdio.h>
int main()
{
int age;
float height;
char initial;
printf("Enter your age, height(m), and initial: ");
scanf("%d %f %c", &age, &height, &initial);
printf("\n--- Your Profile ---\n");
printf("Age: %d\n", age);
printf("Height: %.2f meters\n", height);
printf("Initial: %c\n", initial);
return 0;
}
测试用例:
qq@ubuntu:~/c/2$ ./a.out
Enter your age, height(m), and initial: 20 176.43 l
--- Your Profile ---
Age: 20
Height: 176.43 meters
Initial: l
题目2:输入缓冲区问题
#include <stdio.h>
int main()
{
int num = 0;
char ch = 0;
printf("enter a number: ");
scanf("%d", &num);
while(getchar() != '\n');
printf("enter a character: ");
ch = getchar();
printf("\nnumber: %d, character: %c\n", num, ch);
return 0;
}
qq@ubuntu:~/c/2$ ./a.out
enter a number: 12
enter a character: a
number: 12, character: a
这是去除 while(getchar() != '\n');
qq@ubuntu:~/c/2$ ./a.out
enter a number: 12
enter a character:
number: 12, character:
问题分析:
- 1.如果不处理缓冲区,输入数字后的回车会被
ch读取 - 2.使用
while(getchar() != '\n')清空缓冲区 - 3.测试输入:
42然后按回车,再输入A
为什么printf能正确显示提示:
(printf 是行缓冲,只有 \n 或一行才可以,为何这段代码可以直接输出)
- 程序在
printf("Enter a character: ");后立即调用getchar() - 等待输入的操作触发了输出缓冲区刷新
- 不需要显式
fflush(stdout)
题目3:字符串安全输入
#include <stdio.h>
int main() {
char name[30];
printf("Enter your name (max 29 chars): ");
fgets(name, sizeof(name), stdin);
// 移除fgets可能读取的换行符
if(name[strlen(name)-1] == '\n')
name[strlen(name)-1] = '\0';
printf("\nHello, %s!\n", name);
return 0;
}
qq@ubuntu:~/c/2$ ./a.out
Enter your name (max 29 chars): qweasd
Hello, qweasd!
关键点:
- 1.
fgets()参数:fgets(缓冲区, 缓冲区大小, stdin) - 2.比
scanf("%s")更安全(避免溢出) - 3.比
gets()更安全(C11已移除gets)
| 函数 | 格式 | 对 \n 的处理 |
缓冲区处理 | 缓冲区最终状态 |
|---|---|---|---|---|
scanf |
%d, %f, %s 等 |
跳过前导空白,但保留结尾的 \n |
只读取所需数据 | 保留 \n 和其他未读字符 |
getchar |
无 | 读取并消费一个字符 | 读取单个字符 | 保留后续字符(包括 \n) |
fgets |
带缓冲区大小 | 读取 \n 但需手动转换 \n 为 \0 |
读取整行(包括 \n) |
清空缓冲区(无残留) |
// 移除换行符
size_t len = strlen(buffer);
if(len > 0 && buffer[len-1] == '\n')
buffer[len-1] = '\0';
题目4:格式化输出练习
#include <stdio.h>
int main() {
int id = 1001;
float price = 99.95;
char product[] = "Wireless Mouse";
printf("\n=== Product Info ===\n");
printf("ID: %08d\n", id); // 8位数字,前导0
printf("Price: $%6.2f\n", price); // 宽度6,保留2位小数
printf("Name: %-30s\n", product); // 左对齐,宽度15
return 0;
}
qq@ubuntu:~/c/2$ ./a.out
=== Product Info ===
ID: 00001001
Price: $ 99.95
Name: Wireless Mouse
一、分支结构练习闰年判断:
#include <stdio.h>
int main() {
int year;
printf("Enter year: ");
scanf("%d", &year);
if((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))
printf("%d is leap year\n", year);
else
printf("%d is not leap year\n", year);
return 0;
}
qq@ubuntu:~/c/3$ ./1
Enter year: 2025
2025 is not leap year
qq@ubuntu:~/c/3$ ./1
Enter year: 2026
2026 is not leap year
qq@ubuntu:~/c/3$ ./1
Enter year: 2027
2027 is not leap year
qq@ubuntu:~/c/3$ ./1
Enter year: 2028
2028 is leap year
qq@ubuntu:~/c/3$ ./1
Enter year: 2029
2029 is not leap year
qq@ubuntu:~/c/3$
- 2.
成绩等级转换:
#include <stdio.h> int main() { int score; printf("Enter score (0-100): "); scanf("%d", &score); switch(score/10) { case 10: case 9: printf("A\n"); break; case 8: printf("B\n"); break; case 7: printf("C\n"); break; case 6: printf("D\n"); break; default: printf("E\n"); } return 0; }qq@ubuntu:~/c/3$ ./2 Enter score (0-100): 23 E qq@ubuntu:~/c/3$ ./2 Enter score (0-100): 90 A
二、循环结构练习
- 1.
素数判断:
#include <stdio.h> #include <math.h> int main() { int n, i, isPrime = 1; printf("Enter number: "); scanf("%d", &n); if(n <= 1) isPrime = 0; else { for(i = 2; i <= sqrt(n); i++) { if(n % i == 0) { isPrime = 0; break; } } } printf("%d is %sprime\n", n, isPrime ? "" : "not "); return 0; }qq@ubuntu:~/c/3$ ./3 Enter number: 25 25 is not prime qq@ubuntu:~/c/3$ ./3 Enter number: 4 4 is not prime qq@ubuntu:~/c/3$ ./3 Enter number: 3 3 is prime qq@ubuntu:~/c/3$ ./3 Enter number: 7 7 is prime qq@ubuntu:~/c/3$ - 2.
九九乘法表:
#include <stdio.h> int main() { for(int i = 1; i <= 9; i++) { for(int j = 1; j <= i; j++) { printf("%d*%d=%-2d ", j, i, i*j); } printf("\n"); } return 0; }qq@ubuntu:~/c/3$ ./4 1*1=1 1*2=2 2*2=4 1*3=3 2*3=6 3*3=9 1*4=4 2*4=8 3*4=12 4*4=16 1*5=5 2*5=10 3*5=15 4*5=20 5*5=25 1*6=6 2*6=12 3*6=18 4*6=24 5*6=30 6*6=36 1*7=7 2*7=14 3*7=21 4*7=28 5*7=35 6*7=42 7*7=49 1*8=8 2*8=16 3*8=24 4*8=32 5*8=40 6*8=48 7*8=56 8*8=64 1*9=9 2*9=18 3*9=27 4*9=36 5*9=45 6*9=54 7*9=63 8*9=72 9*9=81
三、辅助控制语句
#include <stdio.h>
int main() {
// 打印1-20的奇数
for(int i = 1; i <= 20; i++) {
if(i % 2 == 0) continue;
printf("%d ", i);
if(i > 15) break;
}
return 0;
}
qq@ubuntu:~/c/3$ ./5
1 3 5 7 9 11 13 15 17 qq@ubuntu:~/c/3$
第四章:数组操作
练习1:数组最值查找(一维数组基础)
#include <stdio.h>
int main()
{
int arr[] = {23,5,12,43,23};
int size = sizeof(arr) / sizeof(arr[0]);
int min = arr[0];
int max = arr[0];
for(int i = 0; i < size; i++)
{
if(arr[i] < min) min = arr[i];
if(arr[i] > max) max = arr[i];
}
// 输出结果
printf("数组元素: ");
for(int i = 0; i < size; i++)
{
printf("%d ", arr[i]);
}
printf("\n最小值: %d\n最大值: %d", min, max);
return 0;
}
qq@ubuntu:~/c/4$ ./a.out
数组元素: 23 5 12 43 23
最小值: 5
最大值: 43qq@ubuntu:~/c/4$
关键点与常见错误:
- •✅ 正确计算数组大小:
sizeof(arr)/sizeof(arr[0]) - •❌ 错误初始化:用0初始化可能导致负数数组错误
- •❌ 边界错误:循环应从
i=1开始,而非i=0
练习2:冒泡排序(排序算法)
#include <stdio.h>
int main()
{
int arr[] = {23,5,12,43,23};
int n = sizeof(arr) / sizeof(arr[0]);
printf("sort: ");
for(int i = 0; i < n; i++)
{
printf("%d ", arr[i]);
}
for(int i = 0; i < n - 1; i++)
{
for(int j = 0; j < n-i-1; j++)
{
if(arr[j] > arr[j+1])
{
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
// 输出结果
printf("\nsort: ");
for(int i = 0; i < n; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
qq@ubuntu:~/c/4$ ./a.out
sort: 23 5 12 43 23
sort: 5 12 23 23 43 qq@ubuntu:~/c/4$ vim b.c
关键点与常见错误:
- •✅ 内循环边界:
j < n-i-1避免越界 - •❌ 边界错误:
j < n-i会导致数组越界 - •❌ 缺少优化:可添加
swapped标志提前结束
练习3:字符串统计(字符数组)
#include <stdio.h>
#include <ctype.h>
int main()
{
char str[100];
int letters = 0, digits = 0;
printf("input str: ");
fgets(str, sizeof(str), stdin);
for(int i= 0; str[i] != '\0'; i++)
{
if(str[i] == '\n') str[i] == '\0';
}
for(int i = 0; str[i] != '\0'; i++)
{
if(isalpha(str[i])) letters++;
if(isdigit(str[i])) digits++;
}
printf("char: %d\nnum: %d\n", letters, digits);
return 0;
}
qq@ubuntu:~/c/4$ ./a.out
input str: qwe123ed
char: 5
num: 3
qq@ubuntu:~/c/4$ ./a.out
input str: 122 dfds 23es23 ew23ewq23
char: 11
num: 11
关键点与常见错误:
- •✅ 使用
fgets:避免缓冲区溢出 - •❌ 未处理换行符:fgets会包含输入时的换行符
- •❌ 直接比较ASCII:应使用
isalpha()和isdigit()函数
练习4:矩阵转置(二维数组)
#include <stdio.h>
int main()
{
int mat[2][3] = {1,2,3,4,5,6};
int trans[3][2];
printf("mat: \n");
for(int i = 0; i < 2; i++)
{
for(int j = 0; j < 3; j++)
{
printf("%d ", mat[i][j]);
}
printf("\n");
}
for(int i = 0; i < 2; i++)
{
for(int j = 0; j < 3; j++)
{
trans[j][i] = mat[i][j];
}
}
for(int i = 0; i < 3; i++)
{
for(int j = 0; j < 2; j++)
{
printf("%4d ", trans[i][j]);
}
printf("\n");
}
return 0;
}
qq@ubuntu:~/c/4$ ./a.out
mat:
1 2 3
4 5 6
1 4
2 5
3 6
关键点与常见错误:
- •✅ 维度变化:转置后行列数交换
- •❌ 索引混淆:正确顺序是
trans[j][i] = mat[i][j] - •❌ 原地转置:非方阵不能原地转置
第五章:函数编程核心练习(精选4题)
练习1:函数基础使用(值传递)
#include <stdio.h>
int add(int a, int b)
{
return a + b;
}
int main()
{
int num1, num2;
printf("input two number: ");
scanf("%d%d", &num1, &num2);
int sum = add(num1,num2);
printf("%d + %d = %d\n", num1, num2,sum);
return 0;
}
qq@ubuntu:~/c/5$ ./a.out
input two number: 12 32
12 + 32 = 44
关键点与常见错误:
- •✅ 函数声明:在
main()前声明函数原型 - •❌ 忘记返回值:非
void函数必须有返回值 - •❌ 值传递误解:修改形参不影响实参
练习2:使用指针实现值交换(地址传递)
#include <stdio.h>
void swap(int *a, int *b)
{
int temp = *a;
*a = *b;
*b = temp;
}
int main()
{
int x = 10,y = 20;
printf("x = %d, y = %d\n", x, y);
swap(&x, &y);
printf("x = %d, y = %d\n", x, y);
return 0;
}
qq@ubuntu:~/c/5$ ./a.out
x = 10, y = 20
x = 20, y = 10
关键点与常见错误:
- •✅ 指针参数:使用
*声明指针参数 - •❌ 忘记取地址:调用时需使用
&获取变量地址 - •❌ 空指针:使用前应检查指针有效性
练习3:递归函数实现阶乘
#include <stdio.h>
long factorial(int n)
{
if(n == 0 || n == 1)
return 1;
return n * factorial(n - 1);
}
int main()
{
int num;
printf("intput int num ");
scanf("%d", &num);
if(num < 0)
{
printf("error num \n");
}
else
{
printf("%d! = %ld\n", num, factorial(num));
}
return 0;
}
qq@ubuntu:~/c/5$ ./a.out
intput int num 123
123! = 0
qq@ubuntu:~/c/5$ ./a.out
intput int num 2
2! = 2
qq@ubuntu:~/c/5$ ./a.out
intput int num 12
12! = 479001600
qq@ubuntu:~/c/5$ ./a.out
intput int num 1
1! = 1
qq@ubuntu:~/c/5$ ./a.out
intput int num -1
error num
qq@ubuntu:~/c/5$ ./a.out
intput int num 23
23! = 8128291617894825984
qq@ubuntu:~/c/5$ ./a.out
intput int num 34
34! = 4926277576697053184
qq@ubuntu:~/c/5$ ./a.out
intput int num 321
321! = 0
关键点与常见错误:
- •✅ 终止条件:递归必须有终止条件
- •❌ 栈溢出:递归深度过大会导致栈溢出
- •❌ 性能问题:递归效率通常低于迭代方法
练习4:静态变量应用(函数调用计数)
#include <stdio.h>
void counter()
{
static int count = 0;
count++;
printf("cnt num %d \n", count);
}
int main()
{
for(int i = 0; i < 5; i++)
{
counter();
}
return 0;
}
qq@ubuntu:~/c/5$ ./a.out
cnt num 1
cnt num 2
cnt num 3
cnt num 4
cnt num 5
关键点与常见错误:
- •✅ 静态变量:只初始化一次,保持值不变
- •❌ 初始化位置:静态变量在函数内初始化
- •❌ 作用域误解:静态变量只在函数内可见
函数使用核心要点
1. 函数声明与定义
// 声明(原型)
int sum(int a, int b);
// 定义
int sum(int a, int b)
{
return a + b;
}
2. 参数传递方式
| 类型 | 特点 | 适用场景 |
|---|---|---|
| 值传递 | 传递副本,不影响原值 | 简单数据类型 |
| 地址传递 | 传递指针,可修改原值 | 需要修改参数 |
| 数组传递 | 自动转换为指针 | 数组操作 |
3. 递归函数设计
int fibonacci(int n)
{
if(n <= 1)
return n; // 终止条件
return fibonacci(n-1) + fibonacci(n-2); // 递归调用
}
4. 变量作用域
| 变量类型 | 作用域 | 生命周期 |
|---|---|---|
| 局部变量 | 代码块内 | 函数执行期间 |
| 静态局部变量 | 代码块内 | 程序运行期间 |
| 全局变量 | 整个文件 | 程序运行期间 |
第六章:预处理命令核心练习(精选4题)
练习1:宏定义基础应用
#include <stdio.h>
#define PI 3.1415926
#define CIRCLE_AREA(r) (PI * (r) * (r))
int main()
{
double radius = 5.0;
double area = CIRCLE_AREA(radius);
printf("r: %.2f s: %.2f\n", radius, area);
printf("r(2+3) s: %.2f\n", CIRCLE_AREA(2+3));
return 0;
}
qq@ubuntu:~/c/6$ ./1
r: 5.00 s: 78.54
r(2+3) s: 78.54
关键点与常见错误:
- •✅ 宏参数括号:每个参数和整个表达式都应加括号
- •❌ 忘记括号:
#define CIRCLE_AREA(r) PI*r*r会导致2+3 * 2+3计算错误 - •❌ 副作用:避免在宏中使用自增/自减运算符
练习2:条件编译应用
#include <stdio.h>
#define DEBUG 1
int main()
{
int x = 5, y = 10;
#if DEBUG
printf("debug:::::::\n");
printf("x = %d, y = %d\n", x, y);
#endif
int sum = x + y;
printf("x + y = %d\n", sum);
#if defined(WINDONS)
printf("windows\n");
#elif defined(LINUX)
printf("LINUX");
#else
printf("error\n");
#endif
return 0;
}
qq@ubuntu:~/c/6$ ./2
debug:::::::
x = 5, y = 10
x + y = 15
error
关键点与常见错误:
- •✅ 使用
#ifdef或#if defined()检查宏定义 - •❌ 忘记关闭条件编译块:必须用
#endif结束 - •❌ 宏名拼写错误:
#ifdef对大小写敏感
练习3:预定义宏应用
#include <stdio.h>
int main()
{
printf("time: %s\n", __TIME__);
printf("DAY: %s\n", __DATE__);
printf("LINE: %d\n", __LINE__);
printf("NAME: %s\n", __FILE__);
#ifdef __cplusplus
printf("c++\n");
#else
printf("c\n");
#endif
return 0;
}
qq@ubuntu:~/c/6$ ./3
time: 23:31:12
DAY: Sep 3 2025
LINE: 7
NAME: 3.c
c
关键点与常见错误:
- •✅ 双下划线:预定义宏以双下划线开头和结尾
- •❌ 试图修改:预定义宏不可修改
- •❌ 错误拼写:如
_LINE_少了下划线
预处理命令核心要点
1. 宏定义技巧
// 安全定义带参宏
#define MAX(a, b) ((a) > (b) ? (a) : (b))
// 多行宏使用反斜杠
#define SWAP(a, b) do { \
int temp = (a); \
(a) = (b); \
(b) = temp; \
} while(0)
2. 条件编译应用场景
| 场景 | 使用方式 |
|---|---|
| 平台适配 | #if defined(WIN32) || defined(_WIN32) |
| 调试输出 | #if DEBUG |
| 功能开关 | #ifdef FEATURE_X |
| 代码排除 | #if 0 ... #endif |
3. 头文件最佳实践
#ifndef HEADER_NAME_H
#define HEADER_NAME_H
// 只包含声明,不包含实现
extern void function(void);
#endif /* HEADER_NAME_H */
4. 预定义宏总结
| 宏 | 含义 | 示例 |
|---|---|---|
__LINE__ |
当前行号 | 42 |
__FILE__ |
文件名 | "main.c" |
__DATE__ |
编译日期 | "Jan 23 2024" |
__TIME__ |
编译时间 | "15:45:30" |
__STDC__ |
是否符合C标准 | 1 |
第七章:指针核心练习(精选4题)
练习1:指针基础操作
#include <stdio.h>
int main()
{
int num = 42;
int *ptr = NULL;
ptr = #
printf("num: %d\n", num);
printf("num addr: %p\n", &num);
printf("ptr: %p\n", ptr);
printf("*ptr: %d\n", *ptr);
*ptr = 100;
printf("num: %d\n", num);
int **pptr = &ptr;
printf("pptr: %p\n", pptr);
printf("**pptr: %d\n", **pptr);
return 0;
}
qq@ubuntu:~/c/7$ ./a.out
num: 42
num addr: 0x7ffd54a7b984
ptr: 0x7ffd54a7b984
*ptr: 42
num: 100
pptr: 0x7ffd54a7b988
**pptr: 100
关键点与常见错误:
- •✅ 指针声明:
int *ptr;星号(*)是声明指针的关键 - •❌ 未初始化:使用未初始化的指针会导致段错误
- •❌ 类型不匹配:
int *ptr; float f; ptr = &f;类型不兼容 - •✅ 解引用:
*ptr获取指针指向的值 - •✅ 地址获取:
&num获取变量的地址
练习2:指针与数组关系
#include <stdio.h>
int main()
{
int arr[5] = {1,2,3,4,5};
int *ptr = arr;
printf("arr: \n");
for(int i = 0; i < 5; i++)
{
printf("arr[%d] = %d\n", i, *(ptr + i));
}
printf("\n");
printf("arr[2] = %d\n", arr[2] );
printf("*(arr+2) = %d\n", *(arr+2));
printf("*(ptr+2) = %d\n", *(ptr+2));
printf("ptr[2] = %d\n", ptr[2]);
return 0;
}
qq@ubuntu:~/c/7$ ./a.out
arr:
arr[0] = 1
arr[1] = 2
arr[2] = 3
arr[3] = 4
arr[4] = 5
arr[2] = 3
*(arr+2) = 3
*(ptr+2) = 3
ptr[2] = 3
关键点与常见错误:
- •✅ 数组名作为指针:
arr等价于&arr[0] - •❌ 越界访问:
*(ptr+5)访问越界 - •✅ 指针算术:
ptr+1前进一个元素大小 - •❌ 错误计算:
ptr + 1不是简单加1字节,而是加sizeof(int)
练习3:指针作为函数参数
#include <stdio.h>
void increment(int *num)
{
(*num)++;
}
void printarray(int *arr, int size)
{
for(int i = 0; i < size; i++)
{
printf("%d", arr[i]);
}
printf("\n");
}
int main()
{
int num = 5;
printf("num: %d\n", num);
increment(&num);
printf("num: %d\n", num);
int arr[] = {1,3,4,5,6};
int size = sizeof(arr)/sizeof(arr[0]);
printf("num:");
printarray(arr,size);
return 0;
}
qq@ubuntu:~/c/7$ ./a.out
num: 5
num: 6
num:13456
关键点与常见错误:
- •✅ 地址传递:函数内可修改外部变量
- •❌ 忘记取地址:
increment(num)应该传&num - •✅ 数组传递:函数参数
int arr[]等价于int *arr - •❌ 数组大小丢失:函数内无法通过指针获取数组大小
练习4:函数指针应用
#include <stdio.h>
// 加法函数
int add(int a, int b) {
return a + b;
}
// 减法函数
int subtract(int a, int b) {
return a - b;
}
// 使用函数指针作为参数
int calculate(int (*operation)(int, int), int x, int y) {
return operation(x, y);
}
int main() {
// 声明函数指针
int (*funcPtr)(int, int);
// 指向加法函数
funcPtr = &add;
printf("5 + 3 = %d\n", funcPtr(5, 3));
// 指向减法函数
funcPtr = &subtract;
printf("5 - 3 = %d\n", funcPtr(5, 3));
// 函数指针作为参数
printf("通过calculate函数: 5 + 3 = %d\n", calculate(add, 5, 3));
printf("通过calculate函数: 5 - 3 = %d\n", calculate(subtract, 5, 3));
return 0;
}
qq@ubuntu:~/c/7$ ./a.out
5 + 3 = 8
5 - 3 = 2
通过calculate函数: 5 + 3 = 8
通过calculate函数: 5 - 3 = 2
关键点与常见错误:
- •✅ 函数指针声明:
int (*funcPtr)(int, int) - •❌ 类型不匹配:函数指针必须与函数签名匹配
- •✅ 多种调用方式:
funcPtr(5,3)或(*funcPtr)(5,3) - •❌ 空指针调用:调用未初始化的函数指针导致崩溃
指针使用核心要点
1. 指针声明与初始化
int *ptr; // 未初始化(危险!)
int *ptr = NULL; // 安全初始化
int num = 10;
int *ptr = # // 指向变量
2. 指针操作
| 操作 | 示例 | 说明 |
|---|---|---|
| 取地址 | &var |
获取变量地址 |
| 解引用 | *ptr |
获取指针指向的值 |
| 指针赋值 | ptr1 = ptr2 |
指向相同地址 |
| 指针比较 | ptr1 == ptr2 |
比较地址是否相同 |
3. 指针与数组关系
int arr[5];
int *p = arr; // 等价于 p = &arr[0]
// 以下表达式等价
arr[2] == *(arr + 2) == *(p + 2) == p[2]
4. 多级指针
int num = 10;
int *p = #
int **pp = &p; // 二级指针
// 访问值
**pp = 20; // 修改num的值
5. 函数指针应用场景
| 场景 | 示例 |
|---|---|
| 回调函数 | qsort中的比较函数 |
| 策略模式 | 运行时选择算法 |
| 事件处理 | 根据事件类型调用不同处理函数 |
第八章:结构体与联合核心练习(精选4题)
练习1:结构体基础使用
#include <stdio.h>
#include <string.h>
struct student {
char name[50];
int age;
float gpa;
};
int main()
{
struct student stu1 = {"qwe", 20, 12.23};
struct student stu2;
strcpy(stu2.name, "asd");
stu2.age = 12;
stu2.gpa = 3.21;
printf("stu1: %s, %d age, %.2f\n", stu1.name, stu1.age, stu1.gpa);
printf("stu2: %s, %d age, %.2f\n", stu2.name, stu2.age, stu2.gpa);
struct student *ptr = &stu1;
printf("ptr->name: %s\n", ptr->name);
struct student class[3] = {
{"qw", 12, 2.22},
{"as", 23, 1.21},
{"zx", 32, 2.32}
};
printf("\n");
for(int i = 0; i < 3; i++)
{
printf("%s, %d, %.2f\n", class[i].name, class[i].age, class[i].gpa);
}
return 0;
}
qq@ubuntu:~/c/8$ ./1
stu1: qwe, 20 age, 12.23
stu2: asd, 12 age, 3.21
ptr->name: qwe
qw, 12, 2.22
as, 23, 1.21
zx, 32, 2.32
关键点与常见错误:
- •✅ 结构体定义:使用
struct关键字定义 - •❌ 忘记分号:结构体定义后必须有分号
- •✅ 成员访问:
.运算符访问成员 - •✅ 指针访问:
->运算符简化指针访问 - •❌ 字符串赋值:不能直接赋值,需用
strcpy
练习2:结构体作为函数参数
#include <stdio.h>
struct point{
int x;
int y;
};
void printpoint(struct point p){
printf("point:(%d, %d)\n", p.x, p.y);
}
void movepoint(struct point *p, int dx, int dy)
{
p->x += dx;
p->y += dy;
}
int main()
{
struct point pt = {10, 20};
printpoint(pt);
movepoint(&pt, 5, -3);
printpoint(pt);
return 0;
}
qq@ubuntu:~/c/8$ ./2
point:(10, 20)
point:(15, 17)
关键点与常见错误:
- •✅ 值传递:函数内操作副本
- •✅ 指针传递:可修改原始结构体
- •❌ 忘记取地址:
movePoint(pt, 5, -3)应传&pt - •❌ 结构体复制开销:大型结构体应传指针提高效率
练习3:结构体嵌套与类型定义
#include <stdio.h>
struct date{
int year;
int month;
};
typedef struct empoyee{
char name[32];
struct date birthday;
} employee;
int main()
{
employee emp = {
"qwe",
{1990, 2},
};
printf("name: %s\n", emp.name);
printf("date: %d %d\n", emp.birthday.year, emp.birthday.month);
emp.birthday.year = 1992;
printf("%d\n", emp.birthday.year);
return 0;
}
qq@ubuntu:~/c/8$ ./3
name: qwe
date: 1990 2
1992
关键点与常见错误:
- •✅
typedef简化:创建新类型名 - •✅ 结构体嵌套:实现复杂数据结构
- •❌ 嵌套访问错误:需通过多层访问
emp.birthdate.year - •❌ 初始化顺序:嵌套结构体需按定义顺序初始化
练习4:联合体应用
#include <stdio.h>
#include <string.h>
union data{
int i;
float f;
char str[20];
};
int main()
{
union data d;
d.i = 32;
printf("d.i = %d\n", d.i);
d.f = 2.22;
printf("d.f = %.2f\n", d.f);
strcpy(d.str, "printf");
printf("d.str: %s\n", d.str);
printf("sizeof: %zu\n", sizeof(union data));
union {
float f;
unsigned int u;
}converter;
converter.f = 3.14f;
printf("float: %.2f\n, %08x\n", converter.f,converter.u);
return 0;
}
qq@ubuntu:~/c/8$ ./4
d.i = 32
d.f = 2.22
d.str: printf
sizeof: 20
float: 3.14
, 4048f5c3
关键点与常见错误:
- •✅ 共享内存:所有成员共享同一内存空间
- •❌ 同时使用:只能同时使用一个成员
- •✅ 内存大小:由最大成员决定
- •✅ 应用场景:类型转换、节省内存
- •❌ 对齐问题:注意不同架构的内存对齐
结构体与联合核心要点
1. 结构体定义技巧
// 匿名结构体
struct {
int x;
int y;
} point; // 直接声明变量
// 位字段
struct {
unsigned int flag1 : 1; // 1位
unsigned int flag2 : 3; // 3位
} flags;
2. 结构体与指针
typedef struct Node {
int data;
struct Node *next; // 自引用指针
} Node;
// 创建链表
Node *head = NULL;
3. 联合体高级应用
// IP地址转换
union IPAddress {
unsigned int address;
unsigned char bytes[4];
};
union IPAddress ip;
ip.address = 0xC0A80101; // 192.168.1.1
printf("IP: %d.%d.%d.%d\n", ip.bytes[3], ip.bytes[2], ip.bytes[1], ip.bytes[0]);
4. 枚举类型
// 定义枚举
typedef enum {
RED, GREEN, BLUE
} Color;
// 使用枚举
Color c = GREEN;
switch(c) {
case RED: printf("红色"); break;
case GREEN: printf("绿色"); break;
case BLUE: printf("蓝色"); break;
}
第九章:内存管理核心练习
练习1:基础内存分配
#include <stdio.h>
#include <stdlib.h>
int main ()
{
int *arr = (int *)malloc(5 * sizeof(int));
if (arr == NULL)
{
printf("fail to malloc\n");
return 1;
}
for(int i = 0; i < 5; i++)
{
arr[i] = i * 10;
}
printf("array: ");
for(int i = 0; i < 5; i++)
{
printf("%d ", arr[i]);
}
free(arr);
arr = NULL;
return 0;
}
qq@ubuntu:~/c/9$ ./a
array: 0 10 20 30 40 qq@ubuntu:~/c/9$
对应内容:
- •
malloc申请堆区空间 - •检查返回值是否为NULL
- •
free释放堆区空间 - •避免悬空指针(安全实践)
练习2:结构体内存管理
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct{
char name[32];
int age;
} person;
int main()
{
person *p = (person *)malloc(sizeof(person));
if(p == NULL)
{
printf("fail to malloc\n");
return 1;
}
strcpy(p->name, "qwe");
p->age = 23;
printf("name:%s\n age = %d\n", p->name, p->age);
free(p);
p = NULL;
return 0;
}
qq@ubuntu:~/c/9$ ./b
name:qwe
age = 23
对应内容:
- •堆区空间操作
- •结构体动态分配
内存管理核心要点
1. 内存分区
graph TB
A[内存] --> B[文本段]
A --> C[栈区]
A --> D[堆区]
A --> E[数据区]
C --> F[局部变量-auto]
D --> G[动态分配-malloc]
E --> H[全局变量/静态变量]
2. 堆区操作函数
| 函数 | 功能 | 使用示例 | 返回值 |
|---|---|---|---|
| malloc | 分配内存 | int *p = malloc(10*sizeof(int)) |
成功:指针,失败:NULL |
| free | 释放内存 | free(p) |
无 |
| calloc | 分配并清零 | int *p = calloc(10, sizeof(int)) |
同malloc |
| realloc | 调整内存 | p = realloc(p, 20*sizeof(int)) |
新指针 |
3. 内存泄漏预防
- 1.分配与释放配对:每个malloc对应一个free
- 2.所有权明确:哪个函数分配,哪个函数释放
- 3.使用工具检测:
valgrind --leak-check=full ./program - 4.RAII原则:资源获取即初始化
4. 安全实践
// 1. 检查返回值
int *p = malloc(size);
if(p == NULL) {
// 错误处理
}
// 2. 释放后置空
free(p);
p = NULL;
// 3. 避免重复释放
if(p != NULL) {
free(p);
p = NULL;
}
// 4. 使用宏封装
#define SAFE_FREE(ptr) do { \
free((ptr)); \
(ptr) = NULL; \
} while(0)
第十章:代码调试与错误处理核心练习
练习1:GDB调试实践
#include <stdio.h>
int factorial(int n)
{
if(n == 0 || n == 1)
return 1;
else
return n * factorial(n-1);
}
int main()
{
int num = 5;
int result = factorial(num);
printf("%d! = %d\n", num, result);
return 0;
}
qq@ubuntu:~/c/10$ ./a.out
5! = 120
qq@ubuntu:~
GDB调试步骤:
# 1. 编译时添加调试信息
gcc -g factorial.c -o factorial
# 2. 启动GDB
gdb ./factorial
# 3. 设置断点
(gdb) b factorial # 在factorial函数设置断点
# 4. 运行程序
(gdb) run
# 5. 调试命令
(gdb) n # 下一行
(gdb) p n # 打印变量n的值
(gdb) bt # 查看调用栈
# 6. 调试递归调用
(gdb) display n # 每次暂停显示n值
(gdb) c # 继续到下一个断点
qq@ubuntu:~/c/10$ gcc 1.c
qq@ubuntu:~/c/10$ ./a.out
5! = 120
qq@ubuntu:~/c/10$ ^C
qq@ubuntu:~/c/10$ gcc 1.c -g
qq@ubuntu:~/c/10$ gdb ./a.out
GNU gdb (Ubuntu 8.1.1-0ubuntu1) 8.1.1
Copyright (C) 2018 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./a.out...done.
(gdb) b factorial
Breakpoint 1 at 0x655: file 1.c, line 5.
(gdb) run
Starting program: /home/qq/c/10/a.out
Breakpoint 1, factorial (n=5) at 1.c:5
5 if(n == 0 || n == 1)
(gdb) n
8 return n * factorial(n-1);
(gdb) p n
$1 = 5
(gdb) bt
#0 factorial (n=5) at 1.c:8
#1 0x0000555555554694 in main () at 1.c:14
(gdb) display n
1: n = 5
(gdb) c
Continuing.
Breakpoint 1, factorial (n=4) at 1.c:5
5 if(n == 0 || n == 1)
1: n = 4
(gdb) c
Continuing.
Breakpoint 1, factorial (n=3) at 1.c:5
5 if(n == 0 || n == 1)
1: n = 3
(gdb) c
Continuing.
Breakpoint 1, factorial (n=2) at 1.c:5
5 if(n == 0 || n == 1)
1: n = 2
(gdb) c
Continuing.
Breakpoint 1, factorial (n=1) at 1.c:5
5 if(n == 0 || n == 1)
1: n = 1
(gdb) c
Continuing.
5! = 120
[Inferior 1 (process 121332) exited normally]
(gdb) c
The program is not being run.
(gdb)
练习2:内存泄漏检测
#include <stdlib.h>
void creat_leak()
{
int *arr = malloc(100 * sizeof(int));
}
int main()
{
creat_leak();
return 0;
}
Valgrind检测:
# 1. 编译程序(添加-g)
gcc -g leak.c -o leak
# 2. 使用Valgrind检测
valgrind --leak-check=full ./leak
# 3. 分析输出
==12345== 400 bytes in 1 blocks are definitely lost in loss record 1 of 1
==12345== at 0x483777F: malloc (vg_replace_malloc.c:307)
==12345== by 0x401145: create_leak (leak.c:5)
==12345== by 0x401156: main (leak.c:10)
qq@ubuntu:~/c/10$ gcc 2.c -o 2
qq@ubuntu:~/c/10$ valgrind --leak-check=full ./2
==121373== Memcheck, a memory error detector
==121373== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==121373== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==121373== Command: ./2
==121373==
==121373==
==121373== HEAP SUMMARY:
==121373== in use at exit: 400 bytes in 1 blocks
==121373== total heap usage: 1 allocs, 0 frees, 400 bytes allocated
==121373==
==121373== 400 bytes in 1 blocks are definitely lost in loss record 1 of 1
==121373== at 0x4C31B0F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==121373== by 0x10865B: creat_leak (in /home/qq/c/10/2)
==121373== by 0x108670: main (in /home/qq/c/10/2)
==121373==
==121373== LEAK SUMMARY:
==121373== definitely lost: 400 bytes in 1 blocks
==121373== indirectly lost: 0 bytes in 0 blocks
==121373== possibly lost: 0 bytes in 0 blocks
==121373== still reachable: 0 bytes in 0 blocks
==121373== suppressed: 0 bytes in 0 blocks
==121373==
==121373== For counts of detected and suppressed errors, rerun with: -v
==121373== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
qq@ubuntu:~/c/10$
修复建议:
- 1.在
create_leak()函数末尾添加free(arr); - 2.重新检测确认无泄漏
调试核心要点
1. 错误处理优先级
graph TD
A[编译错误] --> B[修改第一个错误]
B --> C[重新编译]
C --> D{还有错误?}
D -->|是| B
D -->|否| E[逻辑调试]
2. GDB常用命令
| 命令 | 功能 | 示例 |
|---|---|---|
gdb <程序> |
启动调试 | gdb ./program |
b <位置> |
设置断点 | b main, b 20 |
r |
运行程序 | r |
n |
单步执行 | n |
s |
进入函数 | s |
p <变量> |
打印变量 | p x |
bt |
调用栈回溯 | bt |
q |
退出调试 | q |
3. 段错误调试流程
- 1.使用打印语句定位大致位置
- 2.编译时添加
-g选项 - 3.使用GDB调试:
gdb ./program (gdb) r # 程序崩溃后 (gdb) bt - 4.分析堆栈信息
4. Valgrind内存检测
# 完整内存检查
valgrind --tool=memcheck --leak-check=full ./program
# 结果分析要点:
# - "definitely lost":确认的内存泄漏
# - "indirectly lost":间接内存泄漏
# - "Invalid read/write":非法内存访问
第十一章:位运算符核心练习
练习1:基本位操作和位运算符优先级验证
#include <stdio.h>
void print_binary(unsigned char num)
{
for(int i = 7; i >= 0; i--)
{
printf("%d", (num >> i) & 1);
}
printf("\n");
}
int main()
{
unsigned char a = 0b10101010;
unsigned char b = 0b01110000;
printf("a: ");
print_binary(a);
printf("b: ");
print_binary(b);
printf("&: ");
print_binary(a & b);
printf("|: ");
print_binary(a | b);
printf("^: ");
print_binary(a ^ b);
printf("~: ");
print_binary(~b);
printf(">>2; ");
print_binary(b >> 2);
printf("<<2: ");
print_binary(b << 2);
printf("a & b << 2: ");
print_binary(a & b << 2);
printf("(a & b) << 2: ");
print_binary((a & b) << 2);
printf("a & b | 2: ");
print_binary(a & b | 2);
return 0;
}
qq@ubuntu:~/c/11$ ./a.out
a: 10101010
b: 01110000
&: 00100000
|: 11111010
^: 11011010
~: 10001111
>>2; 00011100
<<2: 11000000
a & b << 2: 10000000
(a & b) << 2: 10000000
a & b | 2: 00100010
关键点:
- •✅ 所有位运算符的使用
- •✅ 二进制字面量表示(
0b前缀) - •❌ 移位边界:移动超过类型大小是未定义行为
- •✅ 移位优先级:
<<和>>高于&、|、^ - •✅ 验证示例:
x = y >> n | m; - •❌ 常见误解:
&优先级低于|,但都低于移位
练习2:置位与清零操作
#include <stdio.h>
#define SET_BIT(num, n) ((num) |= (1 << (n)))
#define CLEAR_BIT(num, n) ((num) &= ~(1 << (n)))
void print_binary(unsigned char num)
{
for(int i = 7; i >= 0; i--)
{
printf("%d", (num >> i) & 1);
}
printf("\n");
}
int main()
{
unsigned char flags = 0b00000000;
SET_BIT(flags, 3);
printf("3: ");
print_binary(flags);
SET_BIT(flags, 2);
printf("2: ");
print_binary(flags);
SET_BIT(flags, 6);
printf("6: ");
print_binary(flags);
CLEAR_BIT(flags, 3);
printf("3: ");
print_binary(flags);
return 0;
}
qq@ubuntu:~/c/11$ ./a.out
3: 00001000
2: 00001100
6: 01001100
3: 01000100
关键点:
- •✅ 置位操作:
num |= (1 << n) - •✅ 清零操作:
num &= ~(1 << n) - •❌ 宏定义括号:每个参数和整个表达式都应加括号
算核心要点
1. 运算符优先级表
| 优先级 | 运算符 | 示例 | 计算顺序 |
|---|---|---|---|
| 高 | <<, >> |
a << b & c |
(a << b) & c |
| 中 | & |
`a & b | c` |
| 低 | ^ |
a ^ b & c |
a ^ (b & c) |
| 最低 | ` | ` | `a |
2. 位运算技巧
// 1. 检查第n位是否为1
if(num & (1 << n)) {
// 第n位为1
}
// 2. 切换第n位
num ^= (1 << n);
// 3. 检查奇偶性
if(num & 1) {
// 奇数
}
3. 使用场景总结
| 场景 | 位操作 |
|---|---|
| 置位 | `num |
| 清零 | num &= ~(1 << n) |
| 切换 | num ^= (1 << n) |
| 交换数值 | a^=b; b^=a; a^=b; |
| 乘除2 | a << 1, a >> 1 |
4. 注意事项
- 1.无符号类型:位运算推荐使用无符号类型
- 2.移位边界:避免移动超过类型大小
- 3.宏定义安全:
// 安全定义(添加括号) #define GET_BIT(num, n) (((num) >> (n)) & 1) - 4.可读性:复杂位运算添加注释
更多推荐


所有评论(0)