C语言编程之旅:从入门到实战
第一节课:C 语言基础知识
第一个 C 程序
1. Hello World 代码展示:
#include <stdio.h> // 预处理指令,包含标准输入输出头文件
int main() { // 主函数,程序入口
printf("Hello, World!\n"); // 输出函数,将双引号内的内容输出到控制台,\n表示换行
return 0; // 返回值,0表示程序正常结束
}
-
#include <stdio.h>:这是预处理指令,<stdio.h>是标准输入输出头文件,包含了输入输出函数(如printf)的声明。在程序编译前,预处理器会将该头文件的内容插入到此处,使得程序可以使用这些函数。 -
int main():main函数是 C 程序的入口点,每个 C 程序都必须有一个main函数。int表示main函数的返回值类型是整数。程序从main函数开始执行。 -
printf("Hello, World!\n");:printf是标准库中的输出函数,用于将双引号内的字符串输出到控制台。\n是转义字符,表示换行,即输出完 “Hello, World!” 后光标会移动到下一行。 -
return 0;:return语句用于结束函数,并返回一个值。这里返回 0,通常表示程序正常执行结束。在 C 语言中,main函数返回值为 0 表示程序成功完成,非 0 值表示程序执行过程中出现了错误。
2. 程序结构解析:
-
函数的组成:一个函数由函数头和函数体组成。
int main()是函数头,其中int是返回值类型,main是函数名,括号内可以包含参数列表(这里为空,用void或空括号表示)。{}之间的部分是函数体,包含了函数要执行的具体语句。 -
头文件的作用:头文件包含了函数声明、类型定义、宏定义等内容。通过
#include指令将头文件包含进来,程序就能使用头文件中定义的函数和符号。除了标准库头文件(如stdio.h),我们还可以自定义头文件,用于组织和复用代码。 -
注释的使用:注释是对代码的解释说明,不会被编译器执行。C 语言中有两种注释方式,一种是单行注释,以
//开头,如// 这是单行注释;另一种是多行注释,以/*开头,以*/结尾,如/* 这是多行注释,可以跨越多行 */。合理使用注释可以提高代码的可读性。
变量与数据类型
1. 基本数据类型:
-
整型(int):用于存储整数,在 32 位系统中,通常占用 4 个字节(32 位),取值范围是 - 2147483648 到 2147483647 。
int num = 10;定义了一个整型变量
num并初始化为 10。 -
浮点型(float、double):
-
float:单精度浮点型,用于存储小数,占用 4 个字节,能精确表示大约 6 - 7 位有效数字。
float f = 3.14f; //注意这里的`f`后缀表示这是一个单精度浮点数。 -
double:双精度浮点型,占用 8 个字节,能精确表示大约 15 - 16 位有效数字。
double d = 3.141592653589793;
-
-
字符型(char):用于存储单个字符,占用 1 个字节。字符型在内存中存储的是字符对应的 ASCII 码值。
char ch = 'A';定义了一个字符型变量
ch并初始化为字符'A',其对应的 ASCII 码值是 65。
2. 变量命名规则:
-
变量名只能由字母、数字和下划线组成,且不能以数字开头。例如,
age、_name是合法的变量名,而123num是非法的。 -
变量名不能是 C 语言的关键字,如
int、return、if等。 -
变量名应具有描述性,能清晰地表达变量的用途,如用
studentAge表示学生的年龄,而不是用无意义的a``b``c。 -
保持命名风格一致性,如统一使用驼峰命名法(如
StudentName)或下划线命名法(如student_name)。
3. 变量的声明与初始化:
-
先声明后赋值:先声明变量类型和名称,然后再给变量赋值。
int num; // 声明一个整型变量num num = 20; // 给num赋值为20 -
一次声明多个变量:可以在同一行声明多个相同类型的变量,用逗号分隔。
int a, b, c; // 声明三个整型变量a、b、c a = 1; b = 2; c = 3; // 分别给a、b、c赋值 -
初始化:在声明变量的同时给变量赋初值。
float f = 3.14f; // 声明并初始化一个单精度浮点型变量f char ch = 'B'; // 声明并初始化一个字符型变量ch -
修改变量值:可以在变量声明后随时修改变量的值。
int num = 10; num = num + 5; // 将num的值修改为15
运算符与表达式
1. 算术运算符:
-
加(+):用于两个数相加。例如
int sum = 3 + 5;,sum的值为 8。 -
减(-):用于两个数相减。例如
int diff = 8 - 3;,diff的值为 5。 -
乘(*):用于两个数相乘。例如
int product = 4 * 6;,product的值为 24。 -
除(/):用于两个数相除。注意,整数相除结果为整数,会舍去小数部分。例如
int result = 7 / 2;,result的值为 3。
若要得到小数结果,则至少有一个操作数为浮点数,如float fResult = 7.0f / 2;,fResult的值为 3.5。 -
取余(%):用于求两个整数相除的余数,操作数必须为整数。例如
int remainder = 7 % 2;,remainder的值为 1。
2. 运算符优先级:运算符优先级决定了表达式中各运算符的计算顺序。
在表达式3 + 4 * 2中,乘法的优先级高于加法,所以先计算4 * 2 = 8,再计算3 + 8 = 11。
常见的运算符优先级口诀:“括号成员第一;全体单目第二;乘除余三,加减四;移位五,关系六;等于(与)不等排第七;位与异或和位或,“三分天下” 八九十;逻辑或跟与,十一和十二;条件高于赋值,逗号运算级最低”。
通过加括号可以改变运算顺序,如(3 + 4) * 2,先计算括号内的3 + 4 = 7,再计算7 * 2 = 14。
输入输出函数
1. printf 函数:
-
基本格式化输出:
printf函数用于将格式化的数据输出到控制台。printf("The number is %d\n", num);其中
%d是格式控制符,表示输出一个十进制整数,num是要输出的变量。 -
控制输出格式:
-
限定宽度:可以使用
%nd来限定输出整数的宽度,n表示宽度。int num = 123; printf("%5d", num);会输出 " 123"(前面有两个空格,使总宽度为 5)。
-
左对齐:默认是右对齐,使用
%-nd可实现左对齐。printf("%-5d", num);会输出 "123 "(后面有两个空格,使总宽度为 5)。
-
显示正负号:使用
%+d可显示整数的正负号。int num = -10; printf("%+d", num);会输出 “-10”。
-
限定小数位数:对于浮点数,使用
%.nf来限定小数位数,n表示小数位数。float f = 3.14159; printf("%.2f", f);会输出 “3.14”。
-
2. scanf 函数:scanf函数用于从标准输入设备(通常是键盘)读取数据。
int num;
scanf("%d", &num);
%d是格式控制符,表示读取一个十进制整数。
&num表示取num的地址,将读取到的数据存储到num变量中。
注意,使用scanf函数时要确保输入的数据类型与格式控制符匹配。
分支结构与循环结构
1. 分支结构:
- if - else 语句:根据条件判断执行不同的代码块。
int num = 10;
if (num > 5) {
printf("The number is greater than 5\n");
} else {
printf("The number is less than or equal to 5\n");
}
首先判断num > 5这个条件是否成立,如果成立,执行if后面花括号内的语句,即输出 “The number is greater than 5”;
如果不成立,执行else后面花括号内的语句,即输出 “The number is less than or equal to 5”。
- if - else if - else 语句:用于多条件判断。
int score = 85;
if (score >= 90) {
printf("Grade: A\n");
} else if (score >= 80) {
printf("Grade: B\n");
} else if (score >= 70) {
printf("Grade: C\n");
} else {
printf("Grade: D\n");
}
依次判断条件,当score >= 90成立时,输出 “Grade: A”;
不成立则判断score >= 80,成立输出 “Grade: B”;
以此类推,若前面条件都不成立,执行else后面的语句,输出 “Grade: D”。
2. 循环结构:
- for 循环:常用于已知循环次数的情况。例如计算 1 到 10 的累加和:
int sum = 0;
for (int i = 1; i <= 10; i++) {
sum = sum + i;
}
printf("The sum from 1 to 10 is %d\n", sum);
for循环有三个表达式:
int i = 1是初始化表达式,只在循环开始时执行一次,用于初始化循环变量i为 1;
i <= 10是条件表达式,每次循环开始时判断,若条件成立则执行循环体,否则结束循环;
i++是更新表达式,每次循环结束后执行,用于更新循环变量i,使i自增 1。在循环体中,将i累加到sum中。
- while 循环:先判断条件,条件成立再执行循环体。例如输出 1 到 5 的数字:
int num = 1;
while (num <= 5) {
printf("%d ", num);
num++;
}
首先判断num <= 5是否成立,成立则执行循环体,输出num的值并将num自增 1,然后再次判断条件,直到条件不成立时结束循环。
- do - while 循环:先执行一次循环体,再判断条件。
int num = 1;
do {
printf("%d ", num);
num++;
} while (num <= 5);
先执行循环体,输出num的值并将num自增 1,然后判断num <= 5是否成立,成立则继续执行循环体,否则结束循环。
与while循环不同,do - while循环至少会执行一次循环体。
数组
1. 数组的定义:数组是一组相同类型数据的集合,能让我们更方便地管理多个同类型变量。例如,定义一个包含 5 个整数的数组:
int numbers[5]; // 定义一个名为numbers的整型数组,数组大小为5,可存储5个int类型数据
2. 数组的初始化:在定义数组时给元素赋值,避免使用未初始化的“脏数据”。
- 部分初始化:只初始化数组的部分元素,未初始化的元素会被自动初始化为 0(数值型数组)或空字符(字符型数组)。
int numbers[5] = {1, 2, 3}; // 数组前三个元素为1、2、3,后两个元素自动设为0
- 完全初始化:初始化数组的所有元素,元素个数需与数组大小一致。例如:
int numbers[5] = {1, 2, 3, 4, 5}; // 5个元素分别对应1、2、3、4、5
- 省略数组大小初始化:初始化时不写数组大小,编译器会根据初始化值的个数自动计算大小。例如:
int numbers[] = {1, 2, 3, 4, 5}; // 编译器自动确定数组大小为5,无需手动计算
3. 数组元素的访问:通过“数组名[下标]”访问,下标从 0 开始(即第一个元素下标为 0,最后一个为“数组大小-1”)。
int numbers[5] = {1, 2, 3, 4, 5};
// 访问并打印数组元素
printf("第一个元素:%d\n", numbers[0]); // 输出1(下标0对应第一个元素)
printf("第三个元素:%d\n", numbers[2]); // 输出3(下标2对应第三个元素)
// 修改数组元素
numbers[1] = 10; // 将第二个元素(下标1)的值从2改为10
printf("修改后第二个元素:%d\n", numbers[1]); // 输出10
4. 数组的遍历:用循环(常用for循环)依次访问数组的所有元素,适合批量处理数据。
int numbers[5] = {1, 2, 3, 4, 5};
// i从0到4(数组大小-1),依次遍历每个元素
for (int i = 0; i < 5; i++) {
printf("numbers[%d] = %d\n", i, numbers[i]);
}
// 输出结果:依次打印1、2、3、4、5
函数
1. 函数的定义:函数是一段实现特定功能的代码块,可重复调用,避免代码重复编写。
定义格式为:返回值类型 函数名(参数列表) { 函数体 }。
例如,定义一个计算两数之和的函数:
// 返回值类型为int,函数名add,参数列表为两个int类型变量a和b
int add(int a, int b) {
int sum = a + b; // 函数体:计算a和b的和
return sum; // 返回计算结果sum
}
2. 函数的调用:使用“函数名(参数)”的格式调用函数,获取函数的返回结果或执行函数功能。
例如,调用上述add函数:
#include <stdio.h> // 需包含stdio.h才能使用printf
// 先定义add函数(函数调用前必须先定义或声明)
int add(int a, int b) {
return a + b;
}
int main() {
int num1 = 3, num2 = 5;
// 调用add函数,传入num1和num2作为参数,将返回值赋给result
int result = add(num1, num2);
printf("3 + 5 = %d\n", result); // 输出结果:3 + 5 = 8
return 0;
}
3. 函数的声明:若函数定义在调用之后(如main函数之后),需先声明函数,告诉编译器函数的“样子”。
声明格式为:返回值类型 函数名(参数列表);。
#include <stdio.h>
// 函数声明:说明有一个int类型返回值、名为add、参数为两个int的函数
int add(int a, int b);
int main() {
int result = add(4, 6); // 此时可正常调用,无需担心函数未定义
printf("4 + 6 = %d\n", result); // 输出10
return 0;
}
// 函数定义(在调用之后,需先声明)
int add(int a, int b) {
return a + b;
}
4. 无返回值与无参数函数:
- 无返回值函数:返回值类型用
void,函数体中无需return(或仅写return;),常用于执行打印、修改变量等操作。
void printHello() { // 无参数、无返回值的函数
printf("Hello, C language!\n"); // 仅执行打印功能
}
int main() {
printHello(); // 调用函数,直接执行打印,无需接收返回值
return 0;
}
- 无参数函数:参数列表用
void表示,说明函数不需要接收参数。
例如上述printHello()函数,参数列表可写为void,即void printHello(void)。
指针
1. 指针的定义:指针是存储变量“内存地址”的变量,相当于变量的“门牌号”。
定义格式为:数据类型 *指针名;,其中“*”表示该变量是指针。
例如,定义一个指向int类型变量的指针:
int num = 10; // 定义普通int变量num,值为10
int *p; // 定义int类型指针p,专门存储int变量的地址
p = # // 将num的地址(&是取地址符)赋值给指针p
// 此时p中存储的是num的地址,通过p可找到num
2. 指针的解引用:用“*指针名”的格式访问指针指向的变量(即通过“门牌号”找到对应的变量),称为“解引用”。
#include <stdio.h>
int main() {
int num = 10;
int *p = # // 定义指针p并直接赋值为num的地址(一步完成)
printf("num的值:%d\n", num); // 输出10(直接访问num)
printf("num的地址:%p\n", &num); // 输出num的地址(如0x7ffeefbff4c4)
printf("p存储的地址:%p\n", p); // 输出与&num相同的地址(p指向num)
printf("p指向的值:%d\n", *p); // 解引用p,输出10(等同于num的值)
*p = 20; // 解引用p并修改值,相当于修改num的值
printf("修改后num的值:%d\n", num); // 输出20(num被指针修改)
return 0;
}
3. 指针与数组的关系:数组名本质是数组第一个元素的地址(即数组名 == &数组[0]),可通过指针遍历数组。
#include <stdio.h>
int main() {
int numbers[5] = {1, 2, 3, 4, 5};
int *p = numbers; // 数组名numbers是&numbers[0],指针p指向数组第一个元素
// 通过指针遍历数组(p++表示指针向后移动一个int类型的大小)
for (int i = 0; i < 5; i++) {
printf("numbers[%d] = %d\n", i, *(p + i)); // 等同于numbers[i]
}
// 输出结果:依次打印1、2、3、4、5
return 0;
}
4. 空指针与野指针:
- 空指针:指向“空地址”(用
NULL表示)的指针,说明指针暂时不指向任何变量,避免乱指。int *p = NULL; - 野指针:未初始化或指向已释放内存的指针,使用野指针会导致程序崩溃,需避免。
int *p; //(未初始化,p的值随机,是野指针)
结构体
1. 结构体的定义:结构体是自定义的数据类型,可将不同类型的数据(如int、char、float)组合在一起,描述一个“复杂对象”。
定义格式为:struct 结构体名 { 成员列表 };。
例如,定义一个描述“学生”的结构体:
// 定义名为Student的结构体,包含学号、姓名、年龄三个成员
struct Student {
int id; // 整型成员:学号
char name[20]; // 字符数组成员:姓名(最多存19个字符,最后一个为\0)
int age; // 整型成员:年龄
};
2. 结构体变量的定义与初始化:定义结构体变量后,可像普通变量一样使用,初始化时需给每个成员赋值。
#include <stdio.h>
// 先定义结构体
struct Student {
int id;
char name[20];
int age;
};
int main() {
// 方式1:定义时初始化(按成员顺序赋值)
struct Student stu1 = {101, "Zhang San", 18};
// 方式2:指定成员名初始化(顺序可任意)
struct Student stu2 = {
.id = 102,
.name = "Li Si",
.age = 19
};
printf("stu1:学号=%d,姓名=%s,年龄=%d\n", stu1.id, stu1.name, stu1.age);
printf("stu2:学号=%d,姓名=%s,年龄=%d\n", stu2.id, stu2.name, stu2.age);
return 0;
}
3. 结构体成员的访问:
- 普通结构体变量:用“变量名.成员名”访问。例如
stu1.id、stu1.name。 - 结构体指针:用“指针名->成员名”访问(或
(*指针名).成员名,前者更简洁)。
#include <stdio.h>
struct Student {
int id;
char name[20];
};
int main() {
struct Student stu = {103, "Wang Wu"};
struct Student *p = &stu; // 定义结构体指针p,指向stu
// 两种访问方式等价
printf("学号:%d\n", p->id); // 指针用->访问成员,输出103
printf("姓名:%s\n", (*p).name); // 解引用后用.访问,输出Wang Wu
return 0;
}
4. 结构体数组:存储结构体变量的数组,可批量管理多个复杂对象(如多个学生)。
#include <stdio.h>
struct Student {
int id;
char name[20];
int age;
};
int main() {
// 定义结构体数组,存储3个学生信息
struct Student students[3] = {
{101, "Zhang San", 18},
{102, "Li Si", 19},
{103, "Wang Wu", 18}
};
// 遍历结构体数组,打印每个学生信息
for (int i = 0; i < 3; i++) {
printf("第%d个学生:学号=%d,姓名=%s,年龄=%d\n",
i+1, students[i].id, students[i].name, students[i].age);
}
return 0;
}

浙公网安备 33010602011771号