C语言基础(一)
文章目录
- C语言基础(一)
-
- [1. 什么是 C 语言?](#1. 什么是 C 语言?)
-
- [1.1 语言概述](#1.1 语言概述)
- [1.2 历史与标准(从 ANSI C 到 C11)](#1.2 历史与标准(从 ANSI C 到 C11))
- [1.3 编译环境与常见编译器](#1.3 编译环境与常见编译器)
- [2. 第一个 C 语言程序](#2. 第一个 C 语言程序)
-
- [2.1 代码剖析](#2.1 代码剖析)
- [2.2 运行流程:编译 → 链接 → 运行](#2.2 运行流程:编译 → 链接 → 运行)
- [3. 数据类型(Data Types)](#3. 数据类型(Data Types))
-
- [3.1 基本类型](#3.1 基本类型)
- [3.2 类型的大小:使用 `sizeof`](#3.2 类型的大小:使用
sizeof) - [3.3 类型存在的意义](#3.3 类型存在的意义)
- [4. 变量与常量](#4. 变量与常量)
-
- [4.1 变量(Variables)](#4.1 变量(Variables))
- 定义与初始化
- 变量的分类
- 核心属性:作用域与生命周期
- [4.2 常量(Constants)](#4.2 常量(Constants))
- 字面常量
- [`const` 修饰的常变量](#
const修饰的常变量) - [`#define` 定义的标识符常量](#define` 定义的标识符常量)
- [枚举常量 `enum`](#枚举常量
enum)
- [5. 字符串、转义字符与注释](#5. 字符串、转义字符与注释)
-
- [5.1 字符串与 `\0`](#5.1 字符串与
\0) - [5.2 转义字符与 `strlen` 陷阱](#5.2 转义字符与
strlen陷阱) - [5.3 注释](#5.3 注释)
- [5.1 字符串与 `\0`](#5.1 字符串与
- [6. 选择语句:`if-else`](#6. 选择语句:
if-else) -
- [6.1 基本结构](#6.1 基本结构)
- [7. 循环语句:`while`](#7. 循环语句:
while) -
- [7.1 `while` 循环](#7.1
while循环)
- [7.1 `while` 循环](#7.1
- [8. 函数(Functions)](#8. 函数(Functions))
-
- [8.1 函数的定义](#8.1 函数的定义)
- [8.2 函数的调用](#8.2 函数的调用)
- [8.3 函数的意义](#8.3 函数的意义)
- [9. 数组(Arrays)](#9. 数组(Arrays))
-
- [9.1 数组定义](#9.1 数组定义)
- [9.2 下标访问(从 0 开始)](#9.2 下标访问(从 0 开始))
- [9.3 遍历数组](#9.3 遍历数组)
- [10. 操作符(Operators)](#10. 操作符(Operators))
-
- [10.1 算术操作符](#10.1 算术操作符)
- [10.2 其他操作符(简要提及)](#10.2 其他操作符(简要提及))
- [11. 常见关键字](#11. 常见关键字)
-
- [11.1 关键字概览](#11.1 关键字概览)
- [11.2 `typedef`:类型重命名](#11.2
typedef:类型重命名) - [11.3 `static`(静态)](#11.3
static(静态))
- [12. `#define` 定义常量和宏](#define` 定义常量和宏)
-
- [12.1 标识符常量](#12.1 标识符常量)
- [12.2 宏(Macros)](#12.2 宏(Macros))
- [13. 指针(内容深度)](#13. 指针(内容深度))
-
- [13.1 内存与地址(The Foundation)](#13.1 内存与地址(The Foundation))
- [13.2 指针变量(Pointer Variables)](#13.2 指针变量(Pointer Variables))
- [13.3 解引用操作(Dereferencing)](#13.3 解引用操作(Dereferencing))
- [13.4 指针的大小(Size of Pointers)](#13.4 指针的大小(Size of Pointers))
- [14. 结构体(Struct)](#14. 结构体(Struct))
-
- [14.1 为什么需要结构体](#14.1 为什么需要结构体)
- [14.2 定义与初始化](#14.2 定义与初始化)
- [14.3 成员访问:`.` 与 `->`](#14.3 成员访问:
.与->)
1. 什么是 C 语言?
1.1 语言概述
-
底层开发
C 语言可以非常直接地操作内存、位、字节等底层细节,适合写:
- 操作系统内核
- 驱动程序
- 嵌入式程序(单片机、ARM 开发板等)
-
高效率
C 语言编译后生成的可执行文件体积小、运行速度快,开销很低。
很多高性能场景(数据库、编译器、图形库等)仍然大量使用 C/C++。
-
跨平台特性
- C 语言本身是标准化的(ANSI C / C89 / C99 / C11 等)。
- 同一份源代码,只要避免依赖特定平台的库函数和特性,就可以在不同操作系统(Windows / Linux / macOS)下通过对应编译器重新编译运行。
1.2 历史与标准(从 ANSI C 到 C11)
- 早期 C:由 Dennis Ritchie 在 1970 年代为 UNIX 系统设计。
- ANSI C(C89/C90) :
- 由 ANSI(美国国家标准协会)在 1989 年标准化,后来被 ISO 采纳为 C90。
- 奠定了 C 语言的基础语法和标准库接口。
- C99 :
- 加入了
//单行注释、long long、可变长数组、inline等特性。
- 加入了
- C11 :
- 引入多线程支持(
<threads.h>)、原子操作、对内存模型的规范等。
- 引入多线程支持(
- 实际开发中:
- 很多项目仍以 C89/C99 为主,C11 支持情况取决于编译器版本。
1.3 编译环境与常见编译器
- GCC(GNU Compiler Collection)
- 常见于 Linux / macOS。
- 命令行使用:
gcc main.c -o main。
- Clang
- 由 LLVM 项目提供,强调模块化和更友好的错误信息。
- 在 macOS 上经常默认使用。
- 命令行:
clang main.c -o main。
- MSVC(Microsoft Visual C++)
- Windows 平台上 Visual Studio 自带的编译器。
- 通常通过 IDE(图形界面)进行编译,也可以用命令行工具
cl.exe。
2. 第一个 C 语言程序
2.1 代码剖析
示例代码:
c
#include <stdio.h>
int main(void)
{
printf("Hello, C!\n");
return 0;
}
#include <stdio.h>- 预处理指令,表示"把标准输入输出库的声明包含进来"。
stdio.h中包含了printf等函数的声明。
int main(void)- 程序入口函数 ,操作系统运行程序时,会从
main函数开始执行。 - 返回类型是
int,表示程序结束时会返回一个整数状态码给操作系统。
- 程序入口函数 ,操作系统运行程序时,会从
{ ... }- 花括号内是
main函数的函数体,写的是程序实际要执行的语句。
- 花括号内是
printf("Hello, C!\n");- 调用标准库函数
printf打印字符串。 \n是换行符(转义字符)。
- 调用标准库函数
return 0;- 返回值 0 通常表示程序正常结束。
- 如果返回非 0,就常被用来表示出现错误。
2.2 运行流程:编译 → 链接 → 运行
- 预处理(Preprocessing)
- 处理
#include、#define等预处理指令,生成"预处理后的源代码"。
- 处理
- 编译(Compile)
- 将 C 源代码编译为目标文件(机器码,但是还没有连到一起)。
- 链接(Link)
- 把多个目标文件和库文件链接成一个最终可执行文件。
- 解决函数、全局变量的"符号引用"。
- 运行(Run)
- 操作系统加载可执行文件到内存,找到
main函数,开始执行。
- 操作系统加载可执行文件到内存,找到
3. 数据类型(Data Types)
3.1 基本类型
常见的基本数据类型:
- 整型
char,short,int,long,long long- 有符号与无符号:
signed/unsigned
- 浮点型
float(单精度)double(双精度)long double(扩展精度)
- 字符型
char存放一个字符(本质上是一个整数,通常 1 字节)。- 例如:
char c = 'A';
3.2 类型的大小:使用 sizeof
sizeof用来获取某种类型或某个变量占用的字节数:
c
printf("int: %zu bytes\n", sizeof(int));
printf("double: %zu bytes\n", sizeof(double));
printf("char: %zu bytes\n", sizeof(char));
- 不同平台、不同编译器,某些类型的大小可能会有差异(尤其是
int、long等)。
3.3 类型存在的意义
- 内存利用率
- 用
char存储只需要 0~127 的编码就足够了,如果用int会浪费空间。
- 用
- 数值范围
- 需要存储很大的数字时,就要用
long long或double。
- 需要存储很大的数字时,就要用
- 性能与语义
- 选择合适的类型可以提升性能,也更清晰表达变量的用途。
4. 变量与常量
关键点:变量要理解"作用域 (在哪里可见)"和"生命周期(能活多久)";常量用来表示不能被修改的值。
4.1 变量(Variables)
定义与初始化
-
定义:指定类型 + 变量名
cint a; // 定义一个 int 变量 a int b = 10; // 定义并初始化 double pi = 3.14; -
命名规则:
- 只能由字母、数字和下划线组成,不能以数字开头。
- 不能使用关键字(如
int、return、if等)。 - 尽量使用有意义的名字:
count,sum,index等。
变量的分类
- 局部变量(local variable)
- 定义在函数内部或代码块内部
{}中。 - 典型存储在栈区。
- 作用域:从定义处到所在代码块结束。
- 当程序执行离开这个代码块时,局部变量就会被销毁。
- 定义在函数内部或代码块内部
- 全局变量(global variable)
- 定义在所有函数外面。
- 存储在静态区。
- 作用域:通常为从定义位置开始到整个源文件结束,如果加上
extern可以在其他文件中使用。 - 生命周期:程序从开始运行到结束。
- 同名时的"局部优先原则"
- 如果局部变量和全局变量同名,在该局部变量的作用域内,局部变量会屏蔽全局变量。
核心属性:作用域与生命周期
- 作用域(Scope)
- 变量"在哪些代码范围内可以被访问":
- 块作用域(函数体、
if、while的{}) - 文件作用域(全局变量)
- 块作用域(函数体、
- 变量"在哪些代码范围内可以被访问":
- 生命周期(Lifecycle)
- 变量在程序执行过程中"从什么时候开始存在,到什么时候消亡":
- 局部变量:进入代码块时创建,离开时销毁。
- 全局变量 / 静态变量:程序运行期间一直存在。
- 变量在程序执行过程中"从什么时候开始存在,到什么时候消亡":
4.2 常量(Constants)
字面常量
- 写在代码里的直接数值:
- 整型字面量:
100,-5 - 浮点字面量:
3.14,0.5 - 字符字面量:
'A','\n' - 字符串字面量:
"hello"
- 整型字面量:
const 修饰的常变量
c
const int MAX_SIZE = 100;
- 表达"这个变量不允许被修改"。
- 本质仍是变量:有地址、有存储空间,有类型。
- 编译器对于修改
MAX_SIZE的行为会报错或警告。
#define 定义的标识符常量
c
#define PI 3.14159
#define MAX_SIZE 100
- 属于预处理阶段的文本替换。
- 没有类型概念,只是在编译前,把所有
PI替换为3.14159。 - 写法上常用全大写表示宏常量。
枚举常量 enum
c
enum Color {
RED, // 0
GREEN, // 1
BLUE // 2
};
- 一组逻辑相关的常量,默认从 0 开始递增。
- 也可以指定初始值:
c
enum Weekday {
MON = 1,
TUE,
WED
};
5. 字符串、转义字符与注释
5.1 字符串与 \0
- 在 C 中没有"独立的字符串类型",字符串通常用字符数组 表示,并以
'\0'结尾:
c
char str1[] = "hello"; // 实际上是 {'h','e','l','l','o','\0'}
'\0'是字符串结束标志。没有'\0'的字符数组,很多字符串函数会"一直往后读",直到在内存中遇到某个\0为止,可能导致:- 打印出奇怪的字符
- 访问越界,甚至程序崩溃
5.2 转义字符与 strlen 陷阱
常见转义字符:
\n:换行\t:水平制表符(Tab)\\:反斜杠\':单引号\":双引号\0:字符串结束符
strlen 的行为:
c
#include <string.h>
char str[] = "ab\nc";
size_t len = strlen(str); // 计算不包括 '\0'
strlen会从起始地址开始,一直数到第一次遇到'\0'为止。- 如果数组中没有
'\0',strlen会继续向后读到未知区域,结果不可预期。
5.3 注释
- C 风格注释(块注释) :
/* ... */- 可以跨多行
- 不支持嵌套
- C++ 风格注释(单行注释) :
//- 从
//起到本行末尾都是注释。 - C99 及以后标准支持
//。
- 从
6. 选择语句:if-else
6.1 基本结构
c
if (condition) {
// 条件为真时执行
} else {
// 条件为假时执行
}
- 条件
condition为"非 0"时视为 真 ,为0视为 假。 - 可以有多分支:
c
if (score >= 90) {
printf("A\n");
} else if (score >= 60) {
printf("B\n");
} else {
printf("C\n");
}
7. 循环语句:while
7.1 while 循环
c
while (condition) {
// 当 condition 为真时,重复执行这段代码
}
示例:计算 1 到 5 的和
c
int i = 1;
int sum = 0;
while (i <= 5) {
sum += i;
i++;
}
printf("sum = %d\n", sum);
- 进入循环前会先判断条件。
- 每次循环结束后,再次判断条件决定是否继续。
8. 函数(Functions)
8.1 函数的定义
一个典型函数的格式:
c
返回类型 函数名(参数列表)
{
// 函数体
}
示例:
c
int add(int a, int b)
{
return a + b;
}
8.2 函数的调用
c
int result = add(3, 5); // 调用 add 函数
printf("%d\n", result); // 输出 8
- 传参:把实际的值(实参)传递给函数定义中的形参。
- 返回值 :通过
return把结果返回给调用者。
8.3 函数的意义
- 模块化:把一个大问题拆解成多个小函数,便于理解与维护。
- 代码复用:减少重复代码,提高可读性。
9. 数组(Arrays)
9.1 数组定义
c
int arr[5]; // 定义一个包含 5 个 int 元素的数组
int arr2[3] = {1,2,3};
- 数组是"一组相同类型元素的集合",在内存中是连续存放的。
9.2 下标访问(从 0 开始)
- 第一个元素下标为
0,最后一个元素下标为n-1:
c
arr[0] = 10;
arr[1] = 20;
- 访问越界(例如
arr[5],当数组长度为 5 时)会导致未定义行为。
9.3 遍历数组
通常结合循环:
c
int arr[5] = {1, 2, 3, 4, 5};
int i = 0;
while (i < 5) {
printf("%d ", arr[i]);
i++;
}
10. 操作符(Operators)
10.1 算术操作符
+:加-:减*:乘/:除%:取模(余数,只能用于整数)
示例:
c
int a = 7, b = 3;
printf("%d\n", a / b); // 2(整数除法,结果为整数)
printf("%d\n", a % b); // 1(余数)
10.2 其他操作符(简要提及)
- 关系操作符:
==,!=,<,>,<=,>= - 逻辑操作符:
&&,||,! - 自增自减:
++,-- - 位操作符:
&,|,^,~,<<,>> - 条件操作符:
?:等
(这些可以在后续进阶时再深入)
11. 常见关键字
11.1 关键字概览
C 语言保留了一些有特殊含义的单词,如:
int,char,double,voidif,else,while,for,switch,case,defaultreturn,break,continuestruct,union,enum,typedef,static,extern等
这些单词不能作为变量名或函数名使用。
11.2 typedef:类型重命名
c
typedef unsigned int uint;
typedef struct Student {
char name[20];
int age;
} Student;
- 之后可以用
uint代替unsigned int,用Student直接表示结构体类型。
11.3 static(静态)
- 修饰局部变量:
c
void func(void)
{
static int count = 0; // 生命周期为整段程序运行期
count++;
printf("%d\n", count);
}
static局部变量:- 仍然只在函数内部可见(作用域不变)。
- 但生命周期从程序开始到结束(不会随着函数退出而销毁)。
- 修饰全局变量 / 函数 :
- 作用:改变"链接属性",限制其只在当前源文件内可见(内部链接)。
- 可以避免全局名字在多文件工程中冲突。
12. #define 定义常量和宏
12.1 标识符常量
c
#define MAX 100
#define PI 3.14159
- 纯粹的文本替换,没有类型检查。
- 用来统一管理一些不易变化的"魔法数字"。
12.2 宏(Macros)
- 带参数的宏本质上是一种文本模板:
c
#define SQUARE(x) ((x) * (x))
- 使用时:
int r = SQUARE(5);→ 预处理阶段变成((5) * (5))。 - 要注意用括号包裹参数和整体表达式,避免优先级坑。
13. 指针(内容深度)
从"内存与地址"理解指针:
地址 是门牌号,指针变量 是专门存门牌号的变量,解引用是拿着门牌号去房间里找东西。
13.1 内存与地址(The Foundation)
- 内存单元 :
- 内存被划分为一个个字节(Byte),通常 1 字节 = 8 bit。
- 地址(Address) :
- 每个字节有一个唯一的编号,就像"门牌号"。
- 指针本质上就是存这个编号的变量。
- 取地址操作符
&
c
int a = 10;
int *p = &a; // &a 表示"a 在内存中的地址"
13.2 指针变量(Pointer Variables)
- 定义形式:
类型 *指针名
c
int *p; // p 是一个指向 int 的指针
double *q; // q 是一个指向 double 的指针
- 类型匹配的意义 :
int *表示"这个指针指向的是一个 int 类型的对象"。- 编译器需要通过"指向的类型"来决定:
- 解引用时读取多少字节
- 指针运算(
p + 1跳过多少字节)
所以,int 的地址就应该存到 int * 里,double 的地址存到 double * 里。
13.3 解引用操作(Dereferencing)
- 操作符:
*
c
int a = 10;
int *p = &a;
printf("%d\n", *p); // 通过指针访问 a 的值
*p = 20; // 通过指针修改 a 的值
- 含义:
p是"存放地址的变量"。*p是"沿着地址去找到那个变量本身"。
13.4 指针的大小(Size of Pointers)
- 在同一平台上,所有指针的大小通常是相同的:
- 32 位系统:指针通常是 4 字节。
- 64 位系统:指针通常是 8 字节。
c
printf("%zu\n", sizeof(int *));
printf("%zu\n", sizeof(double *));
printf("%zu\n", sizeof(char *));
- 原因 :
- 指针大小由 CPU 的地址总线宽度决定,即"可以表达多大的地址空间"。
- 与"指向的数据类型"无关。
14. 结构体(Struct)
14.1 为什么需要结构体
- 现实中的对象往往有多个属性,比如"学生":
- 姓名(字符串)
- 年龄(整数)
- 分数(浮点数)
- 如果用多个分散的变量:
name,age,score,很难整体传递和管理。 - 结构体就是把一组相关的不同类型的数据"打包"成一个整体。
14.2 定义与初始化
c
struct Student {
char name[20];
int age;
double score;
};
struct Student s1 = {"Alice", 18, 95.5};
struct是关键字,Student是结构体标签名。- 定义变量时可以直接初始化。
14.3 成员访问:. 与 ->
- 结构体变量通过
.访问成员:
c
struct Student s;
s.age = 20;
- 结构体指针通过
->访问成员:
c
struct Student *ps = &s;
ps->age = 21; // 等价于 (*ps).age = 21;