嵌入式之C语言基础
1. C语言基础
1.1 关键字和保留标识符
关键字就是C语言已经定义好的专用字,只用不改。(类似于拼音字母的含义)

1.2 常量 constant
在程序使用前定义好了,运行过程没有变化的量。
1.3 变量 variable
在程序运行前或过程中被定义,并被改变或赋值的量。
1.4 数据类型
1.4.1 数据类型关键词
数据类型使用关键字表示。

按计算机的储存方式可分为两大基本类型:整数类型和浮点数类型。
- int表示基本的整数类型。
- long、shortunsigned、signed用于提供基本整数类型的变式,例如unsigned short int 和long long int。
- char关用于指定字母和其他字符,其次,也可以表示较小的整数。
- float、double和long double表示带小数点的数。
- _Bool 类型表示布尔值(true或false)。
- _complex 和 _Imaginary 分别表示复数和虚数。
1.4.2 整数
没有小数部分的数。计算机存储以二进制形式存储整数。
1.4.3 浮点数
有小数部分的数。计算机存储以二进制形式分别存储指数部分和小数部分,如\(0.7=0.7^1\), 0.7为小数部分, 1为指数部分。
浮点数是近似表示,如7.0可能表示为6.9999999,所以浮点数在运算时可能会损失精度。
1.4.4 数据类型介绍
1.4.4.1 int类型
// int 类型(4字节=32位)
// 1. 声明 分配内存空间
int age;
int stuNum, id, scorces;
// 2. 初始化 分配内存空间并赋值
int myAge = 22;
int myStuNum = 1001, myId = 3790, myScorces = 100;
// 3. 打印
printf("my id is %d\n", myId);
printf("my age is %d, stuNum is %d, and scores is %d.\n", myAge, myStuNum, myScorces);
// 4. 显示各个进制
int x = 100;
printf("十进制 = %d\n", x);
printf("八进制 = %o\n", x);
printf("十六进制 = %x\n", x);
printf("-----------显示前缀----------\n");
printf("十进制 = %d\n", x);
printf("八进制 = %#o\n", x);
printf("十六进制 = %#x\n", x);
C语言提供3个附属关键字修饰基本整数类型: short、 long 和unsigned。
- short int类型(或者简写为short)占用的存储空间可能比int类型少,常用于较小数值的场合以节省空间。与int类似,short 是有符号类型。
- long int(long)占用的存储空间可能比int多,适用于较大数值的场合。与int类似,long是有符号类型。
- long long int(long long)(C99 标准加入)占用的储存空间可能比long多,适用于更大数值的场合。该类型至少占64位。与int类似,long long是有符号类型。
- unsigned int或unsigned只用于非负值的场合。这种类型与有符号类型表示的范围不同。例如,16位 unsigned int 允许的取值范围是065535,而不是-3276832767。 用于表示正负号的位现在用于表示另一一个二进制位,所以无符号整型可以表示更大的数。
- 在C90标准中,添加了unsigned long int(unsigned long)和unsigned int(unsigned short)。C99标准又添加了unsigned long long int(unsigned long long)。
- 在任何有符号类型前面添加关键字signed,可强调使用有符号类型的意图。例如,short、short int、signed short、signed short int 都表示同一种类型。
1.4.4.2 char类型
可以有符号(范围\([-128, 127]\)),也可无符号(范围\([0, 255]\))。
// char 类型(1字节=8位)
// 1. 声明 分配内存空间
char sex;
char grade, clas;
// 2. 初始化 分配内存空间并赋值
char mySex = 'M';
char myGrade = 'A', myClass = 120;
// 3. 打印
printf("my sex is %c\n", mySex);
printf("my grade is %c, my class is %d %c.\n", myGrade, myClass, myClass);
转义字符

