- 联合体也是一种构造数据类型
- 联合体和结构体一样可以放置多种数据类型
- 联合体是把变量存放到同一段存储单元中。也就是使用覆盖技术,几个变量互相覆盖
- 几个不同的变量共同占用一段内存的结构,被称作共用体类型结构,简称共用体,也叫联合体
注意:联合体公用一个地址,所以数据类型不能带有构造函数的抽象数据
声明: 在联合体中也可以分配权限:格式:
union 联合体名
{
数据类型 标识符,
...
};
默认为共有类型
union perosn
{
private://私有类型
int age;
protected://保护类型
string name;
int height;
};
定义:
- 先声明后定义
- 在声明中定义(保留联合体名)
- 在声明中定义(删除联合体名)
第一种:先声明后定义
union person
{
int age;
char* name;
int height;
};
union person p1,p2,p3;
第二种:在声明中定义(保留联合体名)
union person
{
int age;
char* name;
int height;
}p1, p2, p3;
第三种:在声明中定义(删除联合体名)
union
{
int age;
char* name;
int height;
}p1, p2, p3;
联合体的初始化:
因为共用一个地址:所以初始化只能初始化一个数据,初始化用大括号
union person
{
int age;
char* name;
int height;
};
person p1 = {20};
联合体的赋值:
直接用 . 访问数据成员
person p1; p1.age = 20; p1.height = 30;
注意事项:共用一个地址,数据也是公用
union person
{
int age;
char* name;
int height;
};
person p1;
p1.age = 20;
p1.height = 30;
cout << p1.age << endl; //结果为30
cout << p1.height << endl;//结果为30
联合体内存占用:
联合体把变量存放到同一段存储单元
- 占用内存为:满足最大成员内存
- 成员的地址相同
空联合体内存为:1
联合体也存在字节对齐
union person
{
int age;
char* name;
double height;
};
person p1;
//内存为成员的最大内存
cout << sizeof(p1) << endl; //double 8个字节
union person
{
int age;
int a[10];
double height;
};
person p1;
//内存为成员的最大内存
cout << sizeof(p1) << endl; //40个字节
union person
{
int a[3];
double height;
};
person p1;
//内存为成员的最大内存
cout << sizeof(p1) << endl; //16个字节 整除与最大数据类型的最小内存
return 0;
联合体可以和结构体相互嵌套:
union person
{
int age;
struct person1
{
int a;
double b;
};
double height;
};
注意事项:union中只能有一个数据成员可以有值。用printf和cout输出其他不同类型的成员时,不会发生类型转换
- 相同类型可以输出
- 不同类型不能输出
union P
{
int a;
char b;
double c;
int d;
}p;
int main()
{
p.a = 4;
cout << p.a << endl;//结果为4
cout << p.b<< endl;
cout << p.c << endl;
cout << p.d << endl;//结果为4
return 0;
}
详细请看:大小端模式_百度百科 (baidu.com)
大小端模式:
大端模式,是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中 (低地址存高位)
小端模式,是指数据的高字节保存在内存的高地址中, 而数据的低字节保存在内存的低地址中,(低地址存低位)
编译器如何用union判断大小端:0000430: e684 6c4e 0100 1800 53ef 0100 0100 0000
在大端模式下,前32位应该这样读: e6 84 6c 4e ( 假设int占4个字节)
在小端模式下,前32位应该这样读: 4e 6c 84 e6 ( 假设int占4个字节)
union {
int i;
char c;
} u;
u.i = 1;
if(u.c == 1)
cout << "小端" << endl;
else
cout << "大端" << endl;
}
来个例题:
题目来自:unsigned int a= 0x1234; unsign_腾讯笔试题_牛客网 (nowcoder.com)
结构体:(C)第一步先完全表示地址:
32位,补齐后的地址为:0x00001234
因为 &a 且 char类型,取地址是从低位开始取
大端模式:低地址存储高位 ,取一个字节的地址 ,所以左边开始取 0x00
小端模式:低地址存储低位, 取一个字节的地址, 所以从右开始取 0x34
- 结构体是由一批数据组合而成的结构型数据
- 组成结构型数据的每个数据称为结构型数据的“成员”
结构体的声明:struct 结构体名
{
数据成员;
};
struct person
{
int a;
char b;
double c;
};
结构体的定义 :
- 先声明后定义
- 在声明中定义(保留结构体名)
- 在声明中定义(删除结构体名)
1.先声明后定义
struct person
{
int a;
char b;
double c;
};
person p1,p2;
2.在声明中定义(保留联合体名)
struct person
{
int a;
char b;
double c;
}p1, p2;
3.在声明中定义(删除联合体名)
struct
{
int a;
char b;
double c;
}p1, p2;
结构体初始化:
结构体的初始化用{}
初始化规则:
- 给出的数据不足时,按照从上往下初始化
- 初始化可以用=
- 不能只给出后面的数据
struct (结构体名可有可无)
{
int a;
char b;
double c;
}p1 = { 12,11,13 }, p2 = { 11,12 }, p3 = p1, p4 = {,20,20};
//p1正确,全部都初始化
//p2正确,初始化了 a 和 b
//p3正确,全部都初始化
//p4错误,必须按照成员的顺序初始化,不能跳过成员
struct person
{
int a;
char b;
double c;
};
person p1 = { 12,11,13 };//正确
person p2 = { 11,12 };//正确
person p3 = p1;//正确
person p4 = { ,20,20 };//错误
结构体的赋值:
- 普通结构体用 .
- 指针结构体用 ->
//普通结构体
struct person
{
int a;
char b;
double c;
};
person p1;
p1.a = 10;
p1.b = 20;
p1.c = 30;
//指针
struct person
{
int a;
char b;
double c;
};
person *p1;
p1->a = 10;
p1->b = 20;
p1->c = 30;
注意事项:结构类型无法将自己的类型作为其成员的类型,因为自己的类型定义尚不完整,要在结束的大括号(})后才算定义完整,然而,结构类型可以包含指向自己类型的指针。
struct p
{
struct p p1;//报错 需要完整的数据类型
struct p *p2;//可以 结构体自引用
};
重点结构体内存计算:
结构体内存计算的方式为内存对齐
改链接右详细解释
struct所占的内存_旷工锁的博客-CSDN博客_struct 内存大小
结构体和联合体的区别:位域: 是一种特殊的结构体,他不会完全使用数据类型的全部字节,指定位来存储
- 结构体内存占用和联合体不同,联合体只存储最大的成员的内存,结构体把所有成员都进行存储,联合体更省内存
- 联合体公用地址,结构体不共用地址
格式:位域的定义类型:
- 只能为 int(整型)
- unsigned int(无符号整型)
- signed int(有符号整型)
声明:struct 位域名
{
位域列表;
};
位域列表: 数据类型 数据名 : 位的数量
注意:不能超过 数据类型的最大位 int(32位)因为int 型 占 4个字节 32位 ,所以 位数不能超过32
struct P
{
int a : 1;
unsigned int b : 1;
signed int c : 1;
};
定义:
- 先声明后定义
- 在声明中定义(保留结构体名)
- 在声明中定义(删除结构体名)
1.先声明后定义
struct P
{
int a : 1;
unsigned int b : 1;
signed int c : 1;
};
P p1, p2, p3;
2.在声明中定义(保留结构体名)
struct P
{
int a : 1;
unsigned int b : 1;
signed int c : 1;
} p1, p2, p3;
3.在声明中定义(删除结构体名)
struct
{
int a : 1;
unsigned int b : 1;
signed int c : 1;
} p1, p2, p3;
初始化:和结构体的初始化相同
赋值:
位域的取值:
- 普通用 .
- 指针用 ->
先把数转化为二进制数表示,然后参数为几位就取几位,超出的直接截去
struct
{
unsigned int b : 3;
unsigned int c : 3;
} p1;
p1.b = 0x124;
printf("%#xn", p1.b);//结果为0x4
//0x124 二进制 表示为 0001 0010 0100 从后往前取3位为 100
//十六进制表示为0x4
无名位域:
- 无名位域无法使用
- 无名位域成员没有名称,只给出数据类型和位宽
struct
{
unsigned int b : 3;
unsigned int : 3;
unsigned int c : 3;
} p1;
位域的内存计算:
- 成员的位域和小于32位时,占4个字节(会字节对齐)
- 成员之间连续存储
- 但空位域会截断存储
- 总结 位域和%32 *4+位域/32*4
struct
{
unsigned int b :1;
unsigned int c : 2;
} p1;
cout << sizeof(p1) << endl;//结果为4
struct
{
unsigned int b :10;
unsigned int c :30;
} p1;
cout << sizeof(p1) << endl;//结果为8 因为 位和大于32 小于64 向上取整
struct
{
unsigned int b :1;
unsigned int : 30;
unsigned int c : 2;
} p1;
cout << sizeof(p1) << endl;//结果为8 但 b c 分开存储
C中的输入输出:
输入:
scanf(限制格式,&变量)函数:从键盘上输入,标准格式常用的格式符:
- 输入格式要和控制字符串的格式一致
- 变量前一定要加& (取地址)
- scanf 会根据地址把读取到的数据写入内存。
| 格式 | 介绍 |
| %c | 读取一个字符 |
| %d | 读取一个十进制整数 short型 (%hd)long型(%ld) |
| %o | 读取一个八进制整数 short型 (%ho)long型(%lo) |
| %x | 读取一个十六进制整数 short型 (%hx)long型(%lx) |
| %s | 读取字符串 |
| %f | 读取浮点数(float) |
| %lf | 读取浮点数(double) |
1.格式控制之间相连
int a;
int b;
scanf("%d%d", &a, &b);//输入时可以使用空格(可以多个)或回车隔开
2.格式控制之间用一个空格隔开
int a;
int b;
scanf("%d %d", &a, &b);//输入时可以使用空格(可以多个)或回车隔开
3.格式控制之间用其他符号隔开
int a;
int b;
scanf("%d,%d", &a, &b);//输入时只能用该字符隔开(有多少个隔多少个)(强制性)
在输入字符串时:
注意:在vs高版本中 把scanf 改为 scanf_s(,,缓冲值)
1.输入一个字符串
char a[20];
scanf_s("%s", a,sizeof(a)/sizeof(a[0]));//要输入缓冲值(包括' ')
printf("%sn", a);
2.输入两个字符串,且用空格隔开
char a[20];
char b[20];
scanf_s("%s %s", a,20,b,20);
printf("%sn", a);
printf("%sn", b);
3.输入两个字符串,用其他符号隔开
char a[20];
char b[20];
scanf_s("%[^,],%s", a,20,b,20);
printf("%sn", a);
printf("%sn", b);
//因为,是字符所以需要使用正则表达式 来特殊化,使字符串a读到,就停止
//[^,] 不包含,的字符串
MySQL正则表达式_旷工锁的博客-CSDN博客
字符输入:getchar():键盘获取一个字符
char a;
a = getchar();
printf("%cn", a);
C++14弃用的字符输入:安全性差
字符串输入:头文件#include
(仅用在Window系统中) getche(): 读取一个字符立刻获取,没有缓冲区
getch():读取一个字符立刻获取,没有缓冲区,输入时显示输入的内容
gets()和scanf()的区别:gets():字符串输入函数
- gets()专用于字符串,scanf()可以输入多种
- gets()只能用回车结束,可以读取空格
- scanf()可以由空格和回车停止读取,不能读取空格
因为gets()可以无限读取,易造内存溢出,在c++11中 gets()被弃用,
用gets_s()替代
| get_s(char* _Buffer,rsize_t _Size) |
char a[20];
gets_s(a,20);
printf("%sn", a);
输出:
printf():标准输出
格式控制符:
| %c | 字符输出 |
| %d | 十进制整数输出 short型(%hd)long 型(%ld) |
| %u | 十进制无符号整数输出 short型(%hu)long 型(%lu) |
| %o | 以八进制整数输出 short型(%hu)long 型(%lu) |
| %#o | 以八进制带前缀整数输出 short型(%hu)long 型(%lu) |
| %x | 以十六进制整数输出 short型(%hx)long 型(%lx) |
| %#x | 以十六进制整数输出 short型(%hx)long 型(%lx) |
| %X | 以十六进制整数输出(大写) short型(%hx)long 型(%lx) |
| %#X | 以十六进制整数输出 (大写) short型(%hx)long 型(%lx) |
| %f | 十进制浮点数(float) |
| %lf | 十进制浮点数(double) |
| %s | 字符串 |
- 左对齐:% -[长度][输出类型]
- 右对齐:% +[长度][输出类型] (+号可以省略)
- 如果输出的字符比控制的长度长,那么控制的长度失效
长度不够用空格补齐
int a = 200;
for (int i = 0; i < 5; i++)
{
for (int j = 0; j < 5; j++)
{
printf("%-9d", a);//左对齐
//printf("%9d",a);//右对齐
}
printf("n");
}
左对齐:
右对齐: 浮点数确定精度:
在输出格式前加 .小数点后的位数
double a = 200;
printf("%.5lf", a);//输出200.00000
double a = 200;
printf("%10.5lf", a);//也可以加入对齐
也可以用于整数和字符串
- 整数使用:表示最小输出宽度,整数的宽度不足时会在左边补 0 (优先级比对齐高)
- 字符串使用:最大输出宽度,会截掉多余的字符 (优先级比对齐高)
int a = 200;
printf("%.5dn", a);//输出00200
printf("%4.5d", a);//输出00200
char a[] = "123456789";
printf("%.5sn", a);//输出12345
printf("%4.5s", a); //输出12345
printf还可以返回字符数:
int printf(const char *format)
- format 为字符串
- 只会计算到' '的前一位
- 会包含空格
#includeusing namespace std; int main() { int p = printf("ABCDE"); //不包含 int p1 = printf("A BCDE"); //包含空格 int p2 = printf("ABCDEn");//包含其他转义字符 int p3 = printf("ABC DE");//只计算到 printf("n"); printf("x=%dn", p); printf("x=%dn", p1); printf("x=%dn", p2); printf("x=%dn", p3); return 0; }
字符输出:
- putchar():输出单个字符
- puts():输出字符串,会自动回车
//输出字符
char a;
a=getchar();
putchar(a);
//输出字符串
char p[10];
gets_s(p, 10);
puts(p);
C语言文件:
文件的作用:保存数据
C语言通过文件流来读写文件,相当于打开文件就是打开了一个流
数据流:data stream
输入流:input stream
输出流:output stream
在C语言中 文件的数据存放在内置的一个结构体中
结构体为 FILE,操作文件时需要先创建一个FILE的指针来存放数据
文件的操作:
- FILE* fopen("文件地址",打开方式)(旧版)
- errno_t fopen_s(&文件指针,文件地址,打开方式)(新版更安全)
- int fclose(FILE *p)正常关闭返回0
- fopen()文件未打开,返回NULL(0)
- fopen_s() 文件未打开,返回 !=0 0代表打开成功
fopen_s函数声明如下: errno_t fopen_s( FILE** pFile, const char *filename, const char *mode }打开方式:
- 默认为普通文件 "t"
- 使用二进制文件的话需要添加模式后面添加 b
rb , wb ,ab ,rb+ 或 r+b ,wb+ 或 w+b ,ab+ 或 a+b
| 模式 | 描述 |
|---|---|
| r | 打开一个已有的文本文件,允许读取文件。文件必须存在,否则打开失败 |
| w | 打开一个文本文件,允许写入文件。如果文件不存在,则会创建一个新文件。在这里,您的程序会从文件的开头写入内容。如果文件存在,则该会被截断为零长度,重新写入。 |
| a | 打开一个文本文件,以追加模式写入文件。如果文件不存在,则会创建一个新文件。在这里,您的程序会在已有的文件内容中追加内容。 |
| r+ | 打开一个文本文件,允许读写文件。文件必须存在,否则打开失败 |
| w+ | 打开一个文本文件,允许读写文件。如果文件已存在,则文件会被截断为零长度,如果文件不存在,则会创建一个新文件。 |
| a+ | 打开一个文本文件,允许读写文件。如果文件不存在,则会创建一个新文件。读取会从文件的开头开始,写入则只能是追加模式。 |
- / 表示 "E:/图片/apic26488.jpg"
- \表示 "E:\图片\apic26488.jpg"
FILE *p;
if ((p = fopen("E:\图片\apic26488.jpg", "w+")) == NULL)//打开文件以读写的方式
{
printf("打开失败n");
}
fclose(p);//关闭文件
以下参考了 某大神博主的文章链接如下:详解请点击下面链接
文本文件和二进制文件的差异和区别_随心1993的博客-CSDN博客_二进制文件和文本文件的区别
文件和二进制文件的区别:| 文本文件 | 二进制文件 |
| 文本文件是基于字符编码的文件(char) | 二进制文件是基于值编码的文件(可存储多种类型)(char int short long float 等 ) |
| 文本文件每条数据通常是固定长度的(译码容易) | 可支持类型有多种,长度不固定(译码较难) |
| 文本文件编辑器就可以读写 | 二进制文件需要解码器 |
| 文本文件是把数据的终端形式的二进制数据输出到磁盘上存放 | 二进制文件是把内存中的数据按其在内存中的存储形式原样输出到磁盘上存放 |
字符读写:文件的读取与操作系统相关:在window中
- C语言文本方式写时,会把(' ')转化成('rn')然后写入到文件中,文本读取时把('rn')转化成(' ')然后再输入到缓冲区
- 二进制文件直接把数据 放到缓冲区
二进制文件不需要转换,效率更高。
- 当写缓冲区中无换行符'n'(0AH),文本写与二进制写的结果是一样的
- 当文件中不存在'rn'(0DH0AH)时,文本读与二进制读的结果一样.
| 函数名 | 作用 |
| int fgetc(FILE*p) | 读取一个字符 |
| int fgets(char*ch,int size,FILE*p) | 读取文件中一个字符串,并保存到字符数组 |
| size_t fread(void *ptr,size_t size,size_t count,FILE *p) | 从指定文件中读取块数据(多行数据) |
| int fputc(int ch,FILE*p) | 向指定的文件中写入一个字符(可以为char类型) |
| int fputs(char *ch,FILE *p) | 把字符串存放到文件中 |
| size_t fwrite (void *ptr,size_t size,size_t count,FILE *p) | 函数用来向文件中写入块数据 |
int fgetc(FILE*p):从指定的文件中读取一个字符fgetc() 读取成功时返回读取到的字符,读取到文件末尾或读取失败时返回EOF
EOF通常为 -1,但也与编译器有关
char ch;
if ((ch = fgetc(p)) != EOF)
{
putchar(ch);//一个个输出文件中的字符
}
- int feof(FILE*p):判断文件内部指针是否指向文件末尾,指向文件末尾时返回非零值,否则返回零值
- int ferror(FILE*p):判断文件操作是否出错,出错时返回非零值,否则返回零值
int fputc(int ch,FILE*p):向指定的文件中写入一个字符(可以为char类型)fputc() 写入成功时返回写入的字符,失败时返回 EOF
//通过循环写入数据
char a[10] = "zxcvbnm";
char b;
for (auto p1 : a)
{
if (p1 != ' ')
{
fputc(p1, p);
}
}
//通过键盘写入一个字符
char t;
while ((t = getchar()) != 'n')
{
fputc(t, p);
}
字符串读写:
int fgets(char*ch,int size,FILE*p):读取文件中一个字符串,并保存到字符数组,size应该小于ch的大小,要保存' 'fgets() 遇到换行时,会将换行符一并读取到当前字符串
读取失败时返回 NULL
文件内部指针已经指向了文件末尾,也返回 NULL
char a[30];
fgets(a, 9, p);//读取9个字符给 字符数组
char b[30];
while (fgets(b, 29, p) != NULL)
{
printf("%s", b);//一行一行输出直到输出了29个字符
}
int fputs(char *ch,FILE *p):把字符串存放到文件中写入成功返回非负数,失败返回 EOF
char b[30]="zxcvnmasdfggh"; fputs(b, p);数据块(多行)的读写:
- size_t fread(void *ptr,size_t size,size_t count,FILE *p):从指定文件中读取块数据(多行数据)
- size_t fwrite (void *ptr,size_t size,size_t count,FILE *p) fwrite() 函数用来向文件中写入块数据
- ptr: 代表容器指针 用来存放数据
- size:每个数据块的字节数
- count:数据块的个数
- p:文件指针
应该以二进制的形式打开文件(windows)
int main()
{
FILE *p;
errno_t err;
int p1[20];
int p2[20];
if ((err = fopen_s(&p,"d:\vs2015\consoleapplication3\consoleapplication3\ppp.txt", "wb+")) != 0)//打开文件以追加的方式
{
printf("打开失败n");
exit(0);
}
for (int i = 0; i < 20; i++)
{
p1[i] = i + 1;
}
fwrite(p1, 4, 20, p);//把数据写入
rewind(p);//把文件指针重新定位到开头
fread(p2, 4, 20, p);//读取数据
for (auto &i : p2)
{
cout << i << endl;
}
fclose(p);//关闭文件
return 0;
}
标准读写:
- int fprintf(FILE*p,"格式控制",变量) 写入数据
- int fscanf(FILE*p,"格式控制",变量) 读取数据
for (int i = 0; i < 3; i++)
{
fprintf(p, "%d %s %d %lf", p1[i].id, p1[i].name, p1[i].number, p1[i].height);//写入文件
}
rewind(p);//把文件指针重新定位到开头
for (int i = 0; i < 3; i++)
{
fscanf_s(p, "%d %s %d %lf", &(p1[i].id), &(p1[i].name), &(p1[i].number), &(p1[i].height));//读取文件
}
文件定位函数:
| 函数名 | 作用 |
| void rewind(FILE*p) | 将位置指针移动到文件开头 |
| void fseek(FILE*p,long p1,int p2) | 将位置指针移动到任意位置 |
- long p1 代表偏移量 (字节数)
- int p2 代表起始位置
- fseek()一般用于二进制文件
| 关键词 | 作用 |
| SEEK_SET (0) | 文件开头 |
| SEEK_CUR(1) | 当前位置 |
| SEEK_END(2) | 文件结尾 |
fseek(fp,100L,0);把fp指针移动到离文件开头100字节处;
fseek(fp,100L,1);把fp指针移动到离文件当前位置100字节处;
fseek(fp,100L,2);把fp指针退回到离文件结尾100字节处。
fwrite(p1, sizeof(struct stu), 10, fp); //写入10条学生信息
fseek(fp, sizeof(struct stu), SEEK_SET); //移动位置指针(移到第二位)
fread(&boy, sizeof(struct stu), 1, fp); //读取一条学生信息(第二位的数据)



