Qt 安装
Day01
基础阶段
C语言的编译步骤
- 预处理:头文件展开,不做语法检查
- 编译:翻译成汇编(要做语法检查)
- 汇编:生成目标文件(已经是二进制文件,但是还不能运行,还要链接一些启动文件,库)
- 链接:生成可执行文件
- 在linux里查看文件依赖的动态库命令ldd
- 在windows可以用Depends.exe查看需要的dll(动态库)
交换文件说明
- vi写的文件没有保存关闭,会生成.swp文件,用来保存之前的内容
- .swp是隐藏文件,在文件恢复后,可以自行删除
寄存器
- 寄存器是最小的存储单元
- 运算的时候数据在cpu的寄存器中
- 由于数据从内存拿到寄存器太消耗时间,所以引出三级缓存。
2.10.7大端对齐与小端对齐
- 计算机的内存最小单位是什么?是BYTE,是字节,一个大于BYTE的数据类型在内存中存放的时候要有先后顺序。
- 高内存地址放整数的高位,低内存地址放整数的低位,这种方式叫到着放,术语叫小端对齐。X86和ARM都是小端对齐的。
- 高内存地址放整数的低位,低内存地址放整数的高位,这种方式叫正着放,术语叫大端对齐,很多unix服务器的CPU是大端对齐的。
头文件和函数文件
- 头文件放的是函数的声明,不是函数,如果是放函数,2个以上的地方引用了头文件就会报错(重复定义)。
- gcc 的第一个步骤预处理:就是把头文件的替换成函数的声明
- 头文件重复包含不会错(但是多余),防止可以用:
指针
- 变量的指针,存的是变量的首地址。
- 变量的指针,存的是变量的首地址。
有关指针的概念
- 指针和指针变量:
- 指针也是一种设计类型,例如:int *
- 指针是地址;
- 指针变量是存放地址的变量;
- int *p :表示整型指针p
- *p:表示取指针指向内存的值
野指针
- 原则:不可以自己设定地址,只能通过定义后的变量,这个变量的地址才是合法的,这是系统分配的地址(系统才有权利分配地址)。操作系统没有授权的内存会出现段错误(运行时的异常);
#### 空指针 - 因为定义一个指针变量,然后又不赋值系统就会赋个随机值,这样就是野指针,所以为了避免野指针,就强行把定义的时候把指针赋值为null
指针大小
多级指针
p[0] 数组符号去指针地址
- []和*是等价的,a[0] 等价 *a
万能指针
- 不可以定义void类型的普通变量,因为不能确定类型;
const 修饰的指针变量
- int const *p和const int *p修饰之后代表指针指向的空间不能修改(只读),指针本身的地址值可以修改
- int *const p:就是常量指针,p变量的值(地址)不能动。
数组名指向数组首元素地址。
- 数组名是常量不允许修改:int * const p,p值不能改,但是*p就是p指向的位置可以修改。
混淆概念
- 指针数组 和 数组指针
- 指针数组:是数组:char *p[]
- 数组指针:是指针
字符串常量
- 字符串是常量,他的地址不会变
- 字符串指的是首元素地址,所以可以直接赋值给一个指针。
字符串
- 放在文字常量区(程序结束才释放)
注意区别两种接收方式
- 第一种是创建在文字常量区的不能改 char const *p
- 第二种是字符串数组首地址不能改 char * const P
- 理解
形参中的数组
- 下图中三种定义的形式参数都是等价的;
- 数组作为函数的参数,会退化为指针-
- 二维数组不是二级指针
返回局部变量的地址
- 指针函数 (返回值是指针):int * fun(){}
- 野指针错误
- 在linux平台地址返回的是0(就是没返回)
- 在windows平台会返回,但是不能操作(因为系统已经收回了该内存,没有权限)
- 可以采用返回全局变量的地址来规避这个问题
返回全局变量的地址
- 所以有:-
-
-理解:
内存管理- 1. 普通局部变量 :放在栈区
-
2 静态局部变量 :数据放在data区
- 只能用常量初始化
- 只能用常量初始化
-
普通全局变量
- 缺陷:(可以强制声明 加extern关键字)
-
所以建议:
-
static全局变量(与普通全局变量的区别就是作用域不一样)
static是内部链接,一个文件只能有一个
普通全局变量是外部链接,全部文件只能有一个 -
同理普通函数和static函数是以道理
-
总结:
有关一个文件,被多个文件include,导致变量被多次引用的问题
- 就是头文件(.h)只声明,不定义(定义到具体文件定义)
内存分区
栈越界
- linux 查看栈空间大小:ulimit -a
几个函数
-
memset的使用-
– memcpy的使用 (不会因为\0结束)
-比较memcmp(不只是比字符串)
-
_指针指向栈区空间(纠正野指针)
-
指针指针堆区空间(纠正野指针)
-
用完释放内存,清空指针
值传递 和 址传递
- 指针在实际开发中的价值往往是用来作为函数的参数
- 值传递:不管变量是什么类型,只要是变量本身传递就是值传递(即时变量本身就是地址,那也是值传递),结果就是形式参的改变不会改变实参的值,但是如果传的是指针,指针的值(地址)不变,但是可以改变指针指向地址内存的值;
- 址传递:传递变量的地址,对变量取地址
经典内存问题分析
- 纠正:
结构体
- 定义变量,使用
- 并且可以这样赋值
- 结构体互相赋值:与面向对象语言中的对象赋值不一样,他是直接复制一份同样的内容到自己空间(是独立的内存)
- 结构体套一级指针
- 非法使用内存导致的错误说明
- 非法使用内存导致的错误说明
- 解决方法,不能往未分配的地址写入值(操作野指针),但是可以给指针赋值,即:
- 定义为str[]的要:用strcpy往定义的空间写(改变地址指向空间的值);不可以改变地址值,因为str是数组,是常量
- 定义为char * 的要:直接赋值一个字符串,(改变地址值);不能往空间写,因为是野指针,会内存污染。
- 如图:
共用体
- union关键字
- 共用体大小为最大成员的大小
- 共用体公用一块内存,所有成员地址都一样
枚举
- enum关键字
enumspectrum { red, yellow, green, blue, white, black };
enum spectrum color;
color = black;
if (color != red)
- 默认时,枚举列表中的常量被指定为0,1,2等
typedef(起别名)
- 与宏定义不同:typedef是编译器处理的,而不是预处理
- 比如:
Day02 文件操作
scaf()读入键盘,先要读入缓存区
printf 把内存的值放入到屏幕
文件指针
- FILE *fp:(不同平台都叫FILE是用typedef定义别名,本来是不一样的)
- 是一个结构体类型(不同平台,成员不一样,不用纠结),
- fp指针,调用fopen()就砸堆区分配空间,把地址返回给fp
- fp指针不是指向文件,fp指针和文件关联,fp的内部成员保存了文件的状态;所以用*fp操作不了文件。关联文件的是fp的内部成员fd:
- 文件指针*fp不是指向文件本身,是指向内存的一个结构体变量(关联)。(文件是放在硬盘上的,怎么能指,笑脸)
Day03
文件分类
- 设备文件:stdout(标准输出文件)
- 磁盘文件
- 二进制文件:图片,视频
- 文本文件
文件操作流程
- 文件操作:fopen()
- 读写文件:
- 按字符:fget,fput
- 按字符串:fgets,fputs
- 判断结尾:feof
- 关闭文件:fclose()
标准文件指针
- 输入:stdin —scanf 写入标准输入
- 输出:stdout —printf 向标准输出写入
- 错误:stderr—perror 向标准错误写入
注意:标准文件指针关闭了,可以自己关联文件???
有关打开方式w r a
- 注意在windows下要按照二进制打开要加b,否则会当成文本处理,采用fread也没有(fread只是方便处理二进制文件,并不是确定文件以二进制方式打开),比如:wb、rb;
- 但是在linux中不需要加上这些- #### 等价的打开方式
文件打开时选择相对路径,那么是相对于可执行程序
feof()不局限文本文件(文本文件没有负数),如果仅仅只是用-1字符表示文件结尾,在二进制文件中,就会出现错误。
从键盘接收输入制作vi命令
- gets:不能有回车
- scanf:忽略空格
- fgets:完美匹配
fgets和fscan
-
fgets:
- 读取文件的时候会读取换行符;
- 会一直读取到feof才会结束;
-
fscan:
- 会按照固定的格式读取,如果没有匹配的格式不会读取;
-
总结:用什么格式写入就用什么格式读取;
文本文件操作函数(按字符,字符串)
- printf,scanf
- sprintf(格式化写入到字符串),sscanf(从字符串按格式扫描出变量)
- fprintf(格式化写入到文件),fscanf(从文件按格式扫描出变量)
- fgetc,fputc
- fgets(从文件读取到字符串),fputs(把字符串写入文件)
- fread,fwrite
- fseek,ftell
- 下面两个等级价
二进制文件操作函数(按块大小)
- fread:
- fwrite:
Day04
注意有关结构体(二进制文件)的野指针错误
- 当结构体包含char *的时候,里面存的是地址
- 如果写进文件,这个时候写进去的是地址,程序如果结束,这个地址指向的内存会被释放掉
- 然后调用文件读取,这是会指针指向的位置已经被释放,操作的是野指针
- 解决方式:
- 写入
- 读取:读取的时候,读取结构体;但是指针变量重新赋值(自己创建新的堆区空间,然后把name写入堆区空间)
经典疑问
- 有关结束符
- 有关二维数组,二级指针
二维数组定义好即分配了空间,默认就在栈空间
二级指针,要指向定义好的变量或者自己开辟空间才有空间,否则是野指针
提高篇
数据类型
有关数组名
- b[10]数组名b是数组的首地址,其值和&b相同
- 取* 向前进一级 ,取&向后退一级
int b[10];
//相同
printf("b:%d,&b:%d\n", b, &b);
//不同
//b是首个元素的地址
//&b是整个数组的首地址
printf("b+1:%d,&b+1:%d", b + 1, &b + 1);
有关void
万能指针void *
- 类似于泛型,可以不固定要传入数据的类型。例如:memcpy可以接收多个类型,但是strcpy只能够接收字符串。
内存4区
- 堆区(程序员管理):
- 指针p指向的堆区内存被释放了,如果指针不置为null,那么指针还是原来的值,如果在堆指针释放就会报错;free(p)报错
- 栈区(临时变量区):
- 内存分析
- 全局区;全局变量(初始化、未初始化),静态变量,文字常量
内存分析:
- 代码区:
函数调用模型
变量的操作
- 生命周期
- 作用域
数组 str[]=“sdfsfsd”;
- 是把文字常量区的字符串,拷贝到栈区,str表示的是栈区的字符串首地址
栈的生长方向和内存的存放方向
指针的强化
- 指针也是一种数据类型(就是固定大小内存块的别名)
- 万能指针使用前要转换类型
为了防止头文件之间的死循环的include
- 可以在头文件写上 pragma
c项目兼容c++编译器
####
- 0和’\0’是同一个,都可以做字符串的结尾;但是‘0’和前两个不同;
有关char buf[]和char *p 中buf和p有什么区别
- 相同都是地址,都可以+i,都可以*(取值)
- 不同:buf是const,不能修改所以不能buf++
- 原因:因为buf是数组,是分配在栈 空间的。系统为了能够管理回收,所以设定buf不能修改;
- const常量可以被间接修改 通过另一个指针修改
二级指针的输入和输出
- 二级指针在函数传参主要应用在输入(主调函数分配内存)
- 二维数组
二维数组步长@增一维,*降一维
- 针对二维数组:a[3][10]的a+i、 a[i] 和*(a+i)值是一样的;区别:
- a[i] 和*(a+i)等价,但是a[i]+1或者*(a+i)+1,地址增长1
- a+i+1地址增长10
day05
- 二级指针做输入:第一种内存模型(分配在文字常量区)
- 二级指针做输入:第二种内存模型(分配在栈区)
- 二级指针做输入:第三种内存模型(分配在堆区)
- 数组类型,也可以直接用指针直接指向
- 动态打造二维空间
- 数组类型,也可以直接用指针直接指向
如果有多个变量指向一块内存,内存释放只要一次,其他的指针,依次赋值为null就行了
数组
- 一维数组
- typedef定义:
-
- typedef定义:
数组指针
-
定义方式1:
-
定义方式2(常用)
-
定义方式3(直接法,没有先用type确定类型名<匿名>):
-
总结 :
-
二维数组指针赋值辨析:
-指向整个一维数组,所以p=&a- 一维数组指针,指向整个二维数组首行,所以p=a
- 一维数组指针,指向整个二维数组首行,所以p=a
注意数组名在size函数中的区别
- a+0代表是地址,与a不同,a表示整个数组
int main(void)
{
int a[2][10];
printf("---> %d,---> %d\n", sizeof(a), sizeof(a + 0));//80 4
printf("---> %d,---> %d\n", sizeof(*(a+0)), sizeof(&a[0]));//40 4
system("pause");
return 0;
}
二维数组在形参中会退化为指针,但是指针的步长要一样才行
- 针对于指针数组:
- char * a[]={“111111”,“222222”,“asdsdfsdf”}
-一个步长是一个char*,那么可以用char **来做形参接收
- char * a[]={“111111”,“222222”,“asdsdfsdf”}
- 针对于二维数组int a[2][10]:
- 一个步长是10*4,
- 要用一维数组指针int (*p)[10] 来做形参接收
结构体之间相互赋值
- 这里可以理解其他语言中的强制类型转换的原理:
- 涉及到的基本条件就是内存空间大小一样就可以强制转换。
- 类型相同即可以相互赋值
- 结构体变量名和对象名不同:
- 结构体名放的是结构体的地址,但是地址是在栈空间,结构体间相互赋值是重新复制一份,两者没关系
- 类对象名的地址是在堆空间,相互赋值,只是改引用,不会重新复制一份
结构体变量深拷贝 和 浅拷贝
- 浅拷贝只拷贝当前的一层(结构体里嵌套了指针),如果当前有指针变量,那么指针指向的空间不拷贝
- 深拷贝则一直拷贝到底
- 注意并不想python中的深拷贝那么智能,完全靠自己手动strcpy
结构体的内存对齐-
- 内部默认按照最长的变量来对齐(以空间换时间)
- 如果结构体套结构体,那么按照所有结构体内最长变量来对齐
- 数组也是复合类型,按一个元素的长度算
- 到了子结构体,前面全部补齐,再开始;要一个新起点
- 可以自己指定对齐长度pragma=package(2),但是指定超过最长,则为最长
文件加强
- 标志操作的文件的结构体叫文件句柄,内部有个成员fd叫文件描述符:
- 文件操作api
- 按 :字符-字符串-块-格式4种方式操作文件;
- 为了更加灵活 还有 随机出来api fseek 和 ftell
- 打开方式
- 文件的写入有缓冲区:要满了,手动刷新,关闭文件,程序结束才会真正写入;
- 但是缓冲区是针对普通文件,标准文件(stdin->键盘,stdout->屏幕,stderro->屏幕)默认自动刷新写入(一输入就显示在屏幕上)
fgets的使用
文件的加解密的时候,注意打开方式,一定要以二进制的方式,
day06
结构体 组成链表
-
静态链表:初始化好链表节点(栈中)
-
动态链表:自己动态分配空间(堆中动态)
-
指针函数 int*fun(){}:返回值是指针
函数指针:指向函数
- 定义:
- 先定义函数类型,在根据类型定义指针:
- 确定是指针还是函数,从左向右看,优先级高是谁就是谁,这就是为什么函数指针为什么要加上(),因为不加(),*的优先级要低于()
- 函数指针数组
- 先定义函数类型,在根据类型定义指针:
- 回调函数:当传入不同的函数参数,得到的行为不一样。
- 多态:调用同一个接口,产生不同的效果
链表内存四区图,还原原始图
-
简洁图:
-
四区图:
C语言处理的四个步骤:预处理,编译,汇编,链接
-
宏定义:
- 只是替换
- 宏定义不受局部影响,定义下面的代码都能用
-
宏定义函数:
-
条件编译
-
防止重复包含头文件:
-
封装动态库
- lib 是编译的时候使用
- dll 是运行的时候使用
-
日志打印:
-
内存泄露检查
- 使用三方的:memwatch