嵌入式之C语言基础

1. C语言基础

1.1 关键字和保留标识符

关键字就是C语言已经定义好的专用字,只用不改。(类似于拼音字母的含义)

image

1.2 常量 constant

在程序使用前定义好了,运行过程没有变化的量。

1.3 变量 variable

在程序运行前或过程中被定义,并被改变或赋值的量。

1.4 数据类型

1.4.1 数据类型关键词

数据类型使用关键字表示。

image

按计算机的储存方式可分为两大基本类型:整数类型和浮点数类型。

  • 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);

转义字符

image

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 格式化输入输出

image

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

  1. #include<...> 系统指定路径下找头文件
  2. #include"..." 先在当前目录找,找不到再去系统指定路径找
  • 预处理仅对include等预处理操作进行处理,并不会进行语法检查,编译时才会语法检查,报语法错误。

2.1.3 define

  • define 宏定义,在预处理时进行替换。
  • 则修改时方便,其他地方预处理时自动替换。(注意:后面不要加分号“;”)
  • 作用范围在 本文件的末尾或者在 #undef 处中止。
  1. 不带参
    例子:#define PI 3.14 用PI代替3.14
  2. 带参
    例子:
    #define S(a, b) a*b
    S(2, 4) // 2*4=8
    S(2+8, 4) // 2+8*4=32
    类似函数,但没有数据类型,预处理时 实参代替字符串形参而其他字符保留。
  • 带参宏与带参函数的区别:
    宏 -> 浪费空间节约时间,即每调用一次展开一次,不压栈弹栈;
    函数 -> 浪费时间节约空间,即代码一份,调用时取指令, 压栈弹栈。

2.1.4 选择性编译

  1. 例子:
#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 内存理解

存储器:存储数据器件

  1. 外存
    外部存储器, 长期存放数据,掉电不丢失数据。
    常见设备:硬盘、flash、rom、U盘、光盘、磁带。
  2. 内存
    内部存储器, 暂时存储数据, 掉电丢失数据。
    常见设备:ram、DDR。
  • 物理内存:现实世界内存条。

  • 虚拟内存:操作系统中的概念, 进程被创建或者程序运行时分配虚拟内存, 虚拟内存和物理内存存在映射关系。

  • 运行程序时,操作系统对虚拟内存分区:


    • 动态申请内存会在此处开辟

    • 存放局部变量
    • 静态全局区
      • 未初始化静态全局区
        存放未初始化的静态变量或全局变量
      • 初始化的静态全局区
        存放初始化的静态变量或全局变量
    • 代码区
      存放程序代码
    • 文字常量区
      存放常量
  • 内存以字节为单元存储数据,可以将程序的虚拟寻址空间,看作一个很大的一维字符数组。

2.2.2 指针的相关概念

简单理解,指针就是地址。
指针变量用来存放地址编号,32位平台 占 4个字节。
相应类型的指针变量,只存放相应类型的变量的地址。

2.2.3 指针的定义方法

  1. 简单指针
// 数据类型 * 指针变量名
// 定义一个 名为 p 的 整型指针
int* p;
  1. 指针的运算符
    & 取地址、 *取值
    *取值跟指针的类型有关,即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);

一行定义多个指针变量,需要在每个变量前都加*。

  1. 指针大小
    在32位平台,均为4字节。
int * p;
char * q;
printf("size %d\n", sizeof(p))
printf("size %d\n", sizeof(q))
// 其他类型类似

2.2.4 指针的分类

按数据类型分

  1. 字符 char
  2. 短整型 short
  3. 整型 int
  4. 长整型 long
  5. 浮点型 float 、 double
  6. 函数指针
  7. 结构体指针
  8. 指针的指针
  9. 数组指针

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 等价。
  • 不同点:
    1. a是常量、p是变量,p可以赋值,a不能;
    2. 取地址结果不同,p取地址为指针的指针,a取地址为数组指针。

2.2.9 指针与函数

值传递:不改变原变量
地址传递:可改变原变量

  1. 指针函数
    本质是函数,只不过返回值是一个指针。
  2. 函数指针
    保存函数的指针,函数的名字就是函数的首地址,即函数的入口地址。
    返回值类型(*函数指针变量名)(形参列表)

例子:

#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;
}
  1. 函数指针数组
    本质是数组,元素是函数指针。
    返回值类型(*函数指针变量名[数组元素个数])(形参列表)
int (*p[10])(int, int);