1.4.4.3 _Bool类型
表示 true 或 false, 1位足矣。
- C++ 为 bool类型
1.4.4.4 float、double、long double类型
// float 4字节 / double 8字节 / long double 类型
// 1. 声明 分配内存空间
float a, b;
double ad, ba;
long double lad;
// 2. 初始化 分配内存空间并赋值
double y = 6.63e-34;
float x = 4.0F;
long double z = 4.0L;
// 3. 打印
printf("x is %e\n", y);
printf("y is %f, z is %le or %la.\n", x, z, z);
1.4.4.5 复数和虚数类型
- 3种复数类型: float_ Complex、 double_ Complex 和 long double_ Complex。例如,float_ Complex 类型的变量应包含两个float类型的值,分别表示复数的实部和虚部。
- 3种虚数类型是float_ Imaginary、 double_ Imaginary和1ong double_ Imaginaryo 如果包含complex.h头文件,便可用complex代替_Complex, 用imaginary代替_ Imaginary, 还可以用I代替-1的平方根。
1.5 格式化输入输出

1.6 运算符
三目运算符 C返回值,一个常量。
- C++返回变量,可以再次赋值。
1.7 表达式和语句
1.8 判断
1.9 循环
1.10 数组
1.11 函数
2. C语言进阶
2.1 预处理
2.1.1 编译过程
1. 预处理
- gcc -E hello.c -o hello.i
将.c中的头文件展开、宏展开,生成.i文件
2. 编译
- gcc -S hello.i -o hello.s
将.i文件生成.s汇编文件
3. 汇编
- gcc -c hello.s -o hello.o
将.s汇编成.o目标文件
4. 链接
- gcc hello.o -o hello_elf
将.o文件链接成目标文件
2.1.2 include
#include<...>系统指定路径下找头文件#include"..."先在当前目录找,找不到再去系统指定路径找
- 预处理仅对include等预处理操作进行处理,并不会进行语法检查,编译时才会语法检查,报语法错误。
2.1.3 define
- define 宏定义,在预处理时进行替换。
- 则修改时方便,其他地方预处理时自动替换。(注意:后面不要加分号“;”)
- 作用范围在 本文件的末尾或者在 #undef 处中止。
- 不带参
例子:#define PI 3.14用PI代替3.14 - 带参
例子:
#define S(a, b) a*b
S(2, 4) // 2*4=8
S(2+8, 4) // 2+8*4=32
类似函数,但没有数据类型,预处理时 实参代替字符串形参而其他字符保留。
- 带参宏与带参函数的区别:
宏 -> 浪费空间节约时间,即每调用一次展开一次,不压栈弹栈;
函数 -> 浪费时间节约空间,即代码一份,调用时取指令, 压栈弹栈。
2.1.4 选择性编译
- 例子:
#ifdef A
code seg1
#else
code seg2
#endif
如果定义过 A, 则编译 代码段一, 否则编译 代码段二。
2. 例子:
#ifndef A
code seg1
#else
code seg2
#endif
如果没定义过 A, 则编译 代码段一, 否则编译 代码段二。
3. 例子:
#if expression
code seg1
#else
code seg2
#endif
如果表达式为真, 则编译 代码段一, 否则编译 代码段二。
2.2 指针
2.2.1 内存理解
存储器:存储数据器件
- 外存
外部存储器, 长期存放数据,掉电不丢失数据。
常见设备:硬盘、flash、rom、U盘、光盘、磁带。 - 内存
内部存储器, 暂时存储数据, 掉电丢失数据。
常见设备:ram、DDR。
-
物理内存:现实世界内存条。
-
虚拟内存:操作系统中的概念, 进程被创建或者程序运行时分配虚拟内存, 虚拟内存和物理内存存在映射关系。
-
运行程序时,操作系统对虚拟内存分区:
- 堆
动态申请内存会在此处开辟 - 栈
存放局部变量 - 静态全局区
- 未初始化静态全局区
存放未初始化的静态变量或全局变量 - 初始化的静态全局区
存放初始化的静态变量或全局变量
- 未初始化静态全局区
- 代码区
存放程序代码 - 文字常量区
存放常量
- 堆
-
内存以字节为单元存储数据,可以将程序的虚拟寻址空间,看作一个很大的一维字符数组。
2.2.2 指针的相关概念
简单理解,指针就是地址。
指针变量用来存放地址编号,32位平台 占 4个字节。
相应类型的指针变量,只存放相应类型的变量的地址。
2.2.3 指针的定义方法
- 简单指针
// 数据类型 * 指针变量名
// 定义一个 名为 p 的 整型指针
int* p;
- 指针的运算符
& 取地址、 *取值
*取值跟指针的类型有关,即int取4个字节,double取8个字节,char取一个字节。
自增自减运算也是按照类型去加减相应的字节数。
// 将 整型 a 的 地址传给 指针变量 p
int a = 100;
int* p = &a;
printf("address of a: %p\n", &a);
printf("content of p: %d and address of a: %p\n", *p, p);
一行定义多个指针变量,需要在每个变量前都加*。
- 指针大小
在32位平台,均为4字节。
int * p;
char * q;
printf("size %d\n", sizeof(p))
printf("size %d\n", sizeof(q))
// 其他类型类似
2.2.4 指针的分类
按数据类型分
- 字符 char
- 短整型 short
- 整型 int
- 长整型 long
- 浮点型 float 、 double
- 函数指针
- 结构体指针
- 指针的指针
- 数组指针
2.2.5 数组元素指针
指针变量可以通过保存数组首元素地址,操纵数组。
// 1. 保存数组首元素地址
int a[10]= {0};
int * p = a; // 或者 int * p = &a[0];
// 2. 引用方法
p[2] = 100; // 或者 *(p + 2) = 100;
printf("a[2] is %d\n", a[2]);
这里也可以看出 *(p + i) == p[i] = a[i]
指向同一个数组的同一类型的指针比较大小才有意义 -> 小的 指向前面的元素;指向同一个数组的同一类型指针做减法的意义 -> 两者之间有多少元素。
2.2.6 指针数组
本质是数组,是由指针变量组成的数组,数组指针是指向数组的指针。
2.2.7 指针的指针
指向指针的指针变量。
一级指针 保存 普通变量的地址
n级指针 保存 n-1级指针变量的地址
例子:
int a = 100;
int * p = &a;
int* * q = &p;
抹掉名字看自身是什么类型的几级指针,抹掉名字和一个*看保存的是什么类型的变量。
2.2.8 数组指针
定义:数组类型 (* 指针变量名) [数组第二维度的元素个数][...]
二维数组
指针对二维数组,会成为其行指针,即 +1 直接跳行。
int b[3][5] = {0};
int (*q)[5] = b;
n+1维数组可以用n维数组指针代替。
对数组名字取地址,会变成数组指针,即
int a[10];
int (*p)[10] = &a; // 获取数组地址, 加1就相差10个元素
数组名字和指针变量的区别
- 相同点:
引用数组时,a和p 等价。 - 不同点:
- a是常量、p是变量,p可以赋值,a不能;
- 取地址结果不同,p取地址为指针的指针,a取地址为数组指针。
2.2.9 指针与函数
值传递:不改变原变量
地址传递:可改变原变量
- 指针函数
本质是函数,只不过返回值是一个指针。 - 函数指针
保存函数的指针,函数的名字就是函数的首地址,即函数的入口地址。
返回值类型(*函数指针变量名)(形参列表)
例子:
#include<stdio.h>
int max(int, int);
int main(){
int(*p)(int, int);
p = max;
int MAX = (*p)(3, 10);
printf("max is %d\n", MAX);
return 0;
}
int max(int x, int y){
if(x > y){
return x;
}else{
return y;
}
}
最常用的地方:
将函数作为参数传递给另一个函数。
例子:
#include<stdio.h>
int max(int, int);
int min(int, int);
int add(int, int);
int sub(int, int);
int process(int(*p)(int, int), int, int);
int main(){
int(*p[4])(int, int) = {max, min, add, sub};
for(int i = 0; i < 4; i++){
process(p[i], 10, 7);
}
return 0;
}
int max(int x, int y){
printf("max is ");
if(x > y){
return x;
}else{
return y;
}
}
int min(int x, int y){
printf("min is ");
if(x > y){
return y;
}else{
return x;
}
}
int add(int x, int y){
printf("sum is ");
return x+y;
}
int sub(int x, int y){
printf("sub is ");
return x-y;
}
int process(int(*p)(int, int), int x, int y){
int tmp = (*p)(x, y);
printf("%d\n", tmp);
return tmp;
}
- 函数指针数组
本质是数组,元素是函数指针。
返回值类型(*函数指针变量名[数组元素个数])(形参列表)
int (*p[10])(int, int);
2.2.10 特数指针
- 通用指针 void* (有泛型的意思)
- 空指针 NULL。
2.2.11 main函数传参
int main(int argc, char* argv[]){
// argc 是一个int型变量,标识命令终端传入的参数个数
// argv 是一个char型指针数组,保存传入的参数
int i;
printf("argc=%d\n", argc);
for(i = 0; i < argc; i++){
printf("argv[%d]=%s\n", i, argv[i]);
}
return 0;
}
2.3 动态内存申请
2.3.1 概述
数组长度预先定义好,无法修改,而需要不定长度的数组,则需要动态申请内存。
2.3.2 静态与动态
- 静态
- 编译或运行过程中,按事先规定的大小分配内存空间;
- 须事先指导所用空间大小;
- 分配在栈区或者全局变量区;
- 按计划分配。
- 动态
- 运行过程中,按需分配内存空间;
- 无需事先指导所用空间大小;
- 分配在堆区或者全局变量区。
2.3.2 动态分配函数
- malloc函数
- 头文件:#include<stdlib.h>
- 函数名:void *malloc(unsigned int size)
- 功能:在堆区开辟指定长度的空间
- 参数: size 开辟空间的大小
- 返回值:成功:开辟好的空间首地址;失败:NULL。
- 注意:多次申请空间不一定连续;返回值为void* 所以要强制类型转换,告知类型。
- free函数
- 头文件:#include<stdlib.h>
- 函数名:void free(void *ptr)
- 功能:释放ptr指向的内存
- 参数: 要释放的地址的 所用的指针变量
- 注意:ptr指向内存必须是 动态申请的内存;free后释放内存,则ptr成为野指针,一般会对其赋为NULL;一个内存只能free一次。
- calloc函数
- 头文件:#include<stdlib.h>
- 函数名:void *calloc(size_t nmenb, size_t size) -> size_t是unsigned int 被 typedef 重定义了而已
- 功能:在堆区开辟 nmemb块 空间,每块大小为size个字节的连续区域
- 参数: nmemb 申请的块数量,size 每块的大小
- 返回值:成功:开辟好的空间首地址;失败:NULL。
- 注意:多次申请空间不一定连续;返回值为void* 所以要强制类型转换,告知类型;calloc申请的内存内容提前初始化为0。
- realloc函数
- 头文件:#include<stdlib.h>
- 函数名:void *realloc(void * s, unsigned int newsize)
- 功能:在s 指向的空间基础上重新申请内存, 新的内存大小为 newsize大小; 如果有足够空间,就追加,否则,新开辟一块将原先内容拷贝过来,然后释放原先的;如果newsize比原先小,就释放后面的,只保留newsize个字节的内容。
- 参数: s 原先内存的首地址,newsize 块的大小
- 返回值:成功:开辟好的空间首地址;失败:NULL。
- 注意:多次申请空间不一定连续;返回值为void* 所以要强制类型转换,告知类型;calloc申请的内存内容提前初始化为0。
2.3.3 内存泄漏
申请的内存,首地址找不到了,无法使用和释放了,进而内存无法使用,即内存泄漏。
解决方式:不用时, 及时的free。
2.4 字符串处理函数
2.4.1 获取字符串长度函数
- 头文件:#include<string.h>
- 函数名:size_t strlen(const char *s)
- 功能:测试字符串中的字符个数, 不包括'\0', 在第一个'\0'处结束。
- 参数: s 要测试的字符串内存的首地址
- 返回值:无符号整数,即字符串大小。
2.4.2 获取字符串拷贝函数
- 头文件:#include<string.h>
- 函数名:char *strcpy(char *dest, const char *src)
- 功能:拷贝src的内容到dest, 也包括'\0'。
- 参数: dest 目的地内存的首地址,src 源首地址。
- 返回值:dest的首地址。
- 头文件:#include<string.h>
- 函数名:char *strncpy(char *dest, const char *src, size_t n)
- 功能:拷贝src的前n个字节的内容到dest, 不包括'\0'。
- 参数: dest 目的地内存的首地址,src 源首地址,n 要拷贝的字节数。
- 返回值:dest的首地址。
- 注意:当 n 大于 src 字符串内的字符个数,则在 dest 后面填充 n-strlen(src)个'\0';其次要保证 dest 的内存大于 要存储的字节数。
2.4.3 获取字符串追加函数
- 头文件:#include<string.h>
- 函数名:char *strcat(char *dest, const char *src)
- 功能:把src的内容追加到dest后面, 包括'\0'。
- 参数: dest 追加字符的内存的首地址,src 源首地址。
- 返回值:追加后dest字符的首地址。
- 注意:保证 dest 的内存足够追加。
- 头文件:#include<string.h>
- 函数名:char *strncat(char *dest, const char *src, size_t n)
- 功能:把src的前n个字节内容追加到dest后面, 包括'\0'。
- 参数: dest 目的地内存的首地址,src 源首地址,n 要追加的字节数。
- 返回值:dest的首地址。
- 注意:当 n 大于 src 字符串内的字符个数,则在 dest 后面填充 n-strlen(src)个'\0';其次要保证 dest 的内存足够追加。
2.4.4 获取字符串比较函数
- 头文件:#include<string.h>
- 函数名:char *strcmp(const char *s1, const char *s2)
- 功能:比较s1与s2字符串大小, 逐字符比较ASCII码,一旦比出立即返回。
- 参数: s1 字符的内存的首地址,s2 字符的内存的首地址。
- 返回值:大于返回1;小于返回-1;相等返回0。
- 头文件:#include<string.h>
- 函数名:char *strncmp(const char *s1, const char *s2, size_t n)
- 功能:比较s1与s2字符串n个字节的大小, 逐字符比较ASCII码,一旦比出立即返回。
- 参数: s1 字符的内存的首地址,s2 字符的内存的首地址, n 要比较的字节数。
- 返回值:大于返回1;小于返回-1;相等返回0。
2.4.5 获取字符串查找函数
- 头文件:#include<string.h>
- 函数名:char *strchr(const char *s, int c)
- 功能:在s字符串中, 查找ASCII码为c的值。
- 参数: s 字符的内存的首地址,c 要查找的ASCII码值。
- 返回值:字符地址或NULL未找到。
- 注意:首次匹配,找到第一个就返回。
- 头文件:#include<string.h>
- 函数名:char *strrchr(const char *s, int c)
- 功能:在s字符串中, 查找ASCII码为c的最后一次出现的值。
- 参数: s 字符的内存的首地址,c 要查找的ASCII码值。
- 返回值:字符地址或NULL未找到。
- 注意:末次匹配,找到最后一个就返回。
2.4.6 获取字符串匹配函数
- 头文件:#include<string.h>
- 函数名:char *strstr(const char *haystack, const char *needle)
- 功能:在haystack字符串中, 查找needle字符串。
- 参数: haystack 字符的内存的首地址,needle 要查找的ASCII码值。
- 返回值:字符地址或NULL未找到。
2.4.7 获取字符串转换函数
-
头文件:#include<stdlib.h>
-
函数名:int atoi(const char *nptr)
-
功能:在nptr字符串转为int返回。
-
参数: nptr 指向的字符串首地址。
-
返回值:整数。
-
头文件:#include<stdlib.h>
-
函数名:long atol(const char *nptr)
-
功能:在nptr字符串转为long返回。
-
参数: nptr 指向的字符串首地址。
-
返回值:整数。
-
头文件:#include<stdlib.h>
-
函数名:double atof(const char *nptr)
-
功能:在nptr字符串转为double返回。
-
参数: nptr 指向的字符串首地址。
-
返回值:浮点数。
2.4.8 获取字符串切割函数
- 头文件:#include<string.h>
- 函数名:char *strtok(char *str, const char *delim)
- 功能:将str中delim的字符串内容变为'\0'。
- 参数: str 字符的内存的首地址,delim 要切割的字符串。
- 返回值:字符地址或NULL都切割完。
- 注意:如果有重复的连续delim只将第一个置'\0';多次切割同一字符串,第一次之后传NULL可沿着后续继续切。
2.4.9 获取格式化字符串操作函数
-
头文件:#include<stdio.h>
-
函数名:int sprintf(char *buf, const char *format, ...)
-
功能:将内容按格式输出到buf。
-
参数: buf 字符缓冲的内存的首地址,format 字符串格式,... 内容。
-
返回值:执行结果。
-
头文件:#include<stdio.h>
-
函数名:int sscanf(const char *buf, const char *format, ...)
-
功能:从buf提取内容。
-
参数: buf 字符缓冲的内存的首地址,format 字符串格式,... 内容存放位置。
-
返回值:执行结果。
void test01(){
// 基础用法
char buf[20];
int a, b, c;
sprintf(buf, "%d-%d-%d", 2022, 9, 30);
printf("buf = %s\n", buf);
sscanf(buf, "%d-%d-%d", &a, &b, &c);
printf("a=%d, b=%d, c=%d\n", a, b, c);
}
void test02(){
// 进阶用法
// 1.跳过数据 %*s 或 %*d
char buf1[20];
sscanf("1234 5678", "%*d %s", buf1);
printf("%s\n", buf1);
// 2.读取指定宽度 %[width]s
char buf2[20];
sscanf("12345678", "%4s", buf2);
printf("%s\n", buf2);
// 3.支持正则表达式:只支持获取字符串
// %[a-z] a-z之间任意字符均匹配
// %[aBc] 匹配a、B、c中一员
// %[^aFC] 匹配除了aFc的所有字符
char buf3[20];
sscanf("agsesdad2314", "%[a-z]", buf3);
printf("%s\n", buf3);
}
2.4.10 const
-
修饰变量
1.1- 表示只读,一旦初始化,C中全局变量不可修改和赋值,可以被外界调用;
- 局部变量也是,但由于其存放在栈区,可以通过地址修改和赋值。
1.2
- C++中被修饰的默认数据类型的局部变量, 如果是常量赋值存放在符号表, 为开辟内存空间, 所以也无法修改和赋值, 若是变量赋值, 在栈区开辟内从空间, 可以通过地址修改和赋值;
- 被修饰的自定义数据类型的局部变量(如struct等),在栈区开辟内从空间, 可以通过地址修改和赋值。
- 修饰的全局变量是内部链接, 外界调用需要提前加 extern 关键字声明。
-
修饰指针变量类型 const int * p
- 指向内存的内容不可更改
- 但指向可以更改
-
修饰指针变量 int * const p
- 指向内存的内容可更改
- 指向不可以更改
2.5 结构体、共用体、枚举
构造类型:
不是基本数据类型的数据结构也不是指针,若干相同或不同类型的数据构成的集合。
2.5.1 结构体
- 概念
结构体是一种构造类型的数据结构,由一种或多种基本类型或构造类型的数据的集合。(C++ 还可以放函数) - 定义方法
// 1. 第一种
struct 类型名 {
成员列表
};
struct 类型名 变量名; // struct 不可省略 (C++可以省略)
// 2. 第二种
struct 类型名 {
成员列表
}变量1, 变量2;
struct 类型名 变量3; // struct 不可省略 (C++可以省略)
// 3. 第三种
struct {
成员列表
}变量1, 变量2;
// 不可定义新的
// 常用(取别名)
typedef struct stu{
int num;
char name[20];
char sex;
}STU;
STU bob;
- 结构体变量定义与初始化
- 先定义结构体类型再定义结构体变量;
- 定义变量可以顺便赋值,为结构体初始化;
- 按成员顺序初始化。
#include<stdio.h>
struct stu{
int id;
int age;
}zhangsan = {1002, 20};
int main(int argc, char*argv[]){
struct stu wangwu;
struct stu lisi = {1001, 21};
return 0;
}
- 结构体变量使用
变量.成员
#include<stdio.h>
#include<string.h>
struct stu{
int id;
int age;
char name[20];
}zhangsan = {1002, 20, "zs"};
int main(int argc, char*argv[]){
struct stu lisi = {1001, 21};
zhangsan.id = 1003;
zhangsan.age = 22;
// 指针
strcpy(zhangsan.name, "zhangsan");
printf("%d - %s - %d", zhangsan.id, zhangsan.name, zhangsan.age);
return 0;
}
结构体嵌套
#include<stdio.h>
#include<string.h>
typedef struct{
int year;
int month;
int day;
}BD;
typedef struct{
int id;
char name[20];
BD birthday;
}STU;
int main(int argc, char*argv[]){
STU mike;
mike.id = 101;
strcpy(mike.name, "Mike");
mike.birthday.year = 2000;
mike.birthday.month = 2;
mike.birthday.day = 10;
printf("%d - %s - %d:%d:%d\n", mike.id, mike.name, mike.birthday.year, mike.birthday.month, mike.birthday.day);
STU jane = {102, "Jane", {2000, 2, 11}};
printf("%d - %s - %d:%d:%d\n", jane.id, jane.name, jane.birthday.year, jane.birthday.month, jane.birthday.day);
return 0;
}
相同类型的结构体变量可以相互赋值
#include<stdio.h>
#include<string.h>
typedef struct{
int year;
int month;
int day;
}BD;
typedef struct{
int id;
char name[20];
BD birthday;
}STU;
int main(int argc, char*argv[]){
STU mike;
mike.id = 101;
strcpy(mike.name, "Mike");
mike.birthday.year = 2000;
mike.birthday.month = 2;
mike.birthday.day = 10;
printf("%d - %s - %d:%d:%d\n", mike.id, mike.name, mike.birthday.year, mike.birthday.month, mike.birthday.day);
STU jane = mike;
strcpy(jane.name, "Jane");
printf("%d - %s - %d:%d:%d\n", jane.id, jane.name, jane.birthday.year, jane.birthday.month, jane.birthday.day);
return 0;
}
- 结构体数组
- 定义方法
struct 结构体类型名 数组名[元素个数]; - 数组元素的引用
数组名[下标] - 数组元素的使用
数组名[下标].成员变量
- 结构体指针
指向结构体的地址。
- 定义方法
struct 结构体类型名 *结构体指针变量名; - 指针对成员变量的引用
(*结构体指针变量名).成员变量 或 结构体指针变量名->成员变量
- 结构体内存分配
- 内存大小是所有成员变量大小之和。
以内存占用最大的基本类型为单位开辟内存。
以定义的结构体成员的顺序存储。 - 字节对齐,即内存编号是最大字节的倍数。
空间换时间,提高CPU读取数据的效率。
可用 #pragma pack(value) 改变默认对其原则, value = 1, 2, 4, 8等;使用指定时,取与最大数据类型相比下两者间的较小值。
- 位段
以位为单位的成员,称为位段(位域)。
位段成员无法取地址。
#include<stdio.h>
struct packed_data{
unsigned int a:2;
unsigned int b:6;
unsigned int c:4;
unsigned int d:4;
unsigned int i;
}data;
这也要求赋值不要超过位段定义的范围。
位段成员的类型必须为指定整型或字符型。
2.5.2 共用体
- 概念
类似结构体,几种不同类型变量共同占用一段内存的结构,则地址也是同一地址。
其大小是占内存长度最大的成员的大小。 - 定义方法
// 1. 第一种
union 类型名 {
成员列表
};
union 类型名 变量名; // struct 不可省略
// 2. 第二种
union 类型名 {
成员列表
}变量1, 变量2;
union 类型名 变量3; // struct 不可省略
// 3. 第三种
union {
成员列表
}变量1, 变量2;
// 不可定义新的
// 常用(取别名)
typedef union stu{
int num;
char name[20];
char sex;
}STU;
STU bob;
- 共同体变量定义与初始化
- 先定义类型再定义变量;
- 定义变量可以顺便赋值,为初始化;
- 每次起作用的成员是最后一次存放的成员,其余会被覆盖。
#include<stdio.h>
union stu{
int id;
int age;
}zhangsan = {1002};
int main(int argc, char*argv[]){
union stu wangwu;
union stu lisi = {1001};
return 0;
}
2.5.3 枚举
- 概念
变量的值只限于列举出来的值的范围。 - 定义方法
enum 枚举类型名 {
枚举值列表;
};
enum 类型名 变量名; // 只能取枚举里的一个
值为常量,不可再赋值;定义时不赋值,默认从0开始向后数;定义时赋值,从开始值递增。
3. 枚举变量定义与初始化
- 先定义类型再定义变量;
- 定义变量可以顺便赋值,为初始化。
#include<stdio.h>
enum week{
mon, tue, wed, thu, fri, sat, sun
};
int main(int argc, char*argv[]){
enum week day = mon;
return 0;
}

浙公网安备 33010602011771号