学习嵌入式的C基础知识,主要包括几个核心知识点:三大语法结构、常用的数据类型、函数、结构体、指针、文件操作。
一、顺序结构
程序自上而下依次执行、没有分支、代码简单。
常见顺序结构有:四则运算:+,-,*(乘),/(除)以及%(求余)。
二、选择结构
if-else
switch case
三、循环结构
for循环
while循环
do-while循环
do
{
循环体;
条件控制语句;
}while (条件判断语句); // 终止条件
四、常用的数据类型
一个字节为2的8次方
四、常用的函数
1 Main函数
一个C程序就是由若干头文件和函数组成,有且只有一个主函数,即main函数。
2 strcpy函数
C语言 strcpy() 函数用于对字符串进行复制(拷贝)。
头文件:string.h
语法/原型:
char* strcpy(char* strDestination, const char* strSource);
参数说明:
strDestination:目的字符串。
strSource:源字符串。
strcpy() 会把 strSource 指向的字符串复制到 strDestination。
必须保证 strDestination 足够大,能够容纳下 strSource,否则会导致溢出错误。
返回值:目的字符串,也即 strDestination。
3 malloc函数
malloc 向内存申请一块空间, 大小为 _Size, 并返回一个指向该空间的地址. 指针类型和内存大小由程序员自行定义.
4 free函数
free()函数用于释放内存, 只需向函数传递已经通过 malloc 分配空间的指针即可.
5 strlen函数
用来返回字符串长度的. 函数参数是 指向字符数组的指针
6 strcat函数
用于拼接字符串, 即将原字符串插入目标字符串的后面. 函数参数从左到右依次是 : 指向目标字符数组的指针, 指向原字符数组的指针. 返回类型是一个 char 指针*.
7 strcmp函数
比较字符串的长短或者查看两个字符串是否相等. 其原理是将字符串的字符一一比较 ascii 值大小得到的
8 预处理
__FILE__ //进行编译的源文件文件路径()
__LINE__ //文件当前的行号
__DATE__ //文件被编译的日期
__TIME__ //文件被编译的时间
__STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义
五 结构体
结构体本质上还是一种数据类型,但它可以包括若干个“成员”,每个成员的类型可以相同也可以不同,也可以是基本数据类型或者又是一个构造类型。
结构体的优点:结构体不仅可以记录不同类型的数据,而且使得数据结构是“高内聚,低耦合”的,更利于程序的阅读理解和移植,而且结构体的存储方式可以提高CPU对内存的访问速度。
1 结构声明(定义一个结构体)
struct 结构体名{
成员列表
};
2 定义结构变量
struct 结构体名 结构体变量名;
struct Student stu1; //定义结构体变量
结构体变量的定义可以放在结构体的声明之后:
结构体变量的定义也可以与结构体的声明同时,这样就简化了代码:
3 访问结构成员
虽然结构类似一个数组,只是数组元素的数据类型是相同的,而结构中元素的数据类型是可以不同的。但结构不能像数组那样使用下标去访问其中的各个元素,而应该用结构成员运算符点(.)。
访问成员的一般形式是:
结构变量名 . 成员名
如 stu1 . name 表示学生stu1的姓名。
特殊的:则用 stu1.birthday.year 访问出生的年份。
但如果结构体中的成员又是一个结构体,如:则用 stu1.birthday.year 访问出生的年份。
struct Birthday{ //声明结构体 Birthday
int year;
int month;
int day;
};
struct Student{ //声明结构体 Student
char name[20];
int num;
float score;
struct Birthday birthday; //生日
}stu1;
4 结构体变量的初始化:结构体变量的初始化可以放在定义之后:
struct Student stu1, stu2; //定义结构体变量
strcpy(stu1.name, "Jack");
stu1.num = 18;
stu1.score = 90.5;或
stu2 = (struct Student){"Tom", 15, 88.0};
或 结构体变量的初始化也可以与定义同时
struct Student{ //声明结构体 Student
char name[20];
int num;
float score;
}stu = {"Mike", 15, 91}; //注意初始化值的类型和顺序要与结构体声明时成员的类型和顺序一致
六 结构体数组
结构类型作为一种数据类型,也可以像基本数据类型那样,作为数组的元素的类型。
生活中经常用到结构数组来表示具有相同数据结构的一个群体,如一个班的学生的信息,一个书店或图书馆的书籍信息等。
1 结构数组定义
struct 结构名 {
成员列表
} 数组名[数组长度];如:
struct Student{ //声明结构体 Student
char name[20];
int num;
float score;
}stu[5]; //定义一个结构结构数组stu,共有5个元素
2 结构数组的初始化
1)声明结构数组的同时进行初始化
struct Student stu[2] = {{"Mike", 27, 91},{"Tom", 15, 88.0}};
2)先定义,后初始化
stu[2] = (struct Student){"Jack", 12, 85.0};
3)将结构体变量的成员逐个赋值:
strcpy(stu[3].name, "Smith");
stu[3].num = 18;
stu[3].score = 90.5;
3 输出结构体数组
//结构体数组的长度:
int length = sizeof(stu) / sizeof(struct Student);
//逐个输出结构数组的元素
for (int i = 0; i < length; i++) {
printf("姓名:%s 学号:%d 成绩:%f \n", stu[i].name, stu[i].num, stu[i].score);
}
七 结构体与指针
当一个指针变量用来指向了一个结构变量,这个指针就成了结构指针变量。
结构指针变量中的值是所指向的结构变量的首地址。可以通过指针来访问结构变量。
1 定义结构体指针变量
struct 结构体名 结构体变量名 * 结构指针变量名
struct Student stu *pstu; //定义一个结构体变量和一个结构体指针
或将结构体变量的地址赋值给结构体指针
pstu = &stu
2 通过结构体指针间接访问成员值
(*结构指针变量). 成员名 或 结构指针变量 -> 成员名
(*pstu).name //注意(pstu)的小括号不能省略,因为成员符“.”优先级为1,取地址符“”优先级为2,去掉括号就相当于*(pstu.name)了。
pstu->name
八 结构体的嵌套
1 结构体中的成员可以又是一个结构体,构成结构体的嵌套:
struct Birthday{ //声明结构体 Birthday
int year;
int month;
int day;
};
struct Student{ //声明结构体 Student
char name[20];
int num;
float score;
struct Birthday birthday; //生日
};2 结构体不可以嵌套跟自己类型相同的结构体,但可以嵌套定义自己的指针。如
3 多层嵌套
九 结构体与函数
结构体的成员可以作为函数的参数,属于值传递(成员是数组的除外)。如:
struct Student{ //声明结构体 Student
char name[20];
int num;
float score;
};
void printNum(int num){ //定义一个函数,输出学号
printf("num = %d \n", num);
}
struct Student student0 = {"Mike", 27, 91}; //结构体参数初始化
printNum(student0.num); //调用printNum 函数,以结构成员作函数的参数
//运行结果:num = 27
结构变量名也可以作为函数的参数传递,如
void PrintStu(struct Student student){ //定义 PrintStu 函数,以结构体变量名作函数的形参
student.num = 100; //修改学号
printf("PrintStu 修改后:姓名: %s, 学号: %d, 内存地址: %p \n", student.name, student.num, &student);
}
struct Student student0 = {"Mike", 27, 91};
PrintStu(student0); //调用 PrintStu 函数,以结构变量名作函数的参数
printf("原来:姓名: %s, 学号: %d, 内存地址: %p \n", student0.name, student0.num, &student0);
还可以用定义的结构体指针变量来传参
形参和实参的地址不一样,是在函数中创建了一个局部结构体,然后实参对形参进行全部成员的逐个传送,在函数中对局部结构体变量进行修改并不影响原结构体变量。这样传送的时间空间开销都比较大,特别是当成员有数组的时候,程序效率较低。所以可以考虑使用指针:
void PrintStu2(struct Student *student){ //定义 PrintStu2 函数,以结构指针作函数的形参
student->num = 100; //修改学号
printf("PrintStu2 修改后:姓名: %s, 学号: %d, 内存地址: %p \n", student->name, student->num, student);
}
struct Student student0 = {"Mike", 27, 91};
PrintStu2(&student0); //调用 PrintStu 函数,以结构变量的地址作函数的参数
printf(" 原来:姓名: %s, 学号: %d, 内存地址: %p \n", student0.name, student0.num, &student0);
十 指针
为什么要使用指针
需要说明一下的是指针类型存储的是所指向变量的地址:
所以32位机器只需要32bit,而64位机器需要 64bit由此可知,在一台电脑上,所有指针大小都是一样的。
那么,当我们定义了一些非常复杂庞大的结构体,比如这个结构体有50个字节(Byte)。
这时候,向函数中传入参数的时候,是直接把这个50个字节的数据的副本传递到形参上快?
还是,直接传递32bit或者64bit的指针快呢??很明显,传入指针可以大大大大提高运算的效率。
基本格式:
基本类型 *指针变量名 = &变量名;
* 取值运算符 &取地址运算符
//定义变量:
int a = 666;
char b = 'b';//定义指针变量:
//1.告诉计算机这是一个保存int类型数据地址的指针变量
int *pa = &a;//2.告诉计算机这是一个保存char字符类型数据地址的指针变量
char *pb= &b;
野指针
没有初始化的指针,就可以称为:野指针。
#include <stdio.h>
int main(void){
int *p;
*p = 10;
return 0;
}
十一 指针与数组
一、指向数组元素的指针
1 数组元素地址赋值给指针变量
int c[10], d[20];
int *p, *q=&c[0]; //初始化p = &c[3];//数组c下标为3的元素赋值给指针P
p = d;//整个数组的地址赋值给指针p
2 指针的运算
众所周知,数组,存在内存中一片地址连续的区域中
即就是说,数组,在逻辑上是一个元素挨着一个元素,在物理存储上也是一个元素挨着一个元素的。
1 数组元素指针自身加或减
当指针变量指向数组元素时,指针变量加/减一个整数n,表示指针向后/前移动n个元素!
int c[20];
int *p1 = &c[9];
p1--;
p1+=3;
2 数组元素指针之间加减
相减得到的这个数字是这两个地址之间相差的字节数。
然后,用这个字节数,除以每个元素所占的字节数。
就可以得出这两个地址之间相差几个元素。
二、指针数组(数组元素是指针)
整形数组中,数组元素都是整型
字符数组中,数组元素都是字符
指针数组中,数组元素都是指针
int i = 1;
int j = 2;
int k = 3;
int l = 4;
int *p[4] = {&i, &j, &k, &l};//单目运算符,同优先级的情况下,丛右往左进行运算
//[]先与p结合,表示这是一个数组
//*再与p[]结合,表示这个数组元素类型是指针
//最后加上前面的int,表示这个int类型的指针
注意区分两个概念:
int (*p)[5];
//这里*先与p结合,表示p是一个指针,这个指针指向这个数组,这个数组是int类型的数组
int *p[5];//这个表示指针数组,每个元素都是指针,上面说过了
十二 指针与函数
1 返回值是指针的函数
指针函数:指针函数的本身是一个函数,其函数的返回值是一个指针
函数的返回值的类型既可以是整型(若没有设定,则默认为整型),实型,字符型,也可以是指针型。
返回值为指针类型的函数又称为指针类型的函数,建成指针函数。
其函数值为指针,即它带回来的值的类型为指针,当调用这个函数后,将得到一个"指向返回值为…的指针(地址)
类型名 *函数名(参数表)
int *a(int x)
//解释一下
//这里函数名先和()结合,表示这是一个函数
//然后a()再和前面的*结合,表示这是一个指针类型的函数。
//最后与前面的int结合,表示这是int指针类型的函数,返回值是指针
//(这个过程有点类似前面的指针数组,可以翻上去复习一下)
2 函数指针
如果在程序中定义了一个函数,那么在编译时系统就会为这个函数代码分配一段存储空间,这段存储空间的首地址称为这个函数的地址。而且函数名表示的就是这个地址。既然是地址我们就可以定义一个指针变量来存放,这个指针变量就叫作函数指针变量,简称函数指针。
函数返回值类型 (* 指针变量名) (函数参数列表);
int(*p)(int, int);
十三 文件操作
1 文件指针
在C语言中用一个指针变量指向一个文件,这个指针称为文件指针。
FILE是系统使用typedef定义出来的有关文件信息的一种结构体类型,结构中含有文件名、文件状态和文件当前位置等信息。
typedef struct
{
short level; //缓冲区"满"或者"空"的程度
unsigned flags; //文件状态标志
char fd; //文件描述符
unsigned char hold; //如无缓冲区不读取字符
short bsize; //缓冲区的大小
unsigned char *buffer;//数据缓冲区的位置
unsigned ar; //指针,当前的指向
unsigned istemp; //临时文件,指示器
short token; //用于有效性的检查
}FILE;
C语言中有三个特殊的文件指针由系统默认打开,用户无需定义即可直接使用:
stdin: 标准输入,默认为当前终端(键盘),我们使用的scanf、getchar函数默认从此终端获得数据。
stdout:标准输出,默认为当前终端(屏幕),我们使用的printf、puts函数默认输出信息到此终端。
stderr:标准出错,默认为当前终端(屏幕),我们使用的perror函数默认输出信息到此终端。
2 文件的打开
任何文件使用之前必须打开
#include <stdio.h>
FILE * fopen(const char * filename, const char * mode);
功能: 打开文件
参数:
filename:需要打开的文件名,根据需要加上路径
mode: 打开文件的模式设置
返回值:
成功: 文件指针
失败: NULL
filename参数
FILE *fp_passwd = NULL;
//相对路径:
//打开当前目录passdw文件:源文件(源程序)所在目录
FILE *fp_passwd = fopen("passwd.txt", "r");
//打开当前目录(test)下passwd.txt文件
fp_passwd = fopen(". / test / passwd.txt", "r");
//打开当前目录上一级目录(相对当前目录)passwd.txt文件
fp_passwd = fopen(".. / passwd.txt", "r");
//绝对路径:
//打开C盘test目录下一个叫passwd.txt文件
fp_passwd = fopen("c:/test/passwd.txt","r");
mode参数
3 文件的关闭
任何文件在使用后应该关闭:
- 打开的文件会占用内存资源,如果总是打开不关闭,会消耗很多内存
- 一个进程同时打开的文件数是有限制的,超过最大同时打开文件数,再次调用fopen打开文件会失败
- 如果没有明确的调用fclose关闭打开的文件,那么程序在退出的时候,操作系统会统一关闭。
#include <stdio.h>
int fclose(FILE * stream);
功能:关闭先前fopen()打开的文件。此动作让缓冲区的数据写入文件中,并释放系统所提供的文件资源。
参数:
stream:文件指针
返回值:
成功:0
失败:-1
FILE * fp = NULL;
fp = fopen("abc.txt", "r");
fclose(fp);
4 文件的读写
put 写 get 读
按照字符读写文件fgetc、fputc
按照行读写文件fgets、fputs
按照字符串格式化读写文件fprintf、fscanf //根据format字符串来转换并格式化数据
按照块读写文件fread、fwrite
5 文件的随机读写
int fseek(FILE *stream, long offset, int whence);
功能:移动文件流(文件光标)的读写位置。
long ftell(FILE *stream);
功能:获取文件流(文件光标)的读写位置。
void rewind(FILE *stream);
功能:把文件流(文件光标)的读写位置移动到文件开头。
6 获取文件状态
#include <sys/types.h>
#include <sys/stat.h>
int stat(const char *path, struct stat *buf);
功能:获取文件状态信息
参数:
path:文件名
buf: 保存文件信息的结构体
返回值:
成功:0
失败-1
struct stat
{
dev_t st_dev; //文件的设备编号
ino_t st_ino; //节点
mode_t st_mode; //文件的类型和存取的权限
nlink_t st_nlink; //连到该文件的硬连接数目,刚建立的文件值为1
uid_t st_uid; //用户ID
gid_t st_gid; //组ID
dev_t st_rdev; //(设备类型)若此文件为设备文件,则为其设备编号
off_t st_size; //文件字节数(文件大小)
unsigned long st_blksize; //块大小(文件系统的I/O 缓冲区大小)
unsigned long st_blocks; //块数
time_t st_atime; //最后一次访问时间
time_t st_mtime; //最后一次修改时间
time_t st_ctime; //最后一次改变时间(指属性)
};
7 删除 重命名文件
删除文件
#include <stdio.h>
int remove(const char *pathname);
功能:删除文件
参数:
pathname:文件名
返回值:
成功:0
失败:-1
重命名文件
#include <stdio.h>
int rename(const char *oldpath, const char *newpath);
功能:把oldpath的文件名改为newpath
参数:
oldpath:旧文件名
newpath:新文件名
返回值:
成功:0
失败: - 1
8 文件缓冲区
磁盘文件的存取
#include <stdio.h>
int fflush(FILE *stream);
功能:更新缓冲区,让缓冲区的数据立马写到文件中。
参数:
stream:文件指针
返回值:
成功:0
失败:-1