2.2.10 特数指针

  1. 通用指针 void* (有泛型的意思)
  2. 空指针 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 静态与动态

  1. 静态
  • 编译或运行过程中,按事先规定的大小分配内存空间;
  • 须事先指导所用空间大小;
  • 分配在栈区或者全局变量区;
  • 按计划分配。
  1. 动态
  • 运行过程中,按需分配内存空间;
  • 无需事先指导所用空间大小;
  • 分配在堆区或者全局变量区。

2.3.2 动态分配函数

  1. malloc函数
  • 头文件:#include<stdlib.h>
  • 函数名:void *malloc(unsigned int size)
  • 功能:在堆区开辟指定长度的空间
  • 参数: size 开辟空间的大小
  • 返回值:成功:开辟好的空间首地址;失败:NULL。
  • 注意:多次申请空间不一定连续;返回值为void* 所以要强制类型转换,告知类型。
  1. free函数
  • 头文件:#include<stdlib.h>
  • 函数名:void free(void *ptr)
  • 功能:释放ptr指向的内存
  • 参数: 要释放的地址的 所用的指针变量
  • 注意:ptr指向内存必须是 动态申请的内存;free后释放内存,则ptr成为野指针,一般会对其赋为NULL;一个内存只能free一次。
  1. 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。
  1. 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.1

    • 表示只读,一旦初始化,C中全局变量不可修改和赋值,可以被外界调用;
    • 局部变量也是,但由于其存放在栈区,可以通过地址修改和赋值。

    1.2

    • C++中被修饰的默认数据类型的局部变量, 如果是常量赋值存放在符号表, 为开辟内存空间, 所以也无法修改和赋值, 若是变量赋值, 在栈区开辟内从空间, 可以通过地址修改和赋值;
    • 被修饰的自定义数据类型的局部变量(如struct等),在栈区开辟内从空间, 可以通过地址修改和赋值。
    • 修饰的全局变量是内部链接, 外界调用需要提前加 extern 关键字声明。
  2. 修饰指针变量类型 const int * p

    • 指向内存的内容不可更改
    • 但指向可以更改
  3. 修饰指针变量 int * const p

    • 指向内存的内容可更改
    • 指向不可以更改

2.5 结构体、共用体、枚举

构造类型:
不是基本数据类型的数据结构也不是指针,若干相同或不同类型的数据构成的集合。

2.5.1 结构体

  1. 概念
    结构体是一种构造类型的数据结构,由一种或多种基本类型或构造类型的数据的集合。(C++ 还可以放函数)
  2. 定义方法
// 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;
  1. 结构体变量定义与初始化
  • 先定义结构体类型再定义结构体变量;
  • 定义变量可以顺便赋值,为结构体初始化;
  • 按成员顺序初始化。
#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;
}
  1. 结构体变量使用
    变量.成员
#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;
}
  1. 结构体数组
  • 定义方法
    struct 结构体类型名 数组名[元素个数];
  • 数组元素的引用
    数组名[下标]
  • 数组元素的使用
    数组名[下标].成员变量
  1. 结构体指针
    指向结构体的地址。
  • 定义方法
    struct 结构体类型名 *结构体指针变量名;
  • 指针对成员变量的引用
    (*结构体指针变量名).成员变量 或 结构体指针变量名->成员变量
  1. 结构体内存分配
  • 内存大小是所有成员变量大小之和。
    以内存占用最大的基本类型为单位开辟内存。
    以定义的结构体成员的顺序存储。
  • 字节对齐,即内存编号是最大字节的倍数。
    空间换时间,提高CPU读取数据的效率。
    可用 #pragma pack(value) 改变默认对其原则, value = 1, 2, 4, 8等;使用指定时,取与最大数据类型相比下两者间的较小值。
  1. 位段
    以位为单位的成员,称为位段(位域)。
    位段成员无法取地址。
#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. 概念
    类似结构体,几种不同类型变量共同占用一段内存的结构,则地址也是同一地址。
    其大小是占内存长度最大的成员的大小。
  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;
  1. 共同体变量定义与初始化
  • 先定义类型再定义变量;
  • 定义变量可以顺便赋值,为初始化;
  • 每次起作用的成员是最后一次存放的成员,其余会被覆盖。
#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 枚举

  1. 概念
    变量的值只限于列举出来的值的范围。
  2. 定义方法
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;
}

所有记录:游戏开发:C++学习

posted @ 2022-10-08 11:05  bok_tech  阅读(161)  评论(0)    收藏  举报