C语言基础
1. 关键词¶
关键字 | 应用场景 |
---|---|
static | ① 在函数内部:该变量在多次函数调用之间保持其值不变 。 ② 使得变量或函数的作用域局限于定义它的文件或函数内部,外部无法访问 。 |
extern | 告诉编译器变量或函数在别的地方定义,可以跨文件寻找并使用变量或函数 。 |
volatile | 所修饰的对象不能被编译器优化 。 |
register | 建议编译器将变量存储在寄存器中以提高访问速度 。 |
typedef | 给数据类型取别名 。 |
const | 修饰只读变量,变量一旦赋初值就不能被修改 。 |
2. 全局变量与局部变量¶
变量类型 | 作用域 | 生命周期 | 存储位置 | 默认值 |
---|---|---|---|---|
局部变量 | 函数或代码块内部 | 函数或代码块执行期间 | 栈(Stack) | 未定义(随机值) |
全局变量 | 整个程序 | 程序运行期间 | 静态区(Data Segment) | 0 |
静态局部变量 | 函数内部 | 程序运行期间 | 静态区(Data Segment) | 0 |
静态全局变量 | 当前文件 | 程序运行期间 | 静态区(Data Segment) | 0 |
3. 预处理器¶
3.1 指令汇总¶
指令 | 描述 |
---|---|
#define | 定义宏 |
#include | 包含一个源代码文件 |
#undef | 取消已定义的宏 |
#ifdef | 如果宏已经定义,则返回真 |
#ifndef | 如果宏没有定义,则返回真 |
#if | 如果给定条件为真,则编译下面代码 |
#else | #if 的代替方案 |
#elif | 如果前面的 #if 给定条件不为真,当前条件为真,则编译下面代码 |
#endif | 结束一个 #if……#else 条件编译块 |
#error | 当遇到标准错误时,输出错误消息 |
#pragma | 使用标准化方法,向编译器发布特殊的命令到编译器中 |
3.2 宏定义¶
// 无参宏
#define DEAD_ZONE_ARR 1850
//有参宏
#define M1A(duty) __HAL_TIM_SetCompare(&htim8, TIM_CHANNEL_1, duty)
#include <stdio.h>
#define VALUE 100
int main() {
printf("VALUE = %d\n", VALUE);
#undef VALUE
// printf("VALUE = %d\n", VALUE); // 此行会报错,因为 VALUE 已被取消定义
return 0;
}
3.3 条件编译¶
#include <stdio.h>
#define DEBUG // 定义调试模式
int main() {
#ifdef DEBUG
printf("Debug mode is enabled.\n");
#else
printf("Release mode.\n");
#endif
return 0;
}
应用场景
4. 指针¶
4.1 指针基础¶
//定义指针
int* p1;
//给指针分配地址
int c;
c = 5;
p1 = &c;
//获取指针所指向的值与地址
printf("p1 address: 0x%p, p1 value: %d", p1, *p1);
/*输出*/
p1 address: 0x0061FF18, p1 value: 5
经过上述代码流程,我们将指针p1
指向变量c
的地址,所以当我们改变变量c
的值,p1指针指向的值也会随c
改变。
//改变指针所指向的值
c = 8;
printf("p1 address: 0x%p, p1 value: %d\n", p1, *p1);
/*输出*/
p1 address: 0x0061FF18, p1 value: 5
p1 address: 0x0061FF18, p1 value: 8
可以看到指针指向的地址没有变,一致都是变量c
的地址,而变量c
的值改变,随之p1指针指向的值也改变。同理,我们可以通过修改*p1
实现修改变量c
的值,完整代码如下:
#include <stdio.h>
int main()
{
//定义指针
int* p1;
//给指针分配地址
int c;
c = 5;
p1 = &c;
//获取指针所指向的值与地址
printf("p1 address: 0x%p, p1 value: %d, c value: %d\n", p1, *p1, c);
//改变指针所指向的值
c = 8;
printf("p1 address: 0x%p, p1 value: %d, c value: %d\n", p1, *p1, c);
//通过指针修改变量的值
*p1 = 10;
printf("p1 address: 0x%p, p1 value: %d, c value: %d\n", p1, *p1, c);
return 0;
}
/*输出*/
p1 address: 0x0061FF18, p1 value: 5, c value: 5
p1 address: 0x0061FF18, p1 value: 8, c value: 8
p1 address: 0x0061FF18, p1 value: 10, c value: 10
4.2 指针与数组¶
在数组中,我们亦经常会用到取值和取地址。有代码不难看出:&a[i]
与a + i
结果一样,都是取地址,而a[i]
则是取值。
#include <stdio.h>
int main()
{
int a[4] = {1, 2, 3, 4};
for (int i = 0 ; i < 4 ; i++)
{
printf("a[%d]: %d\n", i, a[i]);
printf("a[%d]: %d\n", i, a + i);
printf("a[%d]: %d\n", i, &a[i]);
}
return 0;
}
/*输出*/
a[0]: 1
a[0]: 6422284
a[0]: 6422284
a[1]: 2
a[1]: 6422288
a[1]: 6422288
a[2]: 3
a[2]: 6422292
a[2]: 6422292
a[3]: 4
a[3]: 6422296
a[3]: 6422296
int类型数据为 4 字节,char为 1 字节,而指针可以通过p +/- 1
改变指向的地址,而其横跨的地址长度与指针的数据类型有关,比如int类型,指针加 1 (p+1
)地址就增加 4 个字节;可以通过这个方法用指针加减方法读取数组数据和地址。
#include <stdio.h>
int main()
{
int a[4] = {1, 2, 3, 4};
char b[4] = {'a', 'b', 'c', 'd'};
int *p1;
char *p2;
p1 = a + 2;
p2 = b + 1;
printf("p1 address1: %p, p1 address2: %p\n", p1+1, p1);
printf("p2 address2: %p, p2 address2: %p\n", p2+1, p2);
return 0;
}
/*输出*/
p1 address1: 0061FF14, p1 address2: 0061FF10
p2 address2: 0061FF06, p2 address2: 0061FF05
4.3 指针与函数¶
① 用指针接受处理函数参数传入的地址,可以实现交换函数外两个变量的值。
#include <stdio.h>
void swap(int* a, int* b);
int main()
{
int a=1, b=9;
printf("a: %d, b: %d\n", a, b);
swap(&a, &b);
printf("a: %d, b: %d\n", a, b);
return 0;
}
void swap(int* a, int* b)
{
int temp;
temp = *a;
*a = *b;
*b = temp;
}
/*输出*/
a: 1, b: 9
a: 9, b: 1
② 将指针作为参数传入函数。
#include <stdio.h>
void swap(int* a, int* b);
void my_add(int* p);
int main()
{
int a=1, b=9;
printf("a: %d, b: %d\n", a, b);
swap(&a, &b);
printf("a: %d, b: %d\n", a, b);
//此时b已经与a交换,b=1
int *p;
p = &b;
//指针传入函数my_add()
my_add(p);
printf("b: %d, *p: %d\n", b, *p);
return 0;
}
void swap(int* a, int* b)
{
int temp;
temp = *a;
*a = *b;
*b = temp;
}
void my_add(int* p)
{
(*p)++;
}
/*输出*/
a: 1, b: 9
a: 9, b: 1
b: 2, *p: 2
4.4 动态内存分配¶
C语言标准库函数:malloc()
,calloc()
,free()
和realloc()
在程序中可以动态分配内存。
malloc()
函数用于在 堆区 动态分配一块指定大小的内存,内存内容是未初始化的,因此,分配的内存块中的数据是未知的。 并且,它返回一个void指针,可以将其转换为任何形式的指针。
语法示例:
浮点数的大小是4字节,所以例子中创建了 400 字节的内存,其中ptr
指针指向分配的内存的第一个字节。 如果无法分配内存,则表达式将产生NULL指针。
calloc()
也是用于在堆上分配内存, 但会将分配的内存初始化为零。
语法示例:
free()
用于释放calloc()
或malloc()
动态分配的内存空间。
语法示例:
此例子将释放之前由calloc()
或malloc()
动态分配的内存空间,ptr
为它们返回的指针。
realloc()
用于动态分配的内存不足或超过所需,可以使用realloc()函数更改以前分配的内存的大小。
语法示例:
此例子ptr被重新分配为新的大小 x 。
示例 1 :
#include <stdio.h>
#include <stdlib.h>
int main()
{
int n, i, *ptr, sum = 0;
printf("输入元素数量: ");
scanf("%d", &n);
ptr = (int*) malloc(n * sizeof(int));
// 如果无法分配内存
if(ptr == NULL)
{
printf("错误! 内存未分配。");
exit(0);
}
printf("输入元素: ");
for(i = 0; i < n; ++i)
{
scanf("%d", ptr + i);
sum += *(ptr + i);
}
printf("Sum = %d", sum);
//释放内存
free(ptr);
return 0;
}
示例 2 :
#include <stdio.h>
#include <stdlib.h>
int main()
{
int *ptr, i, n1, n2;
printf("输入大小: ");
scanf("%d", &n1);
ptr = (int*)malloc(n1 * sizeof(int));
printf("先前分配的内存地址: ");
for (i = 0; i < n1; ++i)
printf("%u\n", ptr + i);
printf("\n输入新的大小: ");
scanf("%d", &n2);
//重新分配内存
ptr = realloc(ptr, n2 * sizeof(int));
printf("新分配的内存地址: ");
for (i = 0; i < n2; ++i)
printf("%u\n", ptr + i);
free(ptr);
return 0;
}
5. 结构体¶
结构体在嵌入式中非常常见,且我们每天写程序都在用。
5.1 结构体基础¶
StructName
是结构体的名称,type
是成员的数据类型member1
, member2
, member3
等是结构体的成员,variable
是结构变量。
结构体简单示例:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct Student
{
/* data */
char name[20];
int age;
float grade;
};
int main()
{
//定义一个结构体变量
struct Student student1;
//成员赋值
strcpy(student1.name, "C_language");
student1.age = 18;
student1.grade = 98.1;
// 访问结构体成员
printf("Student Name: %s\n", student1.name);
printf("Student Age: %d\n", student1.age);
printf("Student Grade: %.2f\n", student1.grade);
return 0;
}
/*输出*/
Student Name: C_language
Student Age: 18
Student Grade: 98.10
5.2 结构体指针¶
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct Student
{
/* data */
char name[20];
int age;
float grade;
};
int main()
{
//定义一个结构体变量
struct Student student1;
//成员赋值
strcpy(student1.name, "C_language");
student1.age = 18;
student1.grade = 98.1;
// 访问结构体成员
printf("Student Name: %s\n", student1.name);
printf("Student Age: %d\n", student1.age);
printf("Student Grade: %.2f\n", student1.grade);
//定义一个结构体指针
struct Student *student_ptr;
student_ptr = &student1;
printf("Student Name: %s\n", student_ptr->name);
printf("Student Age: %d\n", student_ptr->age);
printf("Student Grade: %.2f\n", student_ptr->grade);
return 0;
}
/*输出*/
Student Name: C_language
Student Age: 18
Student Grade: 98.10
Student Name: C_language
Student Age: 18
Student Grade: 98.10
5.3 结构体做函数参数¶
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct Student
{
/* data */
char name[20];
int age;
float grade;
};
void printf_struct(struct Student student)
{
printf("Student Name: %s\n", student.name);
printf("Student Age: %d\n", student.age);
printf("Student Grade: %.2f\n", student.grade);
}
int main()
{
//定义一个结构体变量
struct Student student1;
//成员赋值
strcpy(student1.name, "C_language");
student1.age = 18;
student1.grade = 98.1;
printf_struct(student1);
return 0;
}
/*输出*/
Student Name: C_language
Student Age: 18
Student Grade: 98.10
传递结构体指针
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct Student
{
/* data */
char name[20];
int age;
float grade;
};
void printf_struct(struct Student *student_ptr)
{
printf("Student Name: %s\n", student_ptr->name);
printf("Student Age: %d\n", student_ptr->age);
printf("Student Grade: %.2f\n", student_ptr->grade);
}
int main()
{
//定义一个结构体变量
struct Student student1;
//成员赋值
strcpy(student1.name, "C_language");
student1.age = 18;
student1.grade = 98.1;
printf_struct(&student1);
return 0;
}
5.4 Typedef使用¶
很多场合typedef struct Student
会直接写出typedef struct
都是一个意思。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 定义结构体类型,并使用 typedef 为结构体类型起别名
typedef struct Student
{
char name[20];
int age;
float grade;
}Student; // 这里的 Student 就是结构体类型的别名
int main()
{
Student student1;
strcpy(student1.name, "C_language");
student1.age = 18;
student1.grade = 98.1;
// 访问结构体成员
printf("Student Name: %s\n", student1.name);
printf("Student Age: %d\n", student1.age);
printf("Student Grade: %.2f\n", student1.grade);
return 0;
}
5.5 结构体数组¶
#include <stdio.h>
struct Student {
char name[50];
int age;
float grade;
};
int main() {
struct Student students[2] = {
{"Alice", 21, 91.2},
{"Bob", 22, 85.5}
};
// 访问结构体数组的成员
for (int i = 0; i < 2; i++) {
printf("Student %d Name: %s\n", i + 1, students[i].name);
printf("Age: %d\n", students[i].age);
printf("Grade: %.2f\n\n", students[i].grade);
}
return 0;
}
5.6 嵌套结构体¶
#include <stdio.h>
// 定义嵌套的结构体
typedef struct {
char street[50];
char city[30];
char zip[10];
} Address; // 地址结构体
typedef struct {
char name[50];
int age;
Address address; // 嵌套 Address 结构体
} Person; // 人员结构体
int main() {
// 创建一个 Person 类型的变量并初始化
Person person1 = {
"Alice",
25,
{
"123 Main St",
"New York",
"10001"
}
};
// 打印嵌套结构体的内容
printf("Name: %s\n", person1.name);
printf("Age: %d\n", person1.age);
printf("Address: %s, %s, %s\n",
person1.address.street,
person1.address.city,
person1.address.zip);
return 0;
}
/*输出*/
Name: Alice
Age: 25
Address: 123 Main St, New York, 10001
6. 枚举¶
6.1 枚举基础¶
默认情况下,枚举类型中值依次为 0,1,2 ... 。
enum Weekday {
MONDAY, // 默认值为 0
TUESDAY, // 默认值为 1
WEDNESDAY, // 默认值为 2
THURSDAY10, // 默认值为 3
FRIDAY // 默认值为 4
};
使用typedef
#include <stdio.h>
// 定义枚举并指定常量值
typedef enum {
MONDAY, // 默认值为 0
TUESDAY, // 默认值为 1
WEDNESDAY, // 默认值为 2
THURSDAY10, // 默认值为 3
FRIDAY // 默认值为 4
}Weekday ;
int main()
{
Weekday today;
today = WEDNESDAY;
printf("today:%d", today);
return 0;
}
/*输出*/
today:2
6.2 枚举与结构体的应用¶
// 基于STM32
typedef enum
{
GPIO_PIN_RESET = 0u,
GPIO_PIN_SET
} GPIO_PinState;
typedef struct
{
uint32_t Pin; /*!< Specifies the GPIO pins to be configured.
This parameter can be any value of @ref GPIO_pins_define */
uint32_t Mode; /*!< Specifies the operating mode for the selected pins.
This parameter can be a value of @ref GPIO_mode_define */
uint32_t Pull; /*!< Specifies the Pull-up or Pull-Down activation for the selected pins.
This parameter can be a value of @ref GPIO_pull_define */
uint32_t Speed; /*!< Specifies the speed for the selected pins.
This parameter can be a value of @ref GPIO_speed_define */
} GPIO_InitTypeDef;
//函数调用
void HAL_GPIO_WritePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState);
//结构体初始化
GPIO_InitTypeDef GPIO_InitStruct = {0};
7. 链表¶
7.1 链表基础¶
其中head
、Next
、last
均为指针,蓝色箭头与红色箭头均是指向整个节点,而绿色表示指向节点内部的Next
指针,灰色为不指向。每一个节点定义为一个结构体,一个成员为数据,一个为Next
指针,下图即是链表结构示意图,也是下面的添加节点、数据的代码流程示意图。
#include <stdio.h>
#include <stdlib.h>
#include "node.h"
int main()
{
//链表 head
Node * head = NULL;
int number;
do
{
scanf("Pleases input number: %d", &number);
if (number != -1)
{
/*数据添加至链表*/
//在堆区开辟一个 Node结构体的内存空间,
Node *p = (Node*)malloc(sizeof(Node));
p->value = number;
//新的结点 p 的Next指向NULL,而让原来的最后一个结点(last)指向这个新的结点
p->next = NULL;
//找最后一个结点
Node* last = head;
if (last)
{
while (last->next)
{
//一开始,last指向的是一个结点,若结点中的Next指针不为空,则让指针指向结点的Next
last = last->next;
}
//让最后一个结点(last)的Next指向这个新的结点(p)
last->next = p;
}
else
{
head = p;
}
}
} while (number != -1);
return 0;
}
7.2 链表函数¶
#include <stdio.h>
#include <stdlib.h>
#include "node.h"
/* 自定义一个数据类型,用于处理head被反复调用,但仅传入值不会改变head
* 与经典的指针swap()函数的原理一样
*/
typedef struct _list
{
Node* head;
}List;
void linked_list_add(List* pList, int number);
int main()
{
//链表 head
// Node * head = NULL;
List list;
list.head = NULL;
int number;
do
{
scanf("Pleases input number: %d", &number);
if (number != -1)
{
linked_list_add(&list, number);
}
} while (number != -1);
return 0;
}
/*
* 参数 pList : 是一个List数据类型的结构体指针
*/
void linked_list_add(List* pList, int number)
{
/*数据添加至链表*/
//在堆区开辟一个 Node结构体的内存空间,
Node *p = (Node*)malloc(sizeof(Node));
p->value = number;
//新的结点 p 的Next指向NULL,而让原来的最后一个结点(last)指向这个新的结点
p->next = NULL;
//找最后一个结点
Node* last = pList->head;
if (last)
{
while (last->next)
{
//一开始,last指向的是一个结点,若结点中的Next指针不为空,则让指针指向结点的Next
last = last->next;
}
//让最后一个结点(last)的Next指向这个新的结点(p)
last->next = p;
}
else
{
pList->head = p;
}
}
7.3 打印链表¶
打印链表函数,实质上就是遍历链表,然后打印数据,其中for (p=pList->head; p; p=p->next)
为常见方法。
#include <stdio.h>
#include <stdlib.h>
#include "node.h"
/* 自定义一个数据类型,用于处理 head被反复调用,但仅传入值不会改变 head
* 与经典的指针swap()函数的原理一样
*/
typedef struct _list
{
Node* head;
}List;
void linked_list_add(List* pList, int number);
void linked_list_print(List* pList);
int main()
{
//链表 head
// Node * head = NULL;
List list;
list.head = NULL;
int number;
do
{
scanf("%d", &number);
if (number != -1)
{
linked_list_add(&list, number);
}
} while (number != -1);
linked_list_print(&list);
return 0;
}
/*
* 参数 pList : 是一个List数据类型的结构体指针
*/
void linked_list_add(List* pList, int number)
{
/*数据添加至链表*/
//在堆区开辟一个 Node结构体的内存空间,
Node *p = (Node*)malloc(sizeof(Node));
p->value = number;
//新的结点 p 的Next指向NULL,而让原来的最后一个结点(last)指向这个新的结点
p->next = NULL;
//找最后一个结点
Node* last = pList->head;
if (last)
{
while (last->next)
{
//一开始,last指向的是一个结点,若结点中的Next指针不为空,则让指针指向结点的Next
last = last->next;
}
//让最后一个结点(last)的Next指向这个新的结点(p)
last->next = p;
}
else
{
pList->head = p;
}
}
/*打印链表*/
void linked_list_print(List* pList)
{
Node *p;
for (p=pList->head; p; p=p->next)
{
printf("%d\t", p->value);
}
printf("\n");
}
7.4 链表的删除¶
指针p
遍历寻找需要删除的结点,指针q
在指针p
指向下一结点(也就是p->next)前指向p
。同时存在一种特殊情况,即删除的结点为第一个结点,此时需要指针q
为空,让head指针指向下一结点(也就是p->next)。
Danger
根据解释,指针变化为蓝色至绿色;橙色为特殊情况。
#include <stdio.h>
#include <stdlib.h>
#include "node.h"
/* 自定义一个数据类型,用于处理 head被反复调用,但仅传入值不会改变 head
* 与经典的指针swap()函数的原理一样
*/
typedef struct _list
{
Node* head;
}List;
void linked_list_add(List* pList, int number);
void linked_list_print(List* pList);
int main()
{
//链表 head
// Node * head = NULL;
List list;
list.head = NULL;
int number;
do
{
scanf("%d", &number);
if (number != -1)
{
linked_list_add(&list, number);
}
} while (number != -1);
linked_list_print(&list);
scanf("%d\n", &number);
Node* p, q;
// int isFound = 0;
for(q=NULL, list.head; p; q=p, p=p->next)
{
if(p->value = number)
{
if(q){
q->next = p->next;
}
else{
list.head = p->next;
}
free(p);
// printf("找到了\n");
// isFound = 1;
break;
}
}
// if(!isFound) {printf("未找到\n");}
return 0;
}
/*
* 参数 pList : 是一个List数据类型的结构体指针
*/
void linked_list_add(List* pList, int number)
{
/*数据添加至链表*/
//在堆区开辟一个 Node结构体的内存空间,
Node *p = (Node*)malloc(sizeof(Node));
p->value = number;
//新的结点 p 的Next指向NULL,而让原来的最后一个结点(last)指向这个新的结点
p->next = NULL;
//找最后一个结点
Node* last = pList->head;
if (last)
{
while (last->next)
{
//一开始,last指向的是一个结点,若结点中的Next指针不为空,则让指针指向结点的Next
last = last->next;
}
//让最后一个结点(last)的Next指向这个新的结点(p)
last->next = p;
}
else
{
pList->head = p;
}
}
/*打印链表*/
void linked_list_print(List* pList)
{
Node *p;
for (p=pList->head; p; p=p->next)
{
printf("%d\t", p->value);
}
printf("\n");
}
7.5 链表的清除¶
#include <stdio.h>
#include <stdlib.h>
#include "node.h"
/* 自定义一个数据类型,用于处理 head被反复调用,但仅传入值不会改变 head
* 与经典的指针swap()函数的原理一样
*/
typedef struct _list
{
Node* head;
}List;
void linked_list_add(List* pList, int number);
void linked_list_print(List* pList);
int main()
{
//链表 head
// Node * head = NULL;
List list;
list.head = NULL;
int number;
do
{
scanf("%d", &number);
if (number != -1)
{
linked_list_add(&list, number);
}
} while (number != -1);
linked_list_print(&list);
scanf("%d", &number);
Node* p;
Node* q;
int isFound = 0;
for(q=NULL, list.head; p; q=p, p=p->next)
{
if(p->value = number)
{
if(q){
q->next = p->next;
}
else{
list.head = p->next;
}
free(p);
printf("find it\n");
isFound = 1;
break;
}
}
if(!isFound) {printf("not find it\n");}
//清除链表
for(p=list.head; p; p=q)
{
q = p->next;
free(p);
}
return 0;
}
/*
* 参数 pList : 是一个List数据类型的结构体指针
*/
void linked_list_add(List* pList, int number)
{
/*数据添加至链表*/
//在堆区开辟一个 Node结构体的内存空间,
Node *p = (Node*)malloc(sizeof(Node));
p->value = number;
//新的结点 p 的Next指向NULL,而让原来的最后一个结点(last)指向这个新的结点
p->next = NULL;
//找最后一个结点
Node* last = pList->head;
if (last)
{
while (last->next)
{
//一开始,last指向的是一个结点,若结点中的Next指针不为空,则让指针指向结点的Next
last = last->next;
}
//让最后一个结点(last)的Next指向这个新的结点(p)
last->next = p;
}
else
{
pList->head = p;
}
}
/*打印链表*/
void linked_list_print(List* pList)
{
Node *p;
for (p=pList->head; p; p=p->next)
{
printf("%d\t", p->value);
}
printf("\n");
}