第一节课:C 语言基础知识
第一个 C 程序
- 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 值表示程序执行过程中出现了错误。
- 程序结构解析:
-
函数的组成:一个函数由函数头和函数体组成。例如
main
函数,int main()
是函数头,其中int
是返回值类型,main
是函数名,括号内可以包含参数列表(这里为空,用void
或空括号表示)。{}
之间的部分是函数体,包含了函数要执行的具体语句。 -
头文件的作用:头文件包含了函数声明、类型定义、宏定义等内容。通过
#include
指令将头文件包含进来,程序就能使用头文件中定义的函数和符号。除了标准库头文件(如stdio.h
),我们还可以自定义头文件,用于组织和复用代码。 -
注释的使用:注释是对代码的解释说明,不会被编译器执行。C 语言中有两种注释方式,一种是单行注释,以
//
开头,如// 这是单行注释
;另一种是多行注释,以/*
开头,以*/
结尾,如/* 这是多行注释,可以跨越多行 */
。合理使用注释可以提高代码的可读性。
变量与数据类型
- 基本数据类型:
-
整型(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。
- 变量命名规则:
-
变量名只能由字母、数字和下划线组成,且不能以数字开头。例如,
age
、_name
是合法的变量名,而123num
是非法的。 -
变量名不能是 C 语言的关键字,如
int
、return
、if
等。 -
变量名应具有描述性,能清晰地表达变量的用途,如用
studentAge
表示学生的年龄,而不是用无意义的a
。 -
保持命名风格一致性,如统一使用驼峰命名法(如
studentName
)或下划线命名法(如student_name
)。
- 变量的声明与初始化:
- 先声明后赋值:先声明变量类型和名称,然后再给变量赋值。例如:
int num; // 声明一个整型变量numnum = 20; // 给num赋值为20
- 一次声明多个变量:可以在同一行声明多个相同类型的变量,用逗号分隔。例如:
int a, b, c; // 声明三个整型变量a、b、ca = 1; b = 2; c = 3; // 分别给a、b、c赋值
- 初始化:在声明变量的同时给变量赋初值。例如:
float f = 3.14f; // 声明并初始化一个单精度浮点型变量fchar ch = 'B'; // 声明并初始化一个字符型变量ch
- 修改变量值:可以在变量声明后随时修改变量的值。例如:
int num = 10;num = num + 5; // 将num的值修改为15
运算符与表达式
- 算术运算符:
-
加(+):用于两个数相加。例如
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。
- 运算符优先级:运算符优先级决定了表达式中各运算符的计算顺序。例如,在表达式
3 + 4 * 2
中,乘法的优先级高于加法,所以先计算4 * 2 = 8
,再计算3 + 8 = 11
。常见的运算符优先级口诀:“括号成员第一;全体单目第二;乘除余三,加减四;移位五,关系六;等于(与)不等排第七;位与异或和位或,“三分天下” 八九十;逻辑或跟与,十一和十二;条件高于赋值,逗号运算级最低”。通过加括号可以改变运算顺序,如(3 + 4) * 2
,先计算括号内的3 + 4 = 7
,再计算7 * 2 = 14
。
输入输出函数
- 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”。 -
显示正负号:使用
%+d
可显示整数的正负号。例如int num = -10; printf("%+d", num);
,会输出 “-10”。 -
限定小数位数:对于浮点数,使用
%.nf
来限定小数位数,n
表示小数位数。例如float f = 3.14159; printf("%.2f", f);
,会输出 “3.14”。
-
- scanf 函数:
scanf
函数用于从标准输入设备(通常是键盘)读取数据。例如int num; scanf("%d", &num);
,%d
是格式控制符,表示读取一个十进制整数,&num
表示取num
的地址,将读取到的数据存储到num
变量中。注意,使用scanf
函数时要确保输入的数据类型与格式控制符匹配,否则可能会导致错误。
分支结构与循环结构
- 分支结构:
- 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”。
- 循环结构:
- 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
循环至少会执行一次循环体。
函数
- 函数的定义:函数是一段完成特定任务的独立代码块。定义函数的一般形式为:返回值类型 函数名 (参数列表) { 函数体 }。例如,定义一个计算两个整数之和的函数:
int add(int a, int b) { // 返回值类型为int,函数名add,参数列表为两个int型参数a和bint sum = a + b; // 函数体,计算a和b的和并存储在sum变量中return sum; // 返回计算结果sum}
- 函数的调用:在主函数或其他函数中可以调用已定义的函数。例如在
main
函数中调用上面定义的add
函数:
int main() {int num1 = 3, num2 = 5;int result = add(num1, num2); // 调用add函数,将返回值赋给result变量printf("The sum of %d and %d is %d\n", num1, num2, result);return 0;}
数组
- 数组的定义:数组是一组相同类型数据的集合,能让我们更方便地管理多个同类型变量。例如,定义一个包含 5 个整数的数组:
int numbers[5]; // 定义一个名为numbers的整型数组,数组大小为5,可存储5个int类型数据
- 数组的初始化:在定义数组时给元素赋值,避免使用未初始化的“脏数据”。
- 部分初始化:只初始化数组的部分元素,未初始化的元素会被自动初始化为 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,无需手动计算
- 数组元素的访问:通过“数组名[下标]”访问,下标从 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
- 数组的遍历:用循环(常用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
函数
- 函数的定义:函数是一段实现特定功能的代码块,可重复调用,避免代码重复编写。定义格式为:
返回值类型 函数名(参数列表) { 函数体 }
。例如,定义一个计算两数之和的函数:
// 返回值类型为int,函数名add,参数列表为两个int类型变量a和b
int add(int a, int b) {int sum = a + b; // 函数体:计算a和b的和return sum; // 返回计算结果sum
}
- 函数的调用:使用“函数名(参数)”的格式调用函数,获取函数的返回结果或执行函数功能。例如,调用上述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作为参数,将返回值赋给resultint result = add(num1, num2);printf("3 + 5 = %d\n", result); // 输出结果:3 + 5 = 8return 0;
}
- 函数的声明:若函数定义在调用之后(如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); // 输出10return 0;
}// 函数定义(在调用之后,需先声明)
int add(int a, int b) {return a + b;
}
- 无返回值与无参数函数:
- 无返回值函数:返回值类型用
void
,函数体中无需return
(或仅写return;
),常用于执行打印、修改变量等操作。例如:
void printHello() { // 无参数、无返回值的函数printf("Hello, C language!\n"); // 仅执行打印功能
}int main() {printHello(); // 调用函数,直接执行打印,无需接收返回值return 0;
}
- 无参数函数:参数列表用
void
表示,说明函数不需要接收参数。例如上述printHello()
函数,参数列表可写为void
,即void printHello(void)
。
指针
- 指针的定义:指针是存储变量“内存地址”的变量,相当于变量的“门牌号”。定义格式为:
数据类型 *指针名;
,其中“*”表示该变量是指针。例如,定义一个指向int类型变量的指针:
int num = 10; // 定义普通int变量num,值为10
int *p; // 定义int类型指针p,专门存储int变量的地址
p = # // 将num的地址(&是取地址符)赋值给指针p
// 此时p中存储的是num的地址,通过p可找到num
- 指针的解引用:用“*指针名”的格式访问指针指向的变量(即通过“门牌号”找到对应的变量),称为“解引用”。例如:
#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;
}
- 指针与数组的关系:数组名本质是数组第一个元素的地址(即
数组名 == &数组[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、5return 0;
}
- 空指针与野指针:
- 空指针:指向“空地址”(用
NULL
表示)的指针,说明指针暂时不指向任何变量,避免乱指。例如:int *p = NULL;
。 - 野指针:未初始化或指向已释放内存的指针,使用野指针会导致程序崩溃,需避免。例如:
int *p;
(未初始化,p的值随机,是野指针)。
结构体
- 结构体的定义:结构体是自定义的数据类型,可将不同类型的数据(如int、char、float)组合在一起,描述一个“复杂对象”。定义格式为:
struct 结构体名 { 成员列表 };
。例如,定义一个描述“学生”的结构体:
// 定义名为Student的结构体,包含学号、姓名、年龄三个成员
struct Student {int id; // 整型成员:学号char name[20]; // 字符数组成员:姓名(最多存19个字符,最后一个为\0)int age; // 整型成员:年龄
};
- 结构体变量的定义与初始化:定义结构体变量后,可像普通变量一样使用,初始化时需给每个成员赋值。例如:
#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;
}
- 结构体成员的访问:
- 普通结构体变量:用“变量名.成员名”访问。例如
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); // 指针用->访问成员,输出103printf("姓名:%s\n", (*p).name); // 解引用后用.访问,输出Wang Wureturn 0;
}
- 结构体数组:存储结构体变量的数组,可批量管理多个复杂对象(如多个学生)。例如:
#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;
}