C 语言零基础入门教程(基础版)
版本:2025/08/04
前言
本来写这篇教程是为了期末周教一下室友,后面觉得也可以拿出来放到网上。本人只是一个业余 C 语言程序员,非专业 C 语言程序设计教师,亦无相关比赛参赛经验,文章内容有误还请指出。
本文不涉及指针、数据结构相关内容(因为我的大学 C 语言程序设计课程也不考这些)。
硬件基础
键盘
SHIFT:切换(上档),打出印有两行文字的按键中,上面一行文字的按键。
CTRL:控制键,常与其他字母组合成快捷键。如复制Ctrl + C
、粘贴Ctrl + V
等。
CAPS LOCK:大写锁定,按下后字母键均打出大写字母。
TAB:缩进,自动添加四个空格或制表符,使得文字显示可以对齐。
HOME:跳转到行首。
END:跳转到行尾。
操作系统基础
所有的 C 语言程序必须有 main()
函数。操作系统在执行程序时,始终从 main 函数开始执行。
程序结束后,必须给操作系统返回一个状态值,操作系统以此判断程序是否正常运行/退出。
main
函数是所有 C 语言程序的入口点,其函数一般以return
+返回值结尾,用于向系统返回一个状态值。若使用return 0;
代表程序正常退出(正常执行完毕),所有非0的返回值,代表程序出现故障,非零返回值的具体含义只有编写代码的人才知道,但操作系统知道“非0返回值代表程序运行出了问题”这个共识。
计算机是无法直接运行程序的“源代码”的,源代码需要被“编译器”编译后,链接生成可以由计算机运行的“机器码”。这是高级编程语言的特性。
全角字符和半角字符,中英文输入:
中文的字符,!?“” —— 都是全角字符。
英文的字符,! ? "" - 都是半角字符。
C 语言程序代码中应当使用半角字符。
操作系统的文件通常由两部分组成:文件名(Filename)和后缀(Suffix)。部分 Windows 系统会默认开启“隐藏已知文件的扩展名”(这里的扩展名就是文件后最)。文件后缀通常用以区分文件的类型。如.txt
是文本文件,.xlsx
是 Excel 表格,.mp3
是 MP3 格式音频文件,.h
是C
/C++
语言头文件等。
C or C++
什么是 C 语言,什么是 C++ 语言?
顾名思义 C++ 与 C 相比,后面多了两个“++”,一看就更高级。从事实上来讲也确实如此。
C 语言是面向过程的编程语言,程序设计的核心思想是“分步”,依次(按照时序图)实现所有需要的操作。
C++ 语言是面向对象的编程语言,程序设计的核心思想是“对象”,可以使用“对象”支持的特性(如“多态”、“继承”等),通过将所需功能的实现载体抽象为许多不同的对象,实现对象间的操作,来实现程序的功能。
比如在经典案例“学生管理系统”中,将“学生”抽象为Student
对象,给这个对象实现了名字(name
)、学号(number
)、年级(year
)等属性(变量),并实现对这些属性(变量)的操作。
C++ 是兼容 C 语言的,但 C 语言不能使用 C++ 语言的代码。
C 语言程序源文件通常以.c
结尾,C++ 语言程序源文件通常以.cpp
结尾。
进制 —— 二进制、八进制、十进制、十六进制
“进制” —— 一个数,不只有一位。例如在十进制中,我们有个位、十位、百位、千位...
十进制的 1763
日常生活中使用的是十进制,即满十进一,例如:
- 10 + 10 = 20
- 9 + 1 = 10
- 19 + 1 = 20
二进制即为满二进一,例如:
- 1 + 1 = 10 (不是十,十一零)
- 0 + 1 = 1
- 10 + 1 = 11 (不是十一,是一一)
- 11 + 1 = 100 (不是一百,是一零零)
十六进制即为满十六进一,十六进制引入了字母来表示从 10 到 15 的“数”。
即:0 1 2 3 4 5 6 7 8 9 a b c d e f
。对应关系:
Dec | Hex |
---|---|
0 | 0 |
1 | 1 |
2 | 2 |
3 | 3 |
4 | 4 |
5 | 5 |
6 | 6 |
7 | 7 |
8 | 8 |
9 | 9 |
10 | a |
11 | b |
12 | c |
13 | d |
14 | e |
15 | f |
0x
后跟十六进制数。
十进制 十六进制
十位 个位 十位 个位
0 9 0 f
+
-----------------------
0 1 0 f
10 0x1e
ASCII(美国信息交换标准代码)
ASCII(或习惯称“ASCII码”)为美国信息交换标准代码,ASCII码的第32~126为可读字符。
也就是说我们的数字(可读字符形式)、字母(大写和小写)、符号(如感叹号、问号、警号、与号、星号等)都有一个唯一对应的数字(十进制、十六进制)来表示,这些“字符”可以和其唯一对应的数字相互转换。
字符 十进制ASCII码0
48A
65a
97
命名
在 C 语言中,我们会遇到很多命名,变量的命名、函数的命名。
C 语言编程中,所有的名字都是严格区分大小写的。例如 Abc
和 abc
和 abC
,他们三个指代不同的东西。
命名需要遵守命名规范。
即:
名称允许包含大小写英文字母和阿拉伯数字(不建议使用中文)和下划线,但是有条件:
- 名称不得以数字开头,允许下划线开头;
- 名称不得出现允许字符之外的字符(即只允许出现上述规定的字符);
- 名称不得与 C 语言中现存“关键字”重复;
- 一个变量/函数只能有一个“唯一的”名字,同一个名字不得重复使用。
关键字:const
/int
/char
/float
/auto
/register
/extern
/if
/while
...
int a_b;
int 0ac; // 错误
int Ab0;
int _0a;
int _;
int Aiojsdajio;
int a91ja;
int const; // 错误
int inta;
int int_jioa;
int _int;
int Abc = 0;
int ABC= 0;
int Abc =0; // 错误,不得与已定义变量名重复
变量
变量的实质,是操作系统,根据变量的类型(所占的内存空间大小)向内存中分配一块恒定大小的内存空间区域,随后变量指向该内存区域的地址。使用变量的过程就是向该地址读写数据。
基本数据类型
int 整型:正整数、负整数、0
- short 短
- long 长
- long long
- long long long...
- float 实型(单精度浮点型):精度为小数点后六(或七)位,多的位数会四舍五入到最后一位精度。
- double 实型(双精度浮点型):精度为小数点后十五位,多的位数会四舍五入到最后一位精度。
- char 字符型:单个字符
void 无/空类型(特殊)
注:使用prinf()
函数%f
/%lf
输出时,float 和 double 默认保留 6 位小数,不足 6 位,以 0 补齐,超出则第 6 位会进行四舍五入。
注:字符串必须用双引号包裹。
char A = 'A';
char a = 'a';
char nihao = 'nihao'; // 错误
char A = "A"; // 可能错误
char *A = "A";
char *nihao = "Hello"; // 字符串(指针类型)
char nihao[5] = "Hello"; // 字符串(数组类型)
char nihao[5] = {'H', 'e', 'l', 'l', 'o'}; // 字符串(数组类型)
变量的定义
(存储类型) 数据类型 变量名 = 变量值(与类型匹配或支持类型转换);
通常存储类型可以省略不写,不写则默认为auto
(代表“动态变量”)。而存储类型表示变量的存储方式和位置。全局变量为static
静态变量。
如:
int ZERO = 0;
char A = 'A';
register int zero = 0; // 寄存器类型,变量会直接存储在 CPU 的寄存器中
static int _O = 'O'; // 静态类型,即使函数的生命周期结束,变量也不会被消失,且保留原值
变量的初始化
变量的初始化有两种方式:定义时赋值和先定义,后赋值(编译器会用默认值初始化)。
int A = 0;
int A; // 部分编译器会默认 A = 0
int main() {
A = 1; // 此处对 A 人为赋值
return A;
}
变量的赋值
变量可以直接被赋值为固定值:
int A = 100;
也可以被赋值为某个函数的返回值:
int _function() {
return 333;
}
int main() {
int A = _function();
printf("%d\n", A);
return 0;
}
常量
常量即长时间存在的量,不会改变,也不能改变,通常伴随整个程序或函数定义域内的生命周期。
常量,我们用关键词const
修饰,被const
修饰的“变量”为不得修改的常量。常量的初始化必须在定义时进行。
类型转换
C 语言中,有很多不同的数据类型,比如整型、实型、字符型,实型甚至都分单精度浮点型和双精度浮点型。同时,我们还可以指定整型、实型、字符型变量有无“符号”(也就是是否能表示负数)。
类型转换过程中,需要注意的核心要点是:数据精度和类型的兼容性。
int A = 1;
float B = 0.013f;
int main() {
// 第一种:
B = A; // B 不是 1,是 1.0
// A 是整型int变量,B是实型float变量。转换过程精度增加。
// 第二种:
A = B; // 精度减小。
}
float A = 0.0000004f;
double B = 0.0000000000013f;
int main() {
// 第一种:
B = A; // 0.00000040000000...
// 第二种:
A = B; // 0.0
}
float A = 0.0000004f;
double B = 0.0000000000013f;
int main() {
// 第一种:
B = A; // 0.00000040000000...
// 第二种:
A = B; // 0.0
}
int main() {
char A = 'A';
unsigned char result;
result = A;
}
int main() {
int a = -20000;
unsigned int result;
result = a;
}
数组
数组:一组数据。
数组取值时,需要用“数组下标”(又称“索引”)来去出数组中对应位数的数据。
一维数组
数据类型 数组名字[数组大小] = {数组的数据}
数组大小:数组内元素的个数。
数组的数据:
数组内的数据,是由一个一个分开的单独的、独立的数据组成的。一组独立的数据构成数组。独立的数据类型应当与数组的数据类型一致。用逗号区分独立的数据。
int asd[5] = {1, 2, 3, 4, 5}; // 取“3”值: asd[2];
int asd[5] = {1,2,3,4,5};
float dsa[2] = { 2.1, 3.4 };
二维数组
数据类型 数组名字[一维数组的个数][一维数组数据大小] = {数组的数据}
int asd[4][5]; // 第九个元素 asd[a][b] a=1 b=3
// 0: 4 (5个)
// 1: 3 (4个)
// asd[1][3]
生命周期 与 定义域
int A = 0; // A 变脸的定义域/生命周期,是“全局”(GLOBAL)
int f1() {
int j = -1; // 定义域/生命周期仅限在f1函数内部。
for (int t = 2; t < 10; ++t) {
// t 变量的定义域/生命周期仅限在f1函数内部的这个for循环中。
printf("%d ", t);
}
for (1) {
printf("%d ", t); // 错误
// t 变量的定义域/生命周期仅限在f1函数内部的这个for循环中。
break;
}
printf("%d ", t); // 错误
return A;
}
int main() {
int A = 1; // 错误,已有全局变量A
int j = 1; // 可以,main函数的定义域找不到j变量。
printf("%d ", j); // 错误
return A;
}
函数
返回值类型 函数名(传入函数的变量) {
//具体的代码
}
int main() {
return 0;
}
函数的声明、实现和调用
C语言中,函数的“声明”和“实现”可以分开。
函数的调用中,必须在“被调用函数”声明后才可以调用该函数。
例如在 A 函数中调用 B 函数,则需在 A 函数之前声明 B 函数。
声明
函数“签名”。
返回值类型 函数名(定义传入函数的变量);
实现
返回值类型 函数名(定义传入函数的变量) {
...
具体的代码实现
...
}
以下两个例子等价。
int add(int a, int b) { // a, b 形参
int c = 10; //实参
return a+b;
}
int main() {
return add(10, 11);
}
int add(int a, int b);
int main() {
c = 10; //实参
return add(1, 1);
}
int add(int a, int b) {
return a+b;
}
void A() {
return;
}
int B() {
A();
return 0;
}
下面的用法是错误的:
int main() {
return add(1, 1);
}
int add(int a, int b) {
return a+b;
}
形参与实参
返回值类型 函数名(定义传入函数的变量,即形参) {
...
具体的代码实现
...
}
例如在函数中:
int add(int a, int b) {
// a 变量和 b 变量就是:形参,没有具体的值,具体的值由调用者决定
return a+b;
}
int main() {
int result = add(1, 1); // 调用 add 函数,传入 a=1,b=1
printf("%d\n", result);
return 0;
}
int add(int a, int b) {
// a 变量和 b 变量就是:形参,没有具体的值,具体的值由调用者决定
return a+b;
}
int ab(int a, int b) {
return a/b;
}
int main() {
int a = 10;
int b = 8;
int c = add(a, b); // 等价于:c=a+b
int d = ab(a, add(b, c)) + add(a, b); // 函数的嵌套调用
// 等价于:d=(a/(b+c))+(a+b)
printf("%d\n", d); // d=18
return 0;
}
运算符
数学运算符
+
加-
减*
乘法/
除法
日常生活中的数学运算中,各个符号的运算优先级同样适用于 C 语言编程。
如,乘除运算优先级高于加减,我们可以使用小括号()
来优先运算我们希望优先运算的运算。
例如:
int A = 1 + 2 * 3; // 结果为 7
int B = (1 + 2) * 3; // 结果为 9
赋值运算符
赋值运算符用“等于”号:=
。
i += a;
实际上是: i = i + a;
i -= a;
实际上是: i = i - a;
同理还有*=
、/=
等。
指针运算符
*
取值运算符,解引用运算符
&
取地址运算符,获得指针地址
比较运算符
相等 ==
(人 == 帅) 帅的话返回 TRUE 不帅则返回 FALSE
不等 !=
(人 != 可爱) TRUE FALSE
或 ||
OR 满足一个即可
与/且 &&
AND 同时满足
(人 == 帅) && (人 == 可爱):如果人即帅又可爱,则返回TRUE,反之返回FALSE
如果说只需要帅或者可爱满足一个条件即可,那么就用||
(人 == 帅) || (人 == 可爱)
!
取反值
如果一个条件为真,你用“!”,则得到的值为假。
如果说我们在只有一个人是不丑的情况下才执行操作:
(人 != 丑)
(人 == 帅)
!(人 == 丑)
// 除法
int printf_ab(int a, int b) {
// b 是被除数,不能为0.
if (b == 0)
return 0; // 被除数不能为0,传入代码有无,函数返回错误值0
printf("%f\n", a/b);
return 1;
}
int main() {
int r = printf_ab(1, 0); // r=0
if (!r) printf("Error\n");
return 0;
}
三元运算符
<条件> ? (条件满足时执行) : (不满足时执行)
例如:
int a = 0;
int b = (a == 0) ? 1 : 2;
int c = (a != 0) ? 1 : 2;
// 此时 b = 1, c = 2
int b = (a == 0) ? 1 : 2;
if (a == 0)
b = 1;
else
b = 2;
return (a == 0) ? 0 : 1; // 返回0
条件判断中的易错点
“非 0 则真”:不论是正数还是负数,只要不是0,在条件中就代表为“真”,0代表“假”。
int main() {
int _TRUE = 1;
int _FALSE = 0;
if (_TRUE) {
printf("TRUE\n");
}
if (!_TRUE) {
printf("TRUE?\n");
}
if (!_FALSE) {
printf("FALSE\n");
}
if (_FALSE) {
printf("FALSE?\n");
}
return 0;
}
预编译指令与头文件
预编译指令
所有的预编译指令,均在源码中用井号(“#
”)来开始。
如 #include
、#define
include
指令
引入头文件,即将头文件的内容包含到源码文件中。
已知,有两个文件:A.h
和A.c
A.h
的内容为:
int A = 0;
A.c
的内容为:
#include <stdio.h>
#include <A.h>
int main() {
printf("%d", A);
return 0;
}
此时,如果在A.c
中,不使用#include <A.h>
,而且要实现相同的功能。
我们可以这么写:
#include <stdio.h>
int A = 0;
int main() {
printf("%d", A);
return 0;
}
define
指令
define 指令通常用于“宏定义”。
编译器,会在编译代码前,将所有宏定义,直接替换成定义的内容。
#define HELLO "HELLO"
#define HI hi
// char *HELLO = "\"HELLO\"";
int main() {
printf("%s", HELLO); //相当于 printf("%s", "HELLO");
printf("%s", HI); //错误,相当于 printf("%s", hi);
return 0;
}
上述代码中main()
函数,实际等于:
int main() {
printf("%s", "HELLO");
return 0;
}
逻辑的力量(流程控制)
一个程序必须要有相应的逻辑和流程控制,我们才能让程序实现我们想要的功能。
判断 if (if...else if...else if...else)
if (a == b) {
// a等于b,执行
}
// 不论a是否等于b,都继续往下执行
if (a == c) {
// a等于c,执行
} else {
// a不等于c,执行
}
if (a == b) {
// a等于b执行
} else if (a == c) {
// 如果a不等于b,a等于c执行
} else {
// 都不满足,执行这里
}
循环
C 语言中有多种方式实现循环。
跳出循环需要用 break
关键字,终止循环,继续执行循环外面的代码。继续下一个项需要用 continue
关键字。
for (条件)
对于 for (;...;)
这样的 for 循环,只要条件为真(非0或“TRUE”),便会一直循环执行内部的代码,除非条件不再为真(非0或“TRUE”)或者内部有代码跳出循环。
for (;a == b;) {
// ...
if (a == c)
break;
if (a == d)
// ...
// ...
}
// ...
for (初始化代码; 条件; 操作)
对于 for (初始化代码; 条件; 操作)
这样的 for 循环,首先会执行一遍(且只会执行一遍初始化代码。
随后:
- 判断条件是否为真,若不为真,则终止循环,否则继续;
- 循环执行内部的代码;
- 执行操作;
除非条件不再为真(非0或“TRUE”)或者内部有代码跳出循环。
for (int i = 0; i < 10; i++) {
// ...
if (i == 5)
break;
// ...
// ...
}
int i = -1;
for (i = 0; i < 10; i++) {
// ...
if (i == 5)
break;
// ...
// ...
}
while 或 do...while
while () {
// ...
if (i == 5)
break;
// ...
}
int uninitialized = 1;
do {
// 至少执行一遍,是否执行第二遍,看条件。
uninitialized = init();
// 如果初始化正常,init返回0,否则返回非0的数。
} while (uninitialized)
// ...
选择 switch (switch...case...default)
switch (被判断的值) {
case <条件1>:
// 满足条件1的操作
case <条件2>:
// 满足条件2的操作
// case ...:
default:
// 所有 case 的条件,都不满足,默认执行
}
switch (被判断的值) {
case <条件1>:
// 满足条件1的操作
break;
case <条件2>:
// 满足条件2的操作
case <条件3>:
// 满足条件3的操作
break;
case <条件4>:
case <条件5>:
// 满足条件4或5的操作
break;
// case ...:
default:
// 所有 case 的条件,都不满足,默认执行
break;
}
注释
随着我们程序复杂度的增加,代码越写越多,人脑是有极限的,我们不可能会记住我们所有写的代码的功能、目的等。所以我们引入注释,来向人类解释或说明。
编译器绝对不会理会任何注释。在编译时,编译器默认忽略全部的注释。在编译器看来“不存在”“注释”。
单行注释
// 我们要写的字
// 第一行
// 第二行
// 第三行
多行注释(块注释)
/*
多行这么写
1234321
sadasd
*/
/* 一行这么写 */
评论 (0)