9-3变量生命周期和修饰符
生命周期
函数的声明周期
起于调用,结束于调用结束
局部变量的生命周期
起于调用,结束于调用结束
main函数的生命周期
main开始 == 进程的开始 main函数的结束 == 进程的结束
全局变量的生命周期
起于main调用,结束于main调用结束
修饰符
auto
含义:只能用于修饰局部变量,表示该变量存储于栈stack
特点: 随用随开,用完消失
C中默认的局部变量,就是auto类型,所以通常将其省略 c++ auto自动类型
register
含义:只能修饰局部变量,原则上,将内存中的变量升级到CPU寄存器中存储,这样访问速度会更快,但由于CPU寄存器数量有限,通常会在程序优化阶段,被优化为普通auto类型,可以通过汇编代码来查看,优化过程(与平台和编译有关)
特点:可以避免cpu与内存的频繁交互
一般程序从内存读取数据 放到寄存器 进行运算 运算结果写入到内存
关于各个存储部件中读写速度
硬盘:7200转
内存:频率1333MHZ 1600MHZ 带宽 = 频率*64/8
缓存:
CPU:
extern
含义:只能用于修饰全局变量,全局变量本身是全局可用的,但是由于文件是单个完成编译,
并且编译是自上而下的,
所以说,对于不是在本范围内定义的全局变量,要想使用必须用extern 进行申明,
如果不加上可能会造成重定义。
特点:
1.可以省略,声明在前,定义在后,不论局部还是全局。
main.c : int a; other.c : int a =200;编译不会报错
2.外延性 某文件定义的全局变量 可在其他文件使用
c语言编译是跨文件的
编译过程:单文件编译 单文件每个编译成xxx.o
链接所有.o文件和lib.so文件
生成可执行文件xxx.out
9-4 static
修饰局部变量
特点:1.初始化值为0,且只初始化一次
2.生命周期等同于程序
修饰全局变量
特点:1.全局的外延性消失,变成文件内部的全局变量 也适用于函数
2.存储在data段
小结问题:
-
extern int a;
int a= 200;
是不是同一个a? -
局部变量和全局变量储存的位置有什么不同?
-
static修饰的变量值所保存的位置在哪里 才导致是累计变化的?
保存在data段中
9-5 字符串常量——9-7字符串的输入输出
定义
是双引号括起的任意字符序列
字符串大小
看到的大小,比我们实际字面量要多一个,
最后的字符’\0’,我们称为字符串结束字符,是系统对双引号引起的字符串自动加设 的,而非手动干预。
字符串存储
字符串的存储,不像其它普通类型的常量自变量的存储一样,普通类型自变量通常存储 在代码段(text),
而字符串,则存储在数据段,且是只读数据段。
也就是通常意义上的常量区,但是常量区这种说法,是不太精确的,也不提倡。
拓展
栈区(stack):临时变量,局部变量,生命周期结束即销毁
堆区(heap):一些new出来的变量 存储的地方 特定销毁时间统一销毁,析构函数
数据段(data):用来存放程序中已初始化的全局变量的一块内存区域,属于静态内存分配。
代码段(text):用来存放程序执行代码的一块内存区域,只读,且不可修改。可能包含一些只读的常数变量,例如字符串常量等。
bbs段:bss段(bss segment)通常是指用来存放程序中未初始化的全局变量的一块内存区域
字符数组和字符串区别
1.字符串数组(栈区)是将text区的常量字符串 拷贝到栈区 字符串值的改变实际上是栈的值改变
字符串指针 是直接指向 text区常量字符串地址 故不能改变text区的值
2.相同点:字符数组长度大于等于字符串长度
字符串的输入输出
puts函数:printf的自带换行
scanf函数:默认遇到空格结束输入,scanf("%[\n]s")遇到回车结束输入,但不检查输入长度,不安全
gets函数:空格也可以读入,但不检查输入长度,不安全。
fgets函数:参数列表(接收变量,长度,输入流)
tips
1.printf(“”) 空串有换行功能
2.gets();不检查预留存储区是否能够容纳实际输入的数据,
换句话说,如果输入的字符数目大于数组的长度,gets 无法检测到这个问题,就会发生内存越界,所以编程时建议使用 fgets()。
char *fgets(char *s, int size, FILE *stream);
fgets() 虽然比 gets() 安全,但安全是要付出代价的,
代价就是它的使用比 gets() 要麻烦一点,有三个参数。
它的功能是从 stream 流中读取 size 个字符存储到字符指针变量 s 所指向的内存空间。它的返回值是一个指针,指向字符串中第一个字符的地址。
3. c中允许 c++不允许
char a[2] = "china";
printf("%s",a);
输出结果:ch
4.char * p = "china";
printf("p = %p p+1 = %p p[0]=%c 0[p] =%c\n", p, p + 1, p[0], 0[p]);
printf(" = %p = %p =%c =%c\n", "china", "china"+1, "china"[0], 0["china"]);
输出结果:地址不相等 与视频不符合
问题?
1.视频说 " 字符串,则存储在数据段"
而 这篇文章说了
在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等
2.字符串指针和字符数组的区别?
字符数组可修改内容,有个拷贝过程,data段常量区拷贝到栈区,字符数组实际是栈区
字符串指针如果指向data段字符串常量 是不可修改地址的,若是指向字符数组反而可以
10-2 从字符串常量到字符数组
10-3 原生字符串处理
strlen(char *) 字符串长度
strcat(char *head,char *tail) 连接两个字符串
注意:被连接的串必须有足够空间
but vs 可行…
char firstName[] = "jim";
char familyName[30] = "tim";
strcat(firstName, familyName);
printf("%s", firstName);
输出:jimtim
优化操作:
原本代码
while(*p) p++;
char *p,*q;
while(1){
p * =q*;
if(p* == '\0'){
break;
}
p++;q++;
}
优化后:
while(*p) p++;
while(*p++ = *q++);
Tip
-
int* 和 char* ,float * 长度 是多少?
指针统一长度为4byteint a = 1; int* ap = &a; printf("%d \n", sizeof(ap)); char * c = "123"; printf("%d \n", sizeof(c)); float d = 1.0f; float * dp = &d; printf("%d \n", sizeof(dp));
输出:4
4
4 -
char *ac = “123”;
printf("%p \n", &ac);
printf("%p \n", “123”);为什么输出地址不同?
3.字符串长度和大小是不同的
长度是指字符长度且不包含\0,大小是指字节大小包含\0
10-7 字符串指针数组入门
指针数组的本质是数组,数组指针的本质是指针。
Tips
1.运行这段代码 设置字符串池优化
char * pa = “china”; char pb = “america”; charpc = “canada”; char*pd = “japan”;
char * cpArr[4] = { pa,pb,pc,pd };
printf(“pa = %p \n”, pa);
printf(“pb = %p \n”, pb);
printf(“pc = %p \n”, pc);
printf(“pd = %p \n”, pd);
for (int i = 0; i < 4; i++)
{
printf("%p \n",cpArr[i]);
}
printf("----------------\n", pd);
char * cpArr2[4] = {"china","america","canada", "japan" };
for (int i = 0; i < 4; i++)
{
printf("%p \n", cpArr2[i]);
}
- NULL ,nullptr,0区别
11-1 栈内存和堆内存的基本概念
概念
源程序:源代码
程序:可执行文件
进程:时间概念可执行性文件被拉起,到结束的这一段过程,称为进程
进程空间:可执行文件 被拉起以后 在内存中的分布情况
栈内存
栈存储的特点
栈中存放任意类型的变量,但必须是 **auto** 类型修饰的,即自动类型的局部变量,随用随开,用完即消。
内存的分配和销毁系统自动完成,不需要人工干预。
分配顺序,由高地址到低地址。
栈的大小
栈的大小并不大,他的意义并不在于存储大数据,而在于数据交换。
常见栈溢出
局部变量申请过多,过大,char[1024*1024*10];
递归层数太多 例如10000层递归
堆内存
栈存储的特点
堆内存可以存放任意类型的数据,但需要自己申请与释放。
分配顺序,由低地址到高地址。
堆大小
堆大小,想像中的无穷大,对于栈来说,大空间申请,唯此,无它耳。但实际使用中,受限于实际内存的大小和内存是否连续性。(基本最大为用户空间大小)
Tips
1.memset(pm,1,10*sizeof(int);
为每个字节赋值为1;一个int 实际4个字节 4个0x01
printf("%#x");
%#表示的输出提示方式,如果是8进制,在前面加0,如果是十进制,不加任何字符,如果是十六进制,会加上0x
2.calloc和malloc
calloc在动态分配完内存后,自动初始化该内存空间为零,而malloc不做初始化,分配到的空间中的数据是随机数据
3.realloc
void *realloc(void *ptr, size_t size);
功能:扩容(缩小)原有内存的大小。通常用于扩容,缩小会会导致内存缩去的部分数据丢失。
参数:
void *ptr:ptr 表示待扩容(缩小)的指针, ptr 为之前用 malloc 或 者 calloc 分配的内存地址。或 ptr==NULL,则该函数等同于 malloc。
size_t:size 表示扩容(缩小)后内存的大小。
返回值:
成功返回非空指针指向申请的空间 ,失败返回 NULL。返回的指针,可能与 ptr 的值相同,也有可能不同。若相同,则说明在原空间后面申请,否则,则可能后续空间不足,重新申请的新的连续空间,原数据拷贝到新空间,原有空间自动释放。
原理:先判断当前的指针是否有足够的连续空间,如果有,扩大mem_address指向的地址,并且将mem_address返回,如果空间不够,先按照newsize指定的大小分配空间,将原有数据从头到尾拷贝到新分配的内存区域,而后释放原来mem_address所指内存区域(注意:原来指针是自动释放,不需要使用free),同时返回新分配的内存区域的首地址。即重新分配存储器块的地址。
malloc 使用流程 申请 判空 使用 释放 置空
问题1:指针接收 创建的内存空间(malloc),释放时怎么知道释放(free)多少内存空间的
heap的每个块儿头有保存本块的大小以及本块分配的大小。
free并不知道那个指针所指空间的大小,它要先查找当前heap的头部,然后整个块儿释放掉。
问题2:如何用malloc/new 定义一维,二维,三维数组?用free/delete销毁呢?malloc/free和new/delete区别?
问题3:malloc/calloc/realloc 区别与不同?
问题4:常见的内存泄露和检测内存泄露的常见方式?内存泄漏和野指针?
12-1 结构体 共用体 枚举
typedef深入分析
定义
用自定义名字为已有数据类型命名。其实叫 typerename 更合适。
形如:typedef 现在类型名 新类型名;
typedef 和#define 的区别
typedef 是以;号结尾的 C 语言语句。而#define 则是预处理阶段的文本替换。有时
他们是可以互换的,但有时不可以。
typedef a b;
#define a b
Tips
1.typedef char *pChar; pChar a; a等同于?
试着预测输出并调试以下代码:
{
char *p,q;
printf("sizeof(p) = %d sizeof(q) = %d \n",sizeof(p),sizeof(q));// 4,1
typedef char *pChar;
pChar a,b;
printf("sizeof(a) = %d sizeof(b) = %d \n",sizeof(a),sizeof(b));// 4, 4
#define DpChar char*;
DpChar m,n;
printf("sizeof(m) = %d sizeof(n) = %d \n",sizeof(m),sizeof(n));// 4 ,1
}
2.总结
新类型名一般用大写表示,以便于区别。
用 typedef 只能声明新的类型名,不能创造新的类型,只是为已经存在的类型起
一个别名,也不能用来定义变量,即只能用其声明的类型来定义变量;
有时也可用宏定义来代替 typedef 的功能,但是宏定义是由预处理完成的,而
typedef 则是在编译时完成的,更为灵活方便。
typedef 可以让类型更见名知意,更便于移值。
结构体的初始化
初始化及成员访问
点成员运算符(.) 优先级等同-> 但比*高
Tips
问题一
初始化和赋值在c++中实际含义是?
问题二
形参和实参之间传递是什么关系?
问题三
typedef和#define 本质区别在哪里?
问题四
结构体作形参为什么要传指针?
结构体数组及应用
结构体嵌套和结构体大小
结构体嵌套
结构体中,嵌套结构体,称为结构体嵌套。结构体中,既可以嵌套结构体类型变量,
也可以嵌套结构体类型,后一种方式不推荐。
结构体类型大小
结构体成员内存分布
首成员在低地址,尾成员在高地址。
内存对齐
对齐规则
目的是解决:一个成员变量需要多个机器周期去读的现象,称为内存不对齐。为什么要对齐
呢?本质是牺牲空间,换取时间的方法。
不同的编译器和处理器,其结构体内部的成员有不同的对齐方式
x86(linux 默认#pragma pack(4), window 默认#pragma pack(8))。linux 最大支持 4 字节对齐。
方法:
①取 pack(n)的值(n= 1 2 4 8--),取结构体中类型最大值 m。两者取小即为外对齐大 小 Y= (m<n?m:n)。
②将每一个结构 体的成员大小与 Y 比较取小者为 X,作为内对齐大小.
③所谓按 X 对齐,即为地址(设起始地址为 0)能被 X 整除的地方开始存放数据。
④外部对齐原则是依据 Y 的值(Y 的最小整数倍),进行补空操作。
外对齐和内对齐:
外对齐Y:保证读取结构体的起始地址到结束地址,表示结构体之间的对齐
内对齐X:保证从结构体内变量起始的地址到结束的地址 正好是该变量的长度,结构体内成员变量之间的对齐
结构体中指针使用注意事项
1.向结构体内未初始化的指针拷贝
结构体中,包含指针,注意指针的赋值,切不可向未知区域拷贝。
struct student
{
char*name;
int score;
}stu;
int main()
{
strcpy(stu.name,"Jimy");
stu.score=99;
return 0;
}
name 指针并没有指向一个合法的地址,这时候其内部存的只是一些乱码。所以在
调用 strcpy 函数时,会将字符串 “Jimy” 往乱码所指的内存上拷贝,内存 name 指针根
本就无权访问,导致出错。 同样stu.name = “Jimy”;可以的,name指向常量区,但是将来name不可改
int main()
{
struct student *pstu;
pstu = (struct student*)malloc(sizeof(struct student));
strcpy(pstu->name,"Jimy");
pstu->score=99;
free(pstu);
return 0;
}
为指针变量 pstu 分配了内存,但是同样没有给 name 指针分配内存。错误与上面
第一种情况一样,解决的办法也一样。这里用了一个 malloc 给人一种错觉,以为也给
name 指针分配了内存。
2.未释放结构体内指针所指向的空间
从内向外依次释放空间。
Tip
1.结构体中嵌套构造类型成员的对齐(数组、结构体成员)
2.深拷贝和浅拷贝
深拷贝:拷贝内存的内容,结构体之间互不影响。
浅拷贝:直接地址赋值,指针共享一片内存。一个结构体发生变化,另一个结构体也会发生变化。
13-2 单链表
14-2 文本文件和二进制文件
文件流
C 语言把文件看作是一个字符的序列,即文件是由一个一个字符组成的字符流,因 此 c 语言将文件也称之为文件流。即,当读写一个文件时,可以不必关心文件的格式或
结构。
文件类型
文件,物理上是二进制,所以文本文件与二进制文件的区别并不是物理上的,而是逻辑上的。
文本文件是基于字符编码的文件,常见的编码有 ASCII 编码,二进制文件是基于值编码
的文件。
文本文件
以 ASCII 码格式存放,一个字节存放一个字符。 文本文件的每一个
字节存放一个 ASCII 码,代表一个字符。这便于对字符的逐个处理,但占用存储空间
较多,而且要花费时间转换。
二进制文件
以值(补码)编码格式存放。二进制文件是把数据以二进制数的格
式存放在文件中的,其占用存储空间较少。数据按其内存中的存储形式原样存放。
用例:
int main() {
short a = 10000;
FILE * fp = fopen("ascii.txt", "w");
fprintf(fp, "%d", a);//文本写
fclose(fp);
FILE *fp2 = fopen("bin.txt", "w");
char buf[] = "abcd";
fwrite(&a, 2, 1, fp2);//字节写
//fwrite(buf, 4, 1, fp2);//字节写
fclose(fp2);
return 0;
}
文件缓存
为什么要有缓冲区(buffer) 原因为多种,有两个重点:
1 从内存中读取数据比从文件中读取数据要快得多。
2 对文件的读写需要用到 open、read、write 等系统底层函数,而用户进程每调用
一次系统函数都要从用户态切换到内核态,等执行完毕后再返回用户态,这种切
换要花费一定时间成本(对于高并发程序而言,这种状态的切换会影响到程序性
能)。
文件的打开和关闭
FILE 结构体
FILE 结构体是对缓冲区和文件读写状态的记录者,所有对文件的操作,都是通过
FILE 结构体完成的。
typedef struct {
short level; /* 缓冲区满/空程度 */
unsigned flags; /* 文件状态标志 */
char fd; /* 文件描述符 */
unsigned char hold; /* 若无缓冲区不读取字符 */
short bsize; /* 缓冲区大小 */
unsigned char *buffer; /* 数据传送缓冲区位置 */
unsigned char *curp; /* 当前读写位置 */
unsigned istemp; /* 临时文件指示 */
short token; /* 用作无效检测 */
} FILE ; /* 结构体类型名 FILE */
在开始执行程序的时候,将自动打开 3 个文件和相关的流:标准输入流(stdin)、标
准输出流(stdout)和标准错误(stderr),它们都是 FIEL*型的指针。流提供了文件和程序的
通信通道。
fopen
如果读写的是二进制文件,则还要加 b,比如 rb, r+b 等。 unix/linux 不区分文本和
二进制文件。
fclose
作用:强制输出缓存内容 然后关闭FILE*
文件的读和写
一次读一个字符
fputc
fgetc
feof
特点:feof 这个函数,是去读标志位判断文件是否结束的。即在读到文件结尾的时候再
去读一次,标志位才会置位,此时再来作判断文件处理结束状态,文件到结尾。如果用
于打印,则会出现多打一次的的现象
一次读一行字符
windows 换行符 ‘\n’ = 0x0d 0a;
linux 换行符 ‘\n’ = 0x 0a;
tips
1.fprintf(fp,fmt,buff) 文本写出
fwrite(buff,size,count,fp) 字节写出
2.乱码原由
二进制文件读取由acsii码的方式读取
3.文件缓存win和linux区别
win会立即输出,linux会等待缓存满了再输出,加上\n会立刻输出缓存
4.rewind(fp) 将文件指针重置到文件头