十一 共用体(联合体)
- 联合union是一个能在同一个存储空间存储不同类型数据的类型;
- 联合体所占的内存长度等于其最长成员的长度倍数,也有叫做共用体;
- 同一内存段可以用来存放几种不同类型的成员,但每一瞬时只有一种起作用;
- 共用体变量中起作用的成员是最后一次存放的成员,在存入一个新的成员后原有的成员的值会被覆盖;
- 共用体变量的地址和它的各成员的地址都是同一地址。
11.1 共用体的定义和使用
#include<stdio.h>
// 定义一个共用体
union test
{
int a;
short b;
float c;
double d; //8个字节
char e;
};
// 共用体是最后一次赋值的数据是有效的
int main()
{
// 创建共用体变量
union test var;
var.a=100;
printf("%d\n",var.a);
var.c=3.14;
printf("%f\n",var.c);
return 0;
}
十二 链表(数据结构)
通过结构体构成链表,单向链表的建立,结点数据的输出、删除与插入
- 链表是一种常用的数据结构,它通过指针将一些列数据结点,连接成一个数据链。相对于数组,链表具有更好的动态性(非顺序存储)链式存储。
- 数据域用来存储数据,指针域用于建立与下一个结点的联系。
- 建立链表时无需预先知道数据总量的,可以随机的分配空间,可以高效的在链表中的任意位置实时插入或删除数据。
- 链表的开销,主要是访问顺序性和组织链的空间损失。
数组和链表的区别
数组:一次性分配一块连续的存储区域。
优点:随机访问元素效率高
缺点:1) 需要分配一块连续的存储区域(很大区域,有可能分配失败)
2) 删除和插入某个元素效率低
链表:无需一次性分配一块连续的存储区域,只需分配n块节点存储区域,通过指针建立关系。
优点:1) 不需要一块连续的存储区域
2) 删除和插入某个元素效率高
数组:一次性分配一块连续的存储区域。
优点:随机访问元素效率高
缺点:1) 需要分配一块连续的存储区域(很大区域,有可能分配失败)
2) 删除和插入某个元素效率低
链表:无需一次性分配一块连续的存储区域,只需分配n块节点存储区域,通过指针建立关系。
优点:1) 不需要一块连续的存储区域
2) 删除和插入某个元素效率高
数组:一次性分配一块连续的存储区域。
优点:随机访问元素效率高
缺点:1) 需要分配一块连续的存储区域(很大区域,有可能分配失败)
2) 删除和插入某个元素效率低
链表:无需一次性分配一块连续的存储区域,只需分配n块节点存储区域,通过指针建立关系。
优点:1) 不需要一块连续的存储区域
2) 删除和插入某个元素效率高
缺点:随机访问元素效率低
十三 位运算
13.1 位运算符的含义和使用
可以使用C对变量中的个别位进行操作。您可能对人们想这样做的原因感到奇怪。这种能力有时确实是必须的,或者至少是有用的。C提供位的逻辑运算符和移位运算符。在以下例子中,我们将使用二进制计数法写出值,以便您可以了解对位发生的操作。在一个实际程序中,您可以使用一般的形式的整数变量或常量。例如不适用00011001的形式,而写为25或者031或者0x19.在我们的例子中,我们将使用8位数字,从左到右,每位的编号是7到0
十进制转成二进制 除二取余法
二进制转成十进制 权值法:将各个二进制位从末尾开始乘以二的n次幂
十进制
八进制
十六进制
13.2 位逻辑运算符
4个位运算符用于整型数据,包括char.将这些位运算符成为位运算的原因是它们对每位进行操作,而不影响左右两侧的位。请不要将这些运算符与常规的逻辑运算符(&& 、||和!)相混淆,常规的位的逻辑运算符对整个值进行操作
按位取反~
一元运算符~将每个1变为0,将每个0变为1,如下面的例子:
~(10011010) 01100101 |
在位运算中根据数据类型进行运算符的补位操作在数据前补0
计算机会采用二进制补码存储数据
负数的原码转补码
负数的源码转换为补码:1、先转换为反码(符号位不变,数值位按位取反)2、在反码的基础上末位加一
负数的补码转换为原码:符号位不变,数值位按位取反,末位加一。
例如:
十进制数:123
转二进制:01111011(原码)
补码:01111011
反码:10000100(~123)
反码11111011(-123的反码)
补码:11111100(-123的补码)
正数的反码和补码都与原码相同。
负数的反码为对该数的原码除符号位外各位取反。
负数的补码为对该数的原码除符号位外各位取反,然后在最后一位加1
#include<stdio.h>
int main()
{
char a=20;
// 00010100
// 11101011 //按位取反
// 10010100//反码
//补码:10010101 -21
//~按位取反
printf("%d\n",~a);
return 0;
}
位与(AND): &
二进制运算符&通过对两个操作数逐位进行比较产生一个新值。对于每个位,只有两个操作数的对应位都是1时结果才为1。
#include <stdio.h>
int main()
{
// int a=10;
// int b=20;
int a=15;
int b=52;
// 将a转为二进制得 0000 1111
//将52转为二进制得 0011 0100
// 相与得到:00000100
// &位于运算的操作
//将10转为二进制 00001010
//将20转为二进制 00010100
//相与得: 00000000
printf("%d\n",a&b);
}
位或(OR): |
二进制运算符|通过对两个操作数逐位进行比较产生一个新值。对于每个位,如果其中任意操作数中对应的位为1,那么结果位就为1.
(10010011) | (00111101) = (10111111) |
int main()
{
int a=10;
int b=20;
//将10转为二进制 0000 1010
//将20转为二进制 0001 0100
//进行或运算 0001 1110
printf("%d\n",a|b);
return 0;
}
位异或: ^
相同为0 不同为1
二进制运算符^对两个操作数逐位进行比较。对于每个位,如果操作数中的对应位有一个是1(但不是都是1),那么结果是1.如果都是0或者都是1,则结果位0.
int main()
{
int a=10;
int b=20;
//将10转为二进制 0000 1010
//将20转为二进制 0001 0100
// 异或: 0001 1110 30
printf("%d\n",a^b);
}
左移 <<
左移运算符<<将其左侧操作数的值的每位向左移动,移动的位数由其右侧操作数指定。空出来的位用0填充,并且丢弃移出左侧操作数末端的位。在下面例子中,每位向左移动两个位置。
#include<stdio.h>
int main()
{
int a=10;
//0000 1010
// <<0001 0100
printf("%d\n",a<<1);
}
右移 >>
右移运算符>>将其左侧的操作数的值每位向右移动,移动的位数由其右侧的操作数指定。丢弃移出左侧操作数有段的位。对于unsigned类型,使用0填充左端空出的位。对于有符号类型,结果依赖于机器。空出的位可能用0填充,或者使用符号(最左端)位的副本填充。
#include<stdio.h>
int main06()
{
int a=10;
//0000 1010
// <<0001 0100
// 位移运算符每次移动相当于乘以2的n次幂
printf("%d\n",a<<1);
}
int main()
{
int a=10;
//0000 1010
//0000 0101
printf("%d\n",a>>1);
return 0;
}
11.2.1 打开位
已知:10011010:
将位2打开
flag | 10011010
(10011010) |(00000100) =(10011110) |
将所有位打开。
flag | ~flag
(10011010) |(01100101) =(11111111) |
交换两个数不需要临时变量
#include<stdio.h>
int main()
{
int a=10;
int b=20;
// 交换 a和b的值
// int temp;
// temp=a;
// a=b;
// b=temp;
// 使用异或运算符
//操作步骤
//0000 1010
//0001 0100
// 异或
// 0001 1110 a=30
// 操作步骤2
// 0001 1110
// 0001 0100
// 异或 0000 1010 //10
// 操作步骤3
// 0001 1110
// 0000 1010
// 0001 0100 //20
a=a^b;
b=a^b;
a=a^b;
printf("%d\n",a);
printf("%d\n",b);
}
移位运算符
11.2.5移位运算符
移位运算符能够提供快捷、高效(依赖于硬件)对2的幂的乘法和除法。
number << n | number乘以2的n次幂 |
number >> n | 如果number非负,则用number除以2的n次幂 |
十四 文件操作
文件类型指针(FILE类型指针)
在C语言中用一个指针变量指向一个文件,这个指针称为文件指针。
typedef struct
{
short level; //缓冲区"满"或者"空"的程度
unsigned flags; //文件状态标志
char fd; //文件描述符
unsigned char hold; //如无缓冲区不读取字符
short bsize; //缓冲区的大小
unsigned char *buffer;//数据缓冲区的位置
unsigned ar; //指针,当前的指向
unsigned istemp; //临时文件,指示器
short token; //用于有效性的检查
}FILE;
FILE是系统使用typedef定义出来的有关文件信息的一种结构体类型,结构中含有文件名、文件状态和文件当前位置等信息。
C语言中有三个特殊的文件指针由系统默认打开,用户无需定义即可直接使用:
- stdin: 标准输入,默认为当前终端(键盘),我们使用的scanf、getchar函数默认从此终端获得数据。
- stdout:标准输出,默认为当前终端(屏幕),我们使用的printf、puts函数默认输出信息到此终端。
stderr:标准出错,默认为当前终端(屏幕),我们使用的perror函数默认输出信息到此终端
文件的打开与关闭
任何文件使用之前必须打开:
#include <stdio.h>
FILE * fopen(const char * filename, const char * mode);
功能:打开文件
参数:
filename:需要打开的文件名,根据需要加上路径
mode:打开文件的模式设置
返回值:
成功:文件指针
失败:NULL
第一个参数的几种形式:
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");
第二个参数的几种形式(打开文件的方式):
打开模式 | 含义 |
r或rb | 以只读方式打开一个文本文件(不创建文件,若文件不存在则报错) |
w或wb | 以写方式打开文件(如果文件存在则清空文件,文件不存在则创建一个文件) |
a或ab | 以追加方式打开文件,在末尾添加内容,若文件不存在则创建文件 |
r+或rb+ | 以可读、可写的方式打开文件(不创建新文件) |
w+或wb+ | 以可读、可写的方式打开文件(如果文件存在则清空文件,文件不存在则创建一个文件) |
a+或ab+ | 以添加方式打开文件,打开文件并在末尾更改文件,若文件不存在则创建文件 |
文件的关闭
任何文件在使用后应该关闭:
- 打开的文件会占用内存资源,如果总是打开不关闭,会消耗很多内存
- 一个进程同时打开的文件数是有限制的,超过最大同时打开文件数,再次调用fopen打开文件会失败
- 如果没有明确的调用fclose关闭打开的文件,那么程序在退出的时候,操作系统会统一关闭。
#include <stdio.h>
int fclose(FILE * stream);
功能:关闭先前fopen()打开的文件。此动作让缓冲区的数据写入文件中,并释放系统所提供的文件资源。
参数:
stream:文件指针
返回值:
成功:0
失败:-1
#include<stdio.h>
#include <stdlib.h>
int main()
{
FILE *fp;
// fopen(文件路径,打开方式) 打开文件
// 打开方式 r 只读方式打开 w 写方式打开 a以追加方式打开
// 在文件操作中 所有路径都需要写[/]文件的名称都需要添加扩展名
fp=fopen("D:/a.txt","r");
// 对打开的文件进行判断 nULL表示空指针
if(fp ==NULL){
printf("打开文件失败");
return -1;
}
printf("文件打开成功%p\n",fp);
//关闭文件
fclose(fp);
return 0;
}
文件的读写
按照字符读写文件fgetc、fputc
按照字符读写文件fgetc、fputc
1)写文件
#include <stdio.h>
int fputc(int ch, FILE * stream);
功能:将ch转换为unsigned char后写入stream指定的文件中
参数:
ch:需要写入文件的字符
stream:文件指针
返回值:
成功:成功写入文件的字符
失败:返回-1
char buf[] = "this is a test for fputc";
int i = 0;
int n = strlen(buf);
for (i = 0; i < n; i++)
{
//往文件fp写入字符buf[i]
int ch = fputc(buf[i], fp);
printf("ch = %c\n", ch);
}
#include <stdio.h>
int fgetc(FILE * stream);
功能:从stream指定的文件中读取一个字符
参数:
stream:文件指针
返回值:
成功:返回读取到的字符
失败:-1
char ch;
#if 0
while ((ch = fgetc(fp)) != EOF)
{
printf("%c", ch);
}
printf("\n");
#endif
while (!feof(fp)) //文件没有结束,则执行循环
{
ch = fgetc(fp);
printf("%c", ch);
}
printf("\n");
按照行读写文件fgets、fputs
写文件
#include <stdio.h>
int fputs(const char * str, FILE * stream);
功能:将str所指定的字符串写入到stream指定的文件中,字符串结束符 '\0' 不写入文件。
参数:
str:字符串
stream:文件指针
返回值:6
成功:0
失败:-1
char *buf[] = { "123456\n", "bbbbbbbbbb\n", "ccccccccccc\n" };
int i = 0;
int n = 3;
for (i = 0; i < n; i++)
{
int len = fputs(buf[i], fp);
printf("len = %d\n", len);
}
文件结尾
在C语言中,EOF表示文件结束符(end of file)。在while循环中以EOF作为文件结束标志,这种以EOF作为文件结束标志的文件,必须是文本文件。在文本文件中,数据都是以字符的ASCII代码值的形式存放。我们知道,ASCII代码值的范围是0~127,不可能出现-1,因此可以用EOF作为文件结束标志。
当把数据以二进制形式存放到文件中时,就会有-1值的出现,因此不能采用EOF作为二进制文件的结束标志。为解决这一个问题,ANSI C提供一个feof函数,用来判断文件是否结束。feof函数既可用以判断二进制文件又可用以判断文本文件。
#include<stdio.h>
int main03()
{
FILE* fp;
char ch;
fp=fopen("D:/a.txt","r");
if(fp==NULL)
{
printf("打开文件失败\n");
return -1;
}
while((ch =fgetc(fp))!=EOF){
printf("%c",ch);
}
fclose(fp);
return 0;
}
int main()
{
char ch='A';
FILE* fp;
fp=fopen("D:/b.txt","w");
if(fp ==NULL)
return -1;
fputc(ch,fp);
fclose(fp);
}
按照格式化文件fprintf、fscanf
1)写文件
#include <stdio.h>
int fprintf(FILE * stream, const char * format, ...);
功能:根据参数format字符串来转换并格式化数据,然后将结果输出到stream指定的文件中,指定出现字符串结束符 '\0' 为止。
参数:
stream:已经打开的文件
format:字符串格式,用法和printf()一样
返回值:
成功:实际写入文件的字符个数
失败:-1
fprintf(fp, "%d %d %d\n", 1, 2, 3);
2)读文件
#include <stdio.h>
int fscanf(FILE * stream, const char * format, ...);
功能:从stream指定的文件读取字符串,并根据参数format字符串来转换并格式化数据。
参数:
stream:已经打开的文件
format:字符串格式,用法和scanf()一样
返回值:
成功:参数数目,成功转换的值的个数
失败: - 1
按照块读写文件fread、fwrite
1)写文件
#include <stdio.h>
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
功能:以数据块的方式给文件写入内容
参数:
ptr:准备写入文件数据的地址
size: size_t 为 unsigned int类型,此参数指定写入文件内容的块数据大小
nmemb:写入文件的块数,写入文件数据总大小为:size * nmemb
stream:已经打开的文件指针
返回值:
成功:实际成功写入文件数据的块数目,此值和 nmemb 相等
失败:0
读文件
#include <stdio.h>
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
功能:以数据块的方式从文件中读取内容
参数:
ptr:存放读取出来数据的内存空间
size: size_t 为 unsigned int类型,此参数指定读取文件内容的块数据大小
nmemb:读取文件的块数,读取文件数据总大小为:size * nmemb
stream:已经打开的文件指针
返回值:
成功:实际成功读取到内容的块数,如果此值比nmemb小,但大于0,说明读到文件的结尾。
失败:0
文件的定位
#include <stdio.h>
int fseek(FILE *stream, long offset, int whence);
功能:移动文件流(文件光标)的读写位置。
参数:
stream:已经打开的文件指针
offset:根据whence来移动的位移数(偏移量),可以是正数,也可以负数,如果正数,则相对于whence往右移动,如果是负数,则相对于whence往左移动。如果向前移动的字节数超过了文件开头则出错返回,如果向后移动的字节数超过了文件末尾,再次写入时将增大文件尺寸。
whence:其取值如下:
SEEK_SET:从文件开头移动offset个字节
SEEK_CUR:从当前位置移动offset个字节
SEEK_END:从文件末尾移动offset个字节
返回值:
成功:0
失败:-1
#include <stdio.h>
long ftell(FILE *stream);
功能:获取文件流(文件光标)的读写位置。
参数:
stream:已经打开的文件指针
返回值:
成功:当前文件流(文件光标)的读写位置
失败:-1
#include <stdio.h>
void rewind(FILE *stream);
功能:把文件流(文件光标)的读写位置移动到文件开头。
参数:
stream:已经打开的文件指针
返回值:
无返回值