C语言简单复习
最近打算开始学习STM32,发现关于C语言的一些知识已经忘得差不多了
这篇文章主要是对C语言的一些基础知识进行简单的复习
指针
*
用于声明指针变量或者获取指针变量所指向的变量。
&
用于获取变量的地址。
指针是一个变量,其值为另一个变量的地址。
一级指针
一级指针是最基本的指针类型,它存储的是变量的内存地址。在内存中,一级指针占用的空间大小固定,通常为4字节或8字节(取决于操作系统的位数,32位系统为4字节,64位系统为8字节)。
1 | int a = 10; //声明一个int类型的变量 |
- 动态内存分配
使用malloc、calloc、realloc和free函数进行动态内存分配和释放。
1
2
3
4
5
6
7int *p = (int *)malloc(sizeof(int) * 10); // 动态分配10个int大小的内存空间
if (p != NULL) {
for (int i = 0; i < 10; i++) {
p[i] = i; // 给动态分配的内存空间赋值
}
free(p); // 释放动态分配的内存空间
} - 函数参数传递
使用指针作为函数参数,可以实现函数内部修改外部变量的值。
1
2
3
4
5
6
7
8
9
10
11
12void swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
int main() {
int x = 10, y = 20;
swap(&x, &y);
printf("x = %d, y = %d\n", x, y); // 输出 x = 20, y = 10
return 0;
}
二级指针
二级指针是指向指针的指针,它存储的是一级指针的地址。在内存中,二级指针占用的空间大小固定,通常为4字节或8字节(取决于操作系统的位数,32位系统为4字节,64位系统为8字节)。
1 | int a = 10; //声明一个int类型的变量 |
- 二级指针的动态内存分配
1
2
3
4
5
6
7
8
9
10
11int **p = (int **)malloc(sizeof(int *) * 10); // 动态分配10个int*大小的内存空间
if (p != NULL) {
for (int i = 0; i < 10; i++) {
p[i] = (int *)malloc(sizeof(int) * 10); // 动态分配10个int大小的内存空间
}
for (int i = 0; i < 10; i++) {
free(p[i]); // 释放动态分配的内存空间
}
free(p); // 释放动态分配的内存空间
} - 二级指针的函数参数传递
1
2
3
4
5
6
7
8
9
10
11
12
13
14void swap(int **a, int **b) {
int *temp = *a;
*a = *b;
*b = temp;
}
int main() {
int x = 10, y = 20;
int *p = &x, *q = &y;
swap(&p, &q);
printf("x = %d, y = %d\n", *p, *q); // 输出 x = 20, y = 10
return 0;
}
指针数组
指针数组是一个数组,其元素是指针。在内存中,指针数组占用的空间大小取决于数组的大小和指针的大小。
1 | int a = 10, b = 20, c = 30; //声明三个int类型的变量 |
数组指针
数组指针是一个指针,其指向一个数组。在内存中,数组指针占用的空间大小固定,通常为4字节或8字节(32位系统为4字节,64位系统为8字节)。
1 | int a[3] = {10, 20, 30}; //声明一个int类型的数组 |
指针函数
指针函数是一个函数,其返回值是指针。
1 | int a = 10; //声明一个int类型的变量 |
函数指针
函数指针是一个指针,其指向一个函数。
1 | int add(int a, int b) { |
数组
数组是一种数据结构,它由相同类型的元素组成。在内存中,数组占用的空间大小取决于数组的大小和元素的大小。
一维数组
一维数组是将相同数据类型的元素按顺序存储在一起的数据结构,可以用一个下标来访问每个元素。
1 | int a[3] = {10, 20, 30}; //声明一个int类型的数组 |
二维数组
二维数组是将相同数据类型的元素按行和列组织在一起的数据结构,可以用两个下标来访问每个元素。
1 | int a[3][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}; //声明一个int类型的二维数组 |
数组排序
数组排序是将数组中的元素按照一定的规则进行排列。常见的排序算法有
冒泡排序: 两两比较相邻记录的关键字,如果反序则交换,直到没有反序的记录为止。
选择排序: 每次从待排序的记录中选出关键字最小的记录,放在已排序记录的末尾。
插入排序: 将待排序的记录插入到已排序的记录中,直到所有记录都插入为止。
快速排序: 通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小,然后分别对这两部分记录继续进行排序,以达到整个序列有序。
归并排序: 将两个或两个以上的有序表合并成一个新的有序表。
堆排序: 利用堆这种数据结构所设计的一种排序算法。
计数排序: 是统计每个输入元素的个数,然后依次输出。
桶排序: 将数组分到有限数量的桶里,然后对每个桶再进行排序。
基数排序: 将整数按位数切割成不同的数字,然后按每个位数分别比较。
字符串
C语言中,字符串实际上是使用空字符'\0'结尾的一维字符数组。因此,'\0'是用于标记字符串的结束。空字符(Null character)又称结束符,缩写NUL,是一个数值为0 的控制字符,'\0' 是转义字符,意思是告诉编译器,这不是字符0,而是空字符。
与C++不同,C语言中没有字符串类型,字符串是以字符数组的形式存储的
字符串的定义和初始化
1 | char str1[] = "Hello"; // 字符串常量 |
字符串操作
C语言提供了许多用于操作字符串的函数,这些函数都包含在头文件string.h
中。
- strcpy(dst, src)
: 将 src
字符串复制到 dst
字符串中。
- strcat(dst, src)
: 将 src
字符串连接到 dst
字符串的尾部。
- strlen(str)
: 返回字符串的长度。
- strcmp(str1, str2)
: 比较两个字符串大小, if < return -1, if > return 1, if = return 0
。
- strchr(str, c)
: 在字符串中查找字符 c
,返回一个指针,指向 c
在 str
中第一次出现的位置。
- strstr(str1, str2)
: 在字符串中查找子字符串 str2
,返回一个指针,指向 str2
在 str1
中第一次出现的位置。
1 | char str1[10] = "Hello"; |
其他字符串操作函数还有: - strncpy(dst, src, n)
: 将 src
字符串的前 n
个字符复制到 dst
字符串中。
- strncat(dst, src, n)
: 将 src
字符串的前 n
个字符连接到 dst
字符串的尾部。
- strncmp(str1, str2, n)
: 比较两个字符串的前 n
个字符大小。
- strrchr(str, c)
: 在字符串中查找字符 c
,返回一个指针,指向 c
在 str
中最后一次出现的位置。
- atoi(str)
: 将字符串转换为整数。
- atof(str)
: 将字符串转换为浮点数。
- atol(str)
: 将字符串转换为长整数。
- itoa(n, str, base)
: 将整数 n
转换为字符串,base
为进制。
- gcvt(value, ndigit, buf)
: 将浮点数 value
转换为字符串,ndigit
为小数点后的位数。
- strtok(str, delim)
: 分解字符串为一组字符串,delim
为分隔符。
- strtoupper(str)
: 将字符串中的小写字母转换为大写字母。
- tolower(str)
: 将字符串中的大写字母转换为小写字母。
变量
变量概述
变量是C语言中用来存储数据的基本单位。它就像一个容器,可以存放各种类型的数据,例如数字、字符、字符串等。
变量的四要素:
- 变量名:变量的名称,用来标识变量。
- 数据类型:变量的类型,用来定义变量的数据类型。
- 存储地址:变量的存储地址,用来标识变量在内存中的位置。
- 数据值:变量的值,用来存储变量的数据值。
变量类型
C语言提供了多种数据类型,每种类型都有其特定的存储大小和取值范围。常见的数据类型包括:
基本数据类型:整型、浮点型、字符型。
- 整数类型:
int
(4字节)、short
(2字节)、long
(4字节)、long long
(8字节)。
- 浮点类型:
float
(4字节)、double
(8字节)、long double
(8字节)。
- 字符类型:
char
(1字节)。
- 空类型:
void
。
- 整数类型:
构造数据类型:
- 数组类型:
int a[10]
。
- 指针类型:
int *p
。
- 结构体类型:
struct {int a; float b;}
。
- 枚举类型:
enum {RED, GREEN, BLUE}
。
- 数组类型:
变量作用域
变量的作用域决定了变量在程序中可被访问的范围。C语言中主要有三种作用域:
- 局部变量:在函数内部定义的变量,只能在函数内部访问。函数结束后,局部变量会被销毁。
- 全局变量:在函数外部定义的变量,可以在整个程序中访问。
- 静态变量:具有静态存储类型的变量,在函数结束后仍然存在。
例如: 1
2
3
4
5
6
7
8
9
10
11
12int a = 10; // 全局变量
void func() {
int b = 20; // 局部变量
static int c = 30; // 静态变量
c += 10;
}
// 第一次调用func(),b = 20, c = 40
// 第二次调用func(),b = 20, c = 50
// 第三次调用func(),b = 20, c = 60
形参和实参
在函数调用时,形参是函数定义中声明的参数,实参是调用函数时传递给形参的实际值。
- 形参:用于接收实参的值,类型和个数必须与实参一致。
- 实参:实际传递给函数的参数,可以是变量、常量或表达式。
形参和实参的关系:
- 形参是形式上的参数,实参是实际的参数。
- 形参在函数内部被赋值,实参在函数外部被赋值。
- 函数调用时,实参会按顺序赋值给形参。
关键字
C语言中的关键字是一些具有特殊含义的单词,它们用于定义变量、函数、结构体等。C语言中的关键字有32个,可以分为以下几类:
数据类型关键字:
int
、float
、char
、double
、void
、short
、long
、signed
、unsigned
。
控制语句关键字:
if
、else
、switch
、case
、default
、while
、do
、for
、break
、continue
、return
。
存储类关键字:
auto
、register
、static
、extern
、const
、volatile
。
其他关键字:
struct
、union
、enum
、typedef
、sizeof
、goto
、asm
。
对于数据类型关键字和控制语句关键字就不多说了,struct
、union
、enum
在结构中会详细介绍。auto
: 用于声明自动变量,存储类为auto的变量在函数内部定义,函数结束后自动销毁。
register
: 用于声明寄存器变量,存储类为register的变量存储在CPU的寄存器中,访问速度快。
static
: 用于声明静态变量,存储类为static的变量在程序运行期间一直存在。
extern
: 用于声明外部变量,存储类为extern的变量在其他文件中定义。
const
: 用于声明常量,常量的值不能被修改。
volatile
: 用于声明易失变量,易失变量的值可能会被意外修改。typedef
: 用于定义类型别名,可以为已有的数据类型定义一个新的名字。
sizeof
: 用于获取数据类型或变量的大小,返回值为字节数。
goto
: 用于无条件跳转到指定的标签,不推荐使用。
asm
: 用于嵌入汇编代码,可以在C语言中嵌入汇编代码。
结构
C语言提供了三种重要的结构化数据类型:结构体、联合体和枚举类型,它们可以用于表示更复杂的数据结构。
结构体
结构体是一种将不同类型的数据组合在一起的类型。它可以用来表示具有多个属性的复杂数据对象,例如学生、日期、书籍等。
结构体定义
结构体的定义使用关键字struct
,其格式为: 1
2
3
4
5struct student {
char name[20]; // 学生姓名
int age; // 学生年龄
int score; // 学生成绩
};
结构体使用
- 声名结构体变量
1 | struct student stu; // 声明一个结构体变量stu |
- 初始化结构体变量
1 | struct student stu = {"Tom", 18, 90}; // 初始化结构体变量stu |
- 访问结构体成员
1 | printf("%s\n", stu.name); // 输出学生姓名 |
结构体特点
- 结构体成员可以是不同类型的数据。
- 结构体变量的大小等于所有成员变量的大小之和。
- 结构体成员可以是其他结构体类型。
- 结构体可以嵌套定义。
例如:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16// 以下代码定义了一个日期结构体和一个学生结构体,
// 将日期结构体作为学生结构体的成员,可以用来表示学生的生日。
struct date {
int year;
int month;
int day;
};
struct student {
char name[20];
int age;
struct date birthday;
};
struct student stu = {"Tom", 18, {2000, 1, 1}};
联合体
联合体与结构体类似,也是将不同类型的数据组合在一起的类型。但联合体的所有成员共享同一块内存空间,每次只能存储一个成员的值。
联合体定义
联合体的定义使用关键字union
,其格式为: 1
2
3
4
5
6union data {
int num; // 整型数据
float fnum; // 浮点型数据
char str[10]; // 字符串
};
联合体使用
1 | union data data1; // 声明一个联合体变量data1 |
联合体特点
- 联合体的大小等于所有成员变量中最大的大小。
- 联合体的成员共享同一块内存空间,每次只能存储一个成员的值。
- 联合体常用于节省内存空间。
枚举类型
枚举类型是一种用于定义一组常量的类型。它可以提高代码的可读性和可维护性。
枚举类型定义
枚举类型的定义使用关键字enum
,其格式为: 1
2
3
4
5enum color {
RED, // 红色
GREEN, // 绿色
BLUE // 蓝色
};
枚举类型使用
1 | enum color c = RED; // 声明一个枚举变量c,并初始化为RED |
枚举类型特点
- 枚举类型可以定义一组相关联的常量。
- 枚举类型的值默认从0开始,依次递增。比如上面的枚举类型RED的值为0,GREEN的值为1,BLUE的值为2。
- 枚举类型可以提高代码的可读性和可维护性。
- 枚举类型可以用于节省内存空间。
运算符
C语言提供了多种运算符,用于对变量和常量进行运算。常见的运算符包括:
- 算术运算符:
+
、-
、*
、/
、%
。
- 关系运算符:
==
、!=
、>
、<
、>=
、<=
。
- 逻辑运算符:
&&
、||
、!
。
- 位运算符:
&
、|
、^
、~
、<<
、>>
。
- 赋值运算符:
=
、+=
、-=
、*=
、/=
、%=
、&=
、|=
、^=
、<<=
、>>=
。
- 条件运算符:
? :
。
- 逗号运算符:
,
。
- 自增自减运算符:
++
、--
。
- 指针运算符:
&
、*
。
- sizeof运算符:
sizeof
。
- 类型转换运算符:
(type)
。
位运算符
位运算符是对二进制数进行操作的运算符,包括按位与、按位或、按位异或、按位取反、左移和右移。
- 按位与:
&
,两个位都为1时,结果为1,否则为0。
- 按位或:
|
,两个位都为0时,结果为0,否则为1。
- 按位异或:
^
,两个位相同时,结果为0,否则为1。
- 按位取反:
~
,对每个位取反。
- 左移:
<<
,将二进制数向左移动指定的位数。
- 右移:
>>
,将二进制数向右移动指定的位数。
1 | int a = 5; // 二进制数为0101 |
类型转换运算符
类型转换运算符用于将一个数据类型转换为另一个数据类型。C语言提供了两种类型转换运算符:强制类型转换和隐式类型转换。
- 强制类型转换:使用
(type)
将一个数据类型转换为另一个数据类型。
1 | int a = 10; |
- 隐式类型转换:在表达式中,不同类型的数据会自动转换为相同的类型。
1 | int a = 10; |
函数
常用的库函数有: ## 输入输出函数
printf
:格式化输出函数,用于将数据输出到控制台。printf("格式化字符串", 参数列表);
scanf
:格式化输入函数,用于从控制台读取数据。scanf("格式化字符串", 参数列表);
getchar
:从控制台读取一个字符。char c = getchar();
putchar
:将一个字符输出到控制台。putchar('c');
gets
:从控制台读取一个字符串。char str[10]; gets(str);
puts
:将一个字符串输出到控制台。puts("Hello");
数学函数
abs
:求整数的绝对值。int a = abs(-10);
fabs
:求浮点数的绝对值。float b = fabs(-3.14);
sqrt
:求平方根。float c = sqrt(16);
cbrt
:求立方根。float d = cbrt(8);
pow
:求幂。float e = pow(2, 3);
exp
:求指数。float f = exp(1);
log
:求自然对数。float g = log(2.718);
log10
:求常用对数。float h = log10(100);
sin
:求正弦。float i = sin(3.14);
cos
:求余弦。float j = cos(3.14);
tan
:求正切。float k = tan(3.14);
字符串函数
strlen
:求字符串的长度。
strcpy
:复制字符串。
strcat
:连接字符串。
strcmp
:比较字符串。
strchr
:查找字符。
strstr
:查找子字符串。
内存函数
malloc
:分配内存空间。
calloc
:分配并初始化内存空间。
realloc
:重新分配内存空间。
free
:释放内存空间。
其他函数
rand
:生成随机数,返回一个0到RAND_MAX
之间的随机数,也可以使用rand() % n
生成[1, n)
之间的随机数。
srand
:设置随机数种子,随机数种子相同,生成的随机数也相同。
time
:获取当前时间。
clock
:获取程序运行时间。
1 |
|
内存
C语言中的内存分为栈内存、堆内存和全局内存。
栈内存
栈内存是程序运行时由编译器自动分配和释放的一块内存区域。它主要用于存储函数的局部变量、函数参数和返回值等。
栈内存的特点:
- 由编译器自动管理,程序员无需手动操作。
- 先进后出,即后压入的栈帧先弹出。
- 空间大小有限,由编译器决定。
示例: 1
2
3
4
5
6
7
8
9
10
11void func(int a, int b) {
int c = a + b;
// ...
}
int main() {
int x = 1;
int y = 2;
func(x, y);
// ...
}func()
函数的局部变量 a
、b
和 c
存储在栈内存中。当 func()
函数结束时,栈内存中的这些变量会被自动释放。
堆内存
堆内存是程序运行时由程序员显式分配和释放的一块内存区域。它主要用于存储动态分配的内存空间,例如使用malloc()
和 calloc()
函数分配的内存。
堆内存的特点:
- 由程序员手动管理,需要显式分配和释放。
- 先分配后释放,程序员需要手动释放堆内存,否则会导致内存泄漏。
- 空间大小可变,由程序员决定。
示例: 1
2
3
4
5int main() {
int *p = malloc(10 * sizeof(int));
// ...
free(p);
}p
指针指向了一块由 malloc()
函数分配的堆内存空间。当不再使用 p
指针指向的内存空间时,需要使用 free()
函数释放它,以避免内存泄漏。
全局内存
全局内存是程序运行时由编译器分配,在程序结束时释放的一块内存区域。它主要用于存储全局变量和静态变量等。
全局内存的特点:
- 由编译器自动管理,程序员无需手动操作。
- 程序运行期间一直存在。
示例: 1
2
3
4
5int global_var = 1;
int main() {
// ...
}global_var
变量存储在全局内存中,从程序开始到结束,一直存在。
内存分配和释放
C语言提供了多种内存分配和释放函数,包括malloc
、calloc
、realloc
和free
。
malloc
:分配指定大小的内存空间,并返回指向该内存空间的指针。
calloc
:分配指定大小的内存空间,并将其初始化为0,返回一个指向分配内存的指针。
realloc
:用于重新分配内存空间,返回一个指向分配内存的指针。
free
:用于释放动态分配的内存空间。
1 | int *p = (int *)malloc(sizeof(int) * 10); // 分配10个int大小的内存空间 |