C语言入门速成:从 0 到会写题
本文适合:期末复习阶段的在校学生和 C 语言初学者;对算法竞赛、OJ 刷题感兴趣的读者,可关注作者或者订阅专栏获取更多竞赛向内容。C程序由头文件、主函数、函数定义/声明、语句组成,其中主函数(main)是程序入口,有且仅有一个。结构说明二、数据类型C语言数据类型分为基本类型(整型、字符型、浮点型)和构造类型(数组、结构体等)注意:注意:字符型实际存储的是码值,因此可以参与数值运算,例如会得到的码。
本文适合:期末复习阶段的在校学生和 C 语言初学者;觉得不错的同学可以点个赞哦,对算法竞赛、OJ 刷题感兴趣的读者,可关注作者或者订阅专栏获取更多竞赛向内容。
一、程序基本结构
C程序由头文件、主函数、函数定义/声明、语句组成,其中主函数(main)是程序入口,有且仅有一个。
- 最小程序示例(Hello World)
#include <stdio.h> // 头文件:引入标准输入输出库
// 主函数:程序执行的起点
int main() {
// 语句:输出字符串,以分号结尾
printf("Hello World!\n"); // \n是换行符
return 0; // 主函数返回值,0表示程序正常结束
}
- 结构说明
#include <stdio.h>:预处理指令,引入stdio.h库才能使用printf、scanf等输入输出函数;
int main():主函数声明,int表示函数返回值类型为整数;
{}:代码块边界,包裹函数体内容;
printf(…):库函数,用于输出内容;
;:语句结束标志,C语言中所有语句必须以分号结尾;
return 0:主函数的返回值,返回给操作系统,0代表执行成功。
二、数据类型
C语言数据类型分为基本类型(整型、字符型、浮点型)和构造类型(数组、结构体等)
- 基本数据类型表格
| 类型 | 占用字节 | 取值范围 |
|---|---|---|
char |
1 | -128 到 127 |
unsigned char |
1 | 0 到 255 |
short (或 short int) |
2 | -32,768 到 32,767 |
unsigned short |
2 | 0 到 65,535 |
int |
4 | -2,147,483,648 到 2,147,483,647 |
unsigned int |
4 | 0 到 4,294,967,295 |
long (或 long int) |
4 | -2,147,483,648 到 2,147,483,647 |
unsigned long |
4 | 0 到 4,294,967,295 |
float |
4 | 大约 ±3.4e-38 到 ±3.4e+38 (大约6-7位有效数字) |
double |
8 | 大约 ±1.7e-308 到 ±1.7e+308 (大约15位有效数字) |
long double |
8 或 12 | 精度和范围比 double 更高 (取决于编译器实现) |
注意:
- 上表中的字节大小和取值范围是 典型值,并非所有系统都完全一致。例如,在64位系统中,
long类型通常为8字节。 - 可以使用
sizeof运算符来获取任何数据类型在当前编译环境下的确切字节大小,例如sizeof(int)。 - 整型(
char,short,int,long)默认是signed(有符号)类型,可以存储正数、负数和零。使用unsigned关键字修饰后,它们只能存储非负数(0和正数),但正数的表示范围会扩大一倍。
- 类型使用示例
#include <stdio.h>
int main() {
// 整型
int age = 20;
// 无符号整型
unsigned int score = 95;
// 字符型(本质是ASCII码值)
char ch = 'A'; // 注意用单引号
// 单精度浮点型
float height = 1.75f; // 加f表示单精度,否则默认double
// 双精度浮点型
double weight = 65.5;
// 输出各类型值
printf("年龄:%d\n", age); // %d是int的格式符
printf("分数:%u\n", score); // %u是unsigned int的格式符
printf("字符:%c(ASCII值:%d)\n", ch, ch); // %c输出字符,%d输出ASCII值
printf("身高:%f\n", height); // %f输出float/double
printf("体重:%lf\n", weight); // %lf也可用于double
return 0;
}
注意:字符型char实际存储的是ASCII码值,因此可以参与数值运算,例如’A’ + 32会得到’a’的ASCII码。
三、变量与常量
(1)变量
在程序设计中,变量 是指一个用于存储数据值的命名内存位置。它的值在程序运行期间可以被改变。
定义格式:
数据类型 变量名 = 初始值;
示例:
// 定义一个整型变量 age 并初始化为 18
int age = 18;
// 定义一个单精度浮点型变量 price,但未初始化
float price;
// 可以在后续代码中为 price 赋值
price = 99.9f;
重要提示:在定义变量时,如果省略了初始值(如上面的 float price;),变量将包含一个不确定的、随机的“垃圾值”。这是一个常见的程序错误来源。因此,始终在定义变量时进行初始化是一个非常好的编程习惯。
// 定义时即初始化
int count = 0;
char initial = 'A';
变量命名规则:
-
合法字符:只能由 字母 (
a-z,A-Z)、数字 (0-9) 和 下划线 (_) 组成。 -
不能以数字开头:变量名的第一个字符必须是字母或下划线。
-
区分大小写:C语言是大小写敏感的,这意味着大写字母和小写字母被视为不同的字符。
-
不能使用关键字:不能使用C语言保留的关键字作为变量名。关键字是语言内部有特殊含义的词。
(2)常量
常量是程序运行中值不可改变的量,分为字面常量、宏定义常量、const修饰常量。
-
常量使用示例
#include <stdio.h> // 1. 宏定义常量:预处理阶段替换,无类型 #define PI 3.14159 // 注意没有分号 #define MAX_AGE 120 int main() { // 2. 字面常量 printf("字面常量:%d\n", 100); // 100是整型字面常量 // 3. const修饰常量:有类型,编译阶段检查 const float g = 9.8f; // 重力加速度,不可修改 // 使用常量计算圆的面积 float r = 5.0f; float area = PI * r * r; printf("圆面积:%.2f\n", area); // %.2f表示保留2位小数 printf("最大年龄:%d\n", MAX_AGE); printf("重力加速度:%f\n", g); // g = 10.0f; // 错误:const常量不能修改 return 0; }
四、运算符与表达式
(1)算术运算符
用于执行数值计算。
+(加)-(减)*(乘)/(除): 对于整数除法,结果会舍弃小数部分(向零取整)。%(取余/模): 计算两个整数相除的余数。结果的符号与被除数相同。++(自增): 将变量的值加1。--(自减): 将变量的值减1。
自增/自减的两种形式:
- 前置:
++a或--a。先将变量的值加/减1,然后使用新的值参与表达式的运算。 - **后置:
a++或a--。先使用变量当前的值参与表达式运算,然后再将变量的值加/减1。
示例:
#include <stdio.h>
int main() {
int a = 10, b = 3;
printf("a + b = %d\n", a + b); // 输出: 13
printf("a - b = %d\n", a - b); // 输出: 7
printf("a * b = %d\n", a * b); // 输出: 30
printf("a / b = %d\n", a / b); // 输出: 3 (整数除法,小数部分被舍弃)
printf("a %% b = %d\n", a % b); // 输出: 1 (10除以3的余数是1)
// 自增自减:前置先运算后取值,后置先取值后运算
int c = a++; // 后置自增:先把a(10)赋值给c,然后a自己变成11
int d = ++b; // 前置自增:先把b自己变成4,然后把新值(4)赋值给d
printf("a = %d, c = %d\n", a, c); // 输出: a = 11, c = 10
printf("b = %d, d = %d\n", b, d); // 输出: b = 4, d = 4
return 0;
}
(2)关系运算符
用于比较两个操作数的大小关系。表达式的结果是一个整数:1 代表 真 (true),0 代表 假 (false)。
==(等于)!=(不等于)>(大于)<(小于)>=(大于等于)<=(小于等于)
(3)逻辑运算符
用于组合或修改逻辑表达式,其操作数通常是关系表达式。结果同样是 1 (真) 或 0 (假)。在C语言中,任何非零值都被视为“真”,只有零被视为“假”。
&&(逻辑与):a && b,当且仅当a和b都为真时,结果才为真。||(逻辑或):a || b,当a或b至少有一个为真时,结果就为真。!(逻辑非):!a,如果a为假 (0),结果为真 (1);如果a为真 (非0),结果为假 (0)。
示例 (关系与逻辑):
#include <stdio.h>
int main() {
int x = 5, y = 0;
// 关系运算
printf("x == 5: %d\n", x == 5); // 1 (真)
printf("x > y: %d\n", x > y); // 1 (真)
// 逻辑运算
// &&:x>0为真,但y>0为假,所以整体为假
printf("x>0 && y>0: %d\n", x > 0 && y > 0); // 0 (假)
// ||:x>0为真,满足一个条件即可,所以整体为真
printf("x>0 || y>0: %d\n", x > 0 || y > 0); // 1 (真)
// !:y是0(假),取非后为真
printf("!y: %d\n", !y); // 1 (真)
return 0;
}
(4)赋值运算符
用于将一个值赋给一个变量。
=(简单赋值):a = b,将b的值赋给a。- 复合赋值运算符: 将算术运算和赋值结合起来,使代码更简洁。
+=(例如a += b等价于a = a + b)-=(例如a -= b等价于a = a - b)*=(例如a *= b等价于a = a * b)/=(例如a /= b等价于a = a / b)%=(例如a %= b等价于a = a % b)
代码:
#include <stdio.h>
int main() {
int a = 10;
a += 5; // 等价于 a = a + 5; 此后 a 的值为 15
printf("a after += 5: %d\n", a); // 输出: 15
a *= 2; // 等价于 a = a * 2; 此后 a 的值为 30
printf("a after *= 2: %d\n", a); // 输出: 30
return 0;
}
五、控制语句
(1)选择
用于根据一个或多个条件来选择性地执行代码块。
if-else 语句
这是最基本的条件判断语句,可以单独使用 if,也可以组合成 if-else 或 if-else if-else 的形式。
示例:
#include <stdio.h>
int main() {
int score = 85;
if (score >= 90) {
printf("优秀\n");
} else if (score >= 80) { // 上一个if条件不满足时,再判断此条件
printf("良好\n");
} else if (score >= 60) {
printf("及格\n");
} else { // 如果以上所有条件都不满足,则执行这里
printf("不及格\n");
}
return 0; // 输出:良好
}
注意:
-
else if和else都是可选的。 -
程序会从上到下依次检查条件,一旦找到一个满足的
if或else if,就会执行其代码块,然后跳过整个if-else结构余下的部分。
switch 语句
用于对一个 整型 或 字符型 表达式的值进行多分支判断。
- 特点:适用于对一系列离散的、特定的值进行判断,代码结构比多个
else if更清晰。
示例:
#include <stdio.h>
int main() {
char grade = 'B';
switch (grade) {
case 'A': // 匹配到字符 'A' 时执行
printf("得分90-100\n");
break; // 执行完毕后,跳出整个switch结构
case 'B':
printf("得分80-89\n");
break;
case 'C':
printf("得分60-79\n");
break;
default: // 如果以上所有case都未匹配成功,则执行default部分
printf("不及格\n");
}
return 0; // 输出:得分80-89
}
说明:
-
switch的表达式结果必须是整型或可以提升为整型(如char)。 -
case后面必须跟一个常量表达式。 -
break至关重要:如果没有break,程序会发生“穿透”,即继续执行下一个case的代码,直到遇到break或switch结束。 -
default是可选的,用于处理所有未列出的情况,建议总是加上以增强程序的健壮性。
(2)循环
用于重复执行一段代码,直到满足某个退出条件。
for 循环
最常用的循环结构,非常适合在 循环次数已知 的情况下使用。
- 格式:
for (初始化表达式; 循环条件; 更新表达式) { 循环体 }
示例:
#include <stdio.h>
int main() {
// 打印从 1 到 5 的数字
for (int i = 1; i <= 5; i++) { // i从1开始,当i<=5时循环,每次循环结束时i加1
printf("%d ", i);
}
printf("\n"); // 换行
// 输出:1 2 3 4 5
return 0;
}
while 循环
先判断条件,如果条件为真,则执行循环体。
- 特点:适合在 循环次数未知,仅依赖某个条件来决定是否继续循环的场景。
示例:
#include <stdio.h>
int main() {
// 同样打印从 1 到 5 的数字
int i = 1;
while (i <= 5) { // 先判断 i <= 5 是否为真
printf("%d ", i);
i++; // 必须在循环体内手动更新条件变量,否则会造成死循环
}
printf("\n");
// 输出:1 2 3 4 5
return 0;
}
do-while 循环
与 while 循环类似,但它是先执行一次循环体,然后再判断条件。
- 特点:保证循环体至少被执行一次。
示例:
#include <stdio.h>
int main() {
int i = 1;
do {
printf("%d ", i);
i++;
} while (i <= 5); // 先执行循环体,再判断条件。注意这里末尾有分号!
printf("\n");
// 输出:1 2 3 4 5
return 0;
}
(3)跳转语句
用于改变程序的正常执行流程,直接跳转到另一处。
break:立即终止并跳出当前所在的switch语句或最内层的循环(for,while,do-while)。continue:立即结束本次循环,跳过循环体中尚未执行的语句,直接开始下一次循环的迭代。
示例:
#include <stdio.h>
int main() {
// break 示例:在循环中提前退出
printf("break示例:");
for (int i = 1; i <= 5; i++) {
if (i == 3) {
break; // 当 i 等于 3 时,立即跳出 for 循环
}
printf("%d ", i);
}
// 输出:1 2
printf("\n\n"); // 分隔
// continue 示例:跳过某次循环
printf("continue示例:");
for (int i = 1; i <= 5; i++) {
if (i == 3) {
continue; // 当 i 等于 3 时,跳过本次循环中后面的 printf,直接进入 i=4 的循环
}
printf("%d ", i);
}
printf("\n");
// 输出:1 2 4 5
return 0;
}
六、函数
函数就是一个能完成 特定任务、可以 重复使用 的代码块。
核心三要素:
-
定义函数
- 想好函数的功能,给它起个名字。
- 确定它需要什么“材料”(参数),以及它要产出什么“成品”(返回值)。
格式:
返回值类型 函数名(参数列表) { // 实现功能的代码 return 返回值; // 把成品交出去 }例子:加法器
// 返回一个整数,名叫add,需要两个整数 int add(int a, int b) { return a + b; // 直接返回它俩的和 } -
调用函数
- 在
main函数或其他地方,通过函数名来使用它。 - 把实际的“材料”传给它。
例子:用上面写好的加法器
#include <stdio.h> // 函数定义(通常放在 main 前面) int add(int a, int b) { return a + b; } int main() { // 调用 add 函数,把 10 和 20 传进去 int sum = add(10, 20); printf("结果是: %d\n", sum); // 输出:结果是: 30 return 0; } - 在
注意:
void:如果函数不返回任何值,返回值类型写void。如果不需要参数,括号里也写void,不推荐括号里留空。
七、数组
数组是存储同一类型数据的连续内存空间,通过下标访问(下标从0开始)。‘
- 一维数组
#include <stdio.h>
int main() {
// 1. 定义并初始化
int arr1[5] = {1, 2, 3, 4, 5}; // 长度5,元素为1-5
// 2. 部分初始化,未赋值元素为0
int arr2[5] = {1, 2}; // 元素:1,2,0,0,0
// 3. 省略长度,由初始化元素个数决定
int arr3[] = {10, 20, 30}; // 长度3
// 访问数组元素:下标从0开始
printf("arr1[0] = %d\n", arr1[0]); // 1
printf("arr1[2] = %d\n", arr1[2]); // 3
// 遍历数组(用for循环)
printf("arr1所有元素:");
for (int i = 0; i < 5; i++) {
printf("%d ", arr1[i]);
} // 输出:1 2 3 4 5
// 修改数组元素
arr1[3] = 40;
printf("\n修改后arr1[3] = %d\n", arr1[3]); // 40
return 0;
}
- 二维数组
#include <stdio.h>
int main() {
// 定义2行3列的二维数组并初始化
int arr[2][3] = {{1,2,3}, {4,5,6}};
// 访问元素:行下标、列下标
printf("arr[0][1] = %d\n", arr[0][1]); // 2(第1行第2列)
// 遍历二维数组
printf("二维数组所有元素:\n");
for (int i = 0; i < 2; i++) { // 遍历行
for (int j = 0; j < 3; j++) { // 遍历列
printf("%d ", arr[i][j]);
}
printf("\n"); // 换行
}
/* 输出:
1 2 3
4 5 6
*/
return 0;
}
八、指针
指针是C语言的灵魂,也是其强大和灵活的根源。从本质上讲,指针就是一个存储内存地址的变量。
1. 核心概念:地址与指向
- 内存地址:计算机的内存被划分为许多个最小单元(通常是字节),每个单元都有一个唯一的编号,这个编号就是内存地址。
- 指针变量:它不存储普通数据,而是专门用来存储这些内存地址。
- 指向:当一个指针变量存储了某个普通变量的地址时,我们就说这个指针“指向”了那个变量。
类比:
- 内存地址 就像酒店的 房间号。
- 变量中存储的数据 就像房间里住的 客人。
- 指针 就像一张记录着某个房间号的 门禁卡。通过这张卡,你可以直接找到并进入对应的房间,与里面的客人互动。
2. 指针的定义与基本操作
定义指针的格式为:数据类型 *指针变量名;
这里的 * 表明这是一个指针变量。而前面的 数据类型 则规定了该指针 指向的内存区域中存放的是什么类型的数据。这决定了指针在进行解引用和算术运算时的行为。
两大核心操作符:
&(取地址符):获取一个变量的内存地址。*(解引用/间接访问符):通过指针存储的地址,访问或修改其指向的内存中的数据。
注意:定义指针时的 int *p; 和使用指针时的 *p = 10; 中,* 的含义不同。
- 定义时 (
int *p;):*是类型说明符,表示p是一个指针。 - 使用时 (
*p = ...):*是运算符,表示“获取指针p指向的地址中的数据”。
【代码示例:指针的基本使用】
#include <stdio.h>
int main() {
// 1. 定义一个普通变量
int a = 10;
printf("变量 a 的值:%d\n", a); // 输出: 10
printf("变量 a 的地址:%p\n", &a); // %p 用于打印地址 (十六进制)
// 2. 定义指针并指向变量 a
int *p; // 定义一个指向 int 类型的指针 p
p = &a; // 将 a 的地址赋值给 p,现在 p 指向 a
// 上面两步可合并为一步初始化:int *p = &a;
printf("指针 p 中存储的地址:%p\n", p); // 输出与 &a 相同的地址
printf("通过指针 p 访问到的值:%d\n", *p); // 解引用 p,获取 a 的值,输出: 10
// 3. 通过指针修改变量的值
*p = 20; // 解引用 p,将 20 存入 p 指向的内存(即变量 a 的内存)
printf("通过指针修改后,变量 a 的值:%d\n", a); // a 的值被改变,输出: 20
return 0;
}
3. 指针的大小
一个指针变量自身占用多少内存,仅取决于操作系统的寻址能力(位数),而与它指向的数据类型无关。
- 在 32位 系统上,内存地址是32位的,因此任何类型的指针都占用 4字节。
- 在 64位 系统上,内存地址是64位的,因此任何类型的指针都占用 8字节。
【代码示例:验证指针大小】
#include <stdio.h>
int main() {
int *p_int;
char *p_char;
double *p_double;
// sizeof 返回 size_t 类型,推荐用 %zu 格式化输出
printf("int* 指针的大小:%zu 字节\n", sizeof(p_int));
printf("char* 指针的大小:%zu 字节\n", sizeof(p_char));
printf("double* 指针的大小:%zu 字节\n", sizeof(p_double));
// 在64位系统上,输出将全部是 8
// 在32位系统上,输出将全部是 4
return 0;
}
4. 指针作为函数参数(地址传递)
默认情况下,C语言函数参数是“值传递”,即函数内部操作的是外部实参的一个副本,无法改变实参本身。
通过传递指针(即变量的地址),函数可以直接访问并修改实参的内存,从而实现对实参的修改。这通常被称为“地址传递”。
【代码示例:使用指针交换两个变量的值】
#include <stdio.h>
// 参数是两个 int 指针,它们接收的是外部变量的地址
void swap(int *x, int *y) {
int temp = *x; // 读取 x 指向的地址中的值 (即 a 的值)
*x = *y; // 将 y 指向的值赋给 x 指向的内存 (a = b)
*y = temp; // 将暂存的值赋给 y 指向的内存 (b = temp)
}
int main() {
int a = 10, b = 20;
printf("交换前:a = %d, b = %d\n", a, b);
// 调用 swap 时,传入 a 和 b 的地址
swap(&a, &b);
printf("交换后:a = %d, b = %d\n", a, b);
return 0;
}
5. 指针与数组
在C语言中,指针和数组关系紧密:
- 数组名就是数组首元素的地址。它是一个地址常量,不能被赋值修改。
int arr[5]; int *p = arr;这两行代码等价于int *p = &arr[0];。- 对指针进行算术运算(如
p++)时,指针会移动“它所指向的数据类型”的大小。例如,一个int*指针p++后,地址会增加sizeof(int)个字节。 arr[i]和*(arr + i)在效果上是等价的。
【代码示例:使用指针遍历数组】
#include <stdio.h>
int main() {
int arr[5] = {10, 20, 30, 40, 50};
int *p = arr; // 指针 p 指向数组首元素 arr[0]
printf("使用指针遍历数组:");
for (int i = 0; i < 5; i++) {
// 第一次循环 *p 是 arr[0],p++ 后 p 指向 arr[1],以此类推
printf("%d ", *p);
p++; // 指针移动到下一个元素
}
printf("\n");
// 验证数组下标与指针算术的等价性
printf("arr[2] 的值是 %d\n", arr[2]); // 输出: 30
printf("*(arr + 2) 的值是 %d\n", *(arr + 2)); // 同样输出: 30
return 0;
}
更多推荐

所有评论(0)