CC++QT开发知识总结

C/C++/QT开发知识总结

C

存储类型

auto、register、extern、static四种存储类型

auto:修饰局部变量,一般的类型定义默认auto

register:寄存器类型,把修饰的变量放在寄存器

extern:外部引用类型,主要用于引用统一工程中的全局变量或函数,目的是声明,告诉编译器该变量存在空间

static:修饰全局变量或函数时,限制变量或函数的作用域,表示只能在本文中使用。修饰局部变量时,延长局部变量的生命周期,运算结果保留上一次结果

程序编译要经过的4个阶段

预处理:gcc -E test.c -o test.i //处理以#开头的文件

编译:gcc -S test.i -o test.s //生成汇编语言

汇编:gcc -c test.s -o test.o //将汇编语言转换成机器语言

链接:gcc test.o -o test //与其它的机器代码文件和库文件汇集成一个可执行的二进制代码文件

运算符

与(&):两个操作数对应二进制同样为1 结果 才为1,否则为0;

或(|): 两个操作数对应二进制同样为0结果 才为0,否则为1;

取反(~): 一个二进制操作数,对应位为0,结果 位为1;对应位为1,结果位为0; (作用是将每位二进制取反)

^:异或: 两个操作数对应二进制相同则结果 为0,不同则为1

左移(<<)运算符:右边空出来的位用0填补高位左移溢出则舍弃该高位

右移(>>)运算符:左边空出来的位用0或1填补,正数用0负数用1填补。

数据结构

数据的结构有线性表、树、图,每个结构都有两种构成方式,一是顺序,二是链式。

顺序分配内存的方式是开辟一片连续的固定大小的内存空间。

链式分配内存的方式是创建结点,单向的链式节点包含数据域与指向下一个节点的指针域,当节点为尾节点时,指针域指向空。双向链式的节点包含指向上一个节点的指针域、数据域、下一个节点的指针域,当节点为头结点时,指针域指向尾节点地址,节点为尾节点时,指针域指向头节点。

数据结构的本质是是一个类,比如顺序表,类中的对象成员有数据的首地址、大小、有效数据的个数,比如单向链表,类中的成员是首节点的首地址、尾节点的首地址、节点个数。

C语言中没有类的概念,在C语言中,数据结构一般由结构体定义

c的

C内存管理

C程序编译时内存分为5大存储区:

1.栈区(stack) --编译器自动分配释放,主要存放函数的参数值,局部变量值等;
2.堆区(heap) --由程序员分配释放;
3.全局区或静态区 --存放全局变量和静态变量;程序结束时由系统释放,分为全局初始化区和全局未初始化区;
4.字符常量区 --常量字符串放与此,程序结束时由系统释放;
5.程序代码区–存放函数体的二进制代码

栈与堆的底层结构

栈是一种运算受限的线性表,其限制是指只仅允许在表的一端进行插入和删除操作 ,及在栈顶进行操作。顺序栈的数据成员是数据域、栈的大小、栈顶下标。链式栈的数据成员是,栈顶地址、

堆是树形结构,是一种特殊的二叉树

分配内存的三种方式

**[1]**从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static变量。
**[2]**在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。
栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
**[3]**从堆上分配,亦称动态内存分配。程序在运行的时候用malloc或new申请任意多少的内存,程序员自己负责在何时用free或delete释放内存。
动态内存的生存期由程序员决定,使用非常灵活,但如果在堆上分配了空间,就有责任回收它,否则运行的程序会出现内存泄漏,频繁地分配和释放不同大小的堆空间将会产生堆内碎块。

if…else与 switch …case的区别

switch…case…只能用于case值为常量的分支结构,而if…else…更加灵活。

if判断条件为逻辑表达式,可以是布尔类型的合法表达式、可以是常量、枚举等。而switch 通常处理算术表达式,或字符。

switch 进行一次条件判断后直接执行到程序的条件语句。而if…else 有几种条件,就得判断多少次。

相比if语句,switch语句是以空间换时间的分支结构。因为它要生成跳转表,所以占用较多的代码空间。当case常量分布范围很大但实际有效值又比较少的情况,switch…case的空间利用率将变得很低。

分支较多时,使用switch的效率高于与代码可读性都高于if,除非第一个if条件就为真。

字符串的常用操作

strlen、strcat、strcmp

字符串拼接sprintf(sql, “select * from user where u_name=‘%s’ and password=‘%s’ and identity=‘%s’;”, this->edit_name->get_content(), this->edit_pwd->get_content(), this->edit_identity->get_content());

将字符串转化为整数(?各种基本数据类型):sprintf(str,“%d”,i),字符串转换小数atof()

文本文件与二进制文件

数据在物理上的存储方式是二进制的,即由0/1字符串构成。而我们解读这些的方式有两种:基于字符编码,和基于值编码。

基于字符编码,即每个我们肉眼可读的字符都有唯一对应的0/1字符串,我们读、写这些字符都使用同一套编码方式。如果某文件的数据使用基于字符的编码,那么该文件即为“文本文件”。常见的基于字符的编码有:ASCII码,Unicode编码(UTF-8编码 )。

基于值编码,可以理解为自定义的编码。

如果某文件的数据使用基于值的编码,那么该文件即为“二进制文件”。不同的应用程序对二进制文件中的每个值会有不同的解读,就像不同的编码对文本文件中的每一/多个字节有不同的解读。

常见的二进制文件有可执行程序、图形、图像、声音等等。

I/O

缓冲机制

全缓冲:当填满标准I/O缓冲区后才进行实际I/O操作.对于存放在磁盘上的普通文件,用标准I/O打开时默认时全缓冲的.

**行缓冲:**当在输入和输出时遇到换行符时执行I/O操作。标准输入流和标准输出流使用行缓冲的典型例子。

无缓冲:不对I/O操作进行缓冲,即在对流的读写时会立刻操作实际的文件.

标准I/O

标准I/O:核心对象是流,每打开一个文件,就会创建FILE结构体描述该文件,我们把FILE称为流,标准I/O函数都基于流进行各种操作。只要操作系统安装了C库,标准I/O函数就可以调用,具有更好的移植性好。可以减少系统调用(缓冲机制)的次数,提高系统效率。

标准I/O的文件操作:

打开成功返回文件流指针,失败则返回NULL。->FILE * fopen (const char *path, const char *mode);

参数1:文件名,参数2:文件的打开方式,打开方式有6种,文件存在时打开只读/可读写文件。文件不存在时,清空打开/追加打开只读/可读写文件。

关闭文件:fclose(文件流指针)

二进制文件

文本文件

读写:读写的方式有4种:

  1. 按字符读写文件,读文件 int fgetc(FILE *stream);写文件int fputc(int c,FILE *stream);

  2. 按行读写文件,读文件char *fgets(char *s,int size ,FILE *stream),写文件int fputs(const char *s,FILE *stream)

  3. 按对象读写文件,

    fwrite()参数1:要写入对象的首地址,参数2:对象的大小,参数3:对象的个数,参数4:文件流指针,返回值:实际写入的对象个数,

    fread()参数1:缓冲区首地址、对象大小、对象个数、文件流指针。

  4. 按格式化读写文件:

    文件写入:fprintf();参数1:文件流指针,参数2:格式,参数3:写入的数据名

    文件读取:fscanf()读取文件,参数1:文件流指针,参数2:格式“%d”,参数3:缓冲区地址int a,这块就要写&a。

文件I/O每次操作都会执行系统调用,好处:直接读写实际文件.坏处:频繁的系统调用会增加系统开销.,每打开一个文件系统会自动分配一个文件描述符文件描述符,(一个非负整数)

**使用标准I/O的方式:**创建文件流指针(FILE *),以某种权限打开文件,选择以某种方式读写文件,创建某种方式的缓冲区,调用某种文件读写的函数,关闭文件。

标准I/O相关函数

feof(文件流指针),可判断二进制文件是否到达末尾。字符是0-127的ASCII码。还有一个判断文本流的方式EOF,文件末尾或发生读错误,则返回 EOF

fflush()手动清理缓冲区

ftell()获得文件流的读写位置

fseek()移动文件流的读写位置:fseek(fp,0,SEEK_SET); //将文件流的读写位置移动的文件开头,SEEK_CUR:当前位置,SEEK_END:文件末尾。

rewind() 重设文件流的读写位置为文件开头

文件I/O

分为普通文件操作与目录文件操作

打开文件open(文件名,打开方式,设置文件操作权限),打开方式有8种,返回值为系统分配的文件描述符,失败返回-1

关闭文件colse(文件描述符)

打开目录文件 DIR *opendir(const char *name)

读取目录文件struct dirent *readdir(DIR * dirp)

关闭目录文件 int closedir(DIR *dirp)

静态库与动态库

库是写好的,现有的,成熟的,可以复用的代码。本质上来说,库是一种可执行代码的二进制形式,可以被操作系统载入内存执行。库有两种:静态库(.a、.lib)和动态库(.so、.dll)。所谓静态、动态是指链接。库文件是事先编译好的方法的合集。

静态库在编译时会直接整合到目标程序中,编译成功的可执行文件可独立运行;动态库在编译时不会放到连接的目标程序中,即可执行文件无法单独运行。

静态库和动态库最本质的区别就是:该库是否被编译进目标(程序)内部。

静态库
优点,静态库被打包到应用程序中加载速度快,发布程序无需提供静态库,移植方便

①静态库被打包到应用程序中加载速度快
②发布程序无需提供静态库,移植方便

缺点:相同的库文件数据可能在内存中被加载多份,消耗系统资源,浪费内存,库文件更新需要重新编译项目文件,生成新的可执行程序,浪费时间。

动态库
优点:可实现不同进程间的资源共享,动态库升级简单,只需要替换库文件,无需重新编译应用程序,可以控制何时加载动态库,不调用库函数动态库不会被加载

缺点:加载速度比静态库慢,发布程序需要提供依赖的动态库

进程、线程与程序的区别

程序:程序是静态的,它是一些保存在磁盘上的指令的有序集合,没有任何执行的概念

进程:进程是动态的,它是程序执行的过程,正在运行的程序,进程是程序执行和资源管理的最小单位 , 每个进程都有自己独立的一块内存空间,一个进程可以有多个线程 。

进程与线程的区别总结

根本区别是进程是操作系统资源分配的基本单位,而线程是处理器任务调度和执行的基本单位。

资源开销方面每个进程都有独立的代码和数据空间,每个线程都有自己独立的运行栈和程序计数器,线程之间切换的开销小。一个进程内可以有多个线程,多条线共同完成;线程是进程的一部分。在内存分配方面,同一进程的线程共享本进程的地址空间和资源,而进程之间的地址空间和资源是相互独立的。

如果一个进程发生崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃会导致整个进程都死掉。所以多进程要比多线程健壮。原因是每个独立的进程有程序运行的入口、顺序执行序列和程序出口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制,两者均可并发执行。

并发与并行???

进程与线程的使用创建和销毁较频繁使用线程,因为创建进程花销大。需要大量数据传送使用线程,因为多线程切换速度快,不需要跨越进程边界。安全稳定选进程;快速频繁选线程;

进程

进程可分为:交互式进程(经常使用),可以在前台运行,也可以在后台运行 ;批处理进程,这类进程不必与用户进行交互,因此通常在后台运行;守护进程(重点)。这类进程一直在后台运行,和任何终端都不关联。通常系统启动时开始执行,系统关闭时才结束。

5种状态(R(SD)T X Z): R 进程当前正在运行,或者正在运行队列中等待调度(就绪态)称为运行态等待态分为:可中断的等待 S sleep(1)与 不可中断的等待 D;停止态 是T进程的执行被暂停,当进程收到SIGSTOP,SIGTSTP,SIGTTIN,SIGTTOU等信号,都会被停止,死亡态 是最终状态, 僵尸态是一个错误,需要去处理。

创建进程的方式有两种:pid_t fork() pid_t 大于0为父进程,返回子进程id,等于0为子进程,小于0为进程创建失败。

vfork()与fork()

**fork()**是子进程拷贝父进程的数据段,代码段,父子进程的执行次序不确定。**vfork()**是子进程与父进程共享数据段,保证子进程先运行,在调用exec或exit之前与父进程数据是共享的,在它调用exec或exit之后父进程才可能被调度运行。

如果在调用这两个函数之前子进程依赖于父进程的进一步动作,则会导致死锁。当需要改变共享数据段中变量的值,则拷贝父进程

进程退出是:exit(0)自带清理缓冲区和_exit(0)不清理缓冲区

回收资源有两种wait(NULL)与waitpid()

一是阻塞等待子进程结束回收资源:wait(NULL),忽略进程退出状态

二是waitpid() 回收子进程资源, 参数1:pid =-1 等待任意一个子进程退出,此时和wait()作用一样,参数2是判断子进程是否正常结束,或者由信号结束 ,获得正常结束退出的状态、获得信号的值,这是4个不同的参数。参数3:如果是0 同wait()一样,阻塞父进程等待子进程退出,WNOHANG: 表示非阻塞等待,若无子进程退出,返回值0,有的话返回值是子进程号。

**wait(NULL) < ====> waitpid(-1,NULL,0)**的意思是阻塞等待任意一个子进程退出,忽略子进程的退出状态

获得进程的id号getpid,获得父进程的id号getppid

(3)停止态 T 进程的执行被暂停,当进程收到SIGSTOP,SIGTSTP,SIGTTIN,SIGTTOU等信号,都会被停止死亡态 X这是最终状态

线程:进程中的一个执行任务(控制单元),负责当前进程中程序的执行。一个进程至少有一个线程,一个进程可以运行多个线程,多个线程可共享数据。

exec函数族

使用exec函数族的目的是:子进程需要运行的程序被单独编写、单独编译连接成一个可执行程序,主程序为父进程,fork创建了子进程后在子进程中exec来执行,达到父子进程分别做不同程序同时(宏观上)运行的效果。

守护进程:守护进程是Linux中三大进程之一,始终运行在后台,通常系统开启时运行,系统关闭时结束。独立于控制终端(与终端无关),周期性执行某种任务或等待某些发生的事件。

创建守护进程的方式

1 创建子进程,父进程退出 (子进程成为后台进程)2 设置新的会话3 设置工作目录4 重设文件掩码5 关闭从父进程继承下来的文件描述符

进程间的通信方式

传统的通信方式

管道可以看成一种特殊的文件,对于它的读写我们实用文件IO中read和write

**无名管道:**只能用于具有亲缘关系(父子进程/兄弟进程)的进程之间的通信,速度慢,容量有限。半双工的通信方式,具有固定的读端fd[0]和写端fd[1].

**有名管道:**有名管道是对无名管道的改进,它可以使互不相关的两个进程互相通信,并且在文件系统中可见,可以通过文件名来找到。半双工的通信方式,进程通过文件IO来操作有名管道。有名管道遵循先进先出原则,不支持lseek()

信号:信号是一种异步通信方式:**分为三种:**硬件产生(组合键)信号 、内核发送信号 管道断裂(SIGPIPE) alarm() 闹钟 SIGALRM 等。 软件方式产生信号 ,raise() 自己给自己发送信号,kill() 给别人发送信号

systemV进程间通信方式

消息队列 1.消息队列由消息队列ID来唯一标识,就是一个消息的列表。用户可以在消息队列中添加消息、读取消息等。可以按照类型来发送/接收消息,相同类型先入先出,不同类型随意存取。全双工的一个通信方式

共享内存是一种最为高效的进程间通信方式,进程可以直接读写内存,而不需要任何数据的拷贝

为了在多个进程间交换信息,内核专门留出了一块内存区,可以由需要访问的进程将其映射到自己的私有地址空间,进程就可以直接读写这一内存区而不需要进行数据的拷贝,从而大大提高的效率。由于多个进程共享一段内存,因此也需要依靠某种同步机制,如互斥锁和信号量等

线程

创建线程:

pthread_t pid1,pid2;

函数是pthread_create(),有4个参数,线程号指针,线程的属性,函数(执行的东西???)的指针,主线程给子线程传递的值

等待子线程结束:pthread_join(pid1,NULL);

线程的退出,一是主动退出pthread_exit(NULL)为空表示线程退出不带值,二是被动取消pthread_cancel(pthread_t id)

char *ps;

线程间通信

一是子线程给主线程传值,通过pthread_create()的参数4,二是pthread_exit()和pthread_join()配合使用,通过pthread_exit(ps);参数不为空退出时,带值给子线程,pthread_join(pid,(void**)&ps)

线程的互斥与同步

**互斥:**多线程不允许同时访问临界资源

引入互斥(mutual exclusion)锁的目的是用来保证共享数据操作的完整性。

互斥锁主要用来保护临界资源

每个临界资源都由一个互斥锁来保护,任何时刻最多只能有一个线程能访问该资源。

线程必须先获得互斥锁才能访问临界资源,访问完资源后释放该锁。如果无法获得锁,线程会阻塞直到获得锁为止。

使用步骤:定义锁(pthread_mutex_t mymutex)、 初始化锁(pthread_mutex_init(&mymutex,NULL))、申请锁(pthread_mutex_lock(&mymutex))、释放锁(pthread_mutex_unlock(&mymutex))

同步: 多线程在访问临界资源时,按照一定的操作顺序来访问。

信号量:有名信号量: 多进程实现同步、 无名信号量: 多线程同步、信号灯集: 多进程实现同步

信号量代表某一类资源,其值表示系统中该资源的数量,信号量的值为非负整数,它被用来控制对公共资源的访问。

信号量是一个受保护的变量,只能通过三种操作来访问

初始化 sem_init()

P操作(申请资源)-1 sem_wait() sem_trywait(),在信号量大于零时,他们都能将信号量值减1.若信号量值为零时,阻塞线程

V操作(释放资源)+1 sem_post(),将信号量的值加1,同时唤醒等待的线程。

使用步骤:定义信号量(sem_t sem1、sem_t sem1)、初始化信号量sem_init(&sem1,0,1);参数1:信号量指针,参数2:线程间用0,参数三:信号量初始值。

销毁信号量,通过P(sem_wait(&sem1))、V(sem_post(&sem1))使用

网络编程

网络七层模型:应用层(http、tftp、)、表示层、会话层、传输层、网络层、数据链路层、物理层

Socket :socket需要一种通用的网络编程接口,它是一个特殊的文件描述符,(read,write,close)

socket类型:

流式套接字(SOCK_STREAM) ----TCP

提供了一个面向连接、可靠的数据传输服务,数据无差错、无重复的发送且按发送顺序接收。内设置流量控制,避免数据流淹没慢的接收方。数据被看作是字节流,无长度限制。

数据报套接字(SOCK_DGRAM)—>UDP

提供无连接服务。数据包以独立数据包的形式被发送,不提供无差错保证,数据可能丢失或重复,顺序发送,可能乱序接收。

原始套接字(SOCK_RAW)

可以对较低层次协议如IP、ICMP直接访问。

IP地址 : A类地址: 1个字节网络地址+3个字节主机地址,B类地址:2个字节网络地址+2个字节主机地址, C类地址:3字节网络地址+1个字节主机地址 ,D类地址用于组播,E类广播地址

端口号

字节序:小端序:低字节在低位,大端序:低字节在高位

套接字的使用方式

创建socket 、指定传输地址与端口号、监听套接字listen、客户端去连接、连接成功、关闭双方套接字

C++

关键字

explicit关键字的作用是 防止类的构造函数有一个参数,并且这个参数不是类类型的引用的时候进行隐式类型转换!

使用final修饰的类不可以被继承;使用final修饰类的虚函数,子类中是不可以进行重写的!

inline内联函数 内联函数只是我们对编译器的建议! 逻辑简单,代码短小 ,频繁使用

mutable关键字:解决const修饰的成员函数中不能修改数据成员值的问题

volatile关键字:防止编译器优化

C++的编译过程

预处理 -i、编译 -s、汇编 -o、链接 exe 或者 .a或者.lib

static修饰类的数据成员

类的静态的数据成员属于类,并不属于具体的某一个对象;但是类的所有对象都可以访问!

类的静态的数据成员要在类内做声明,类外做定义以及初始化!

类的静态的数据成员初始化的格式: 数据类型 类名::成员变量名=初始值;

建议在对应的cpp的开头去做!

类的静态的数据成员访问的方法:

通过对象的方式去访问: 对象名.变量名;

通过类的方式去访问: 类名::变量名;

static修饰类的成员函数

类的静态成员函数可以通过对象的方式去调用,也可以通过类名::的方式去调用

类的静态的成员函数是没有this指针的

类的静态成员函数是不能访问类的非静态的成员;

类的静态成员函数只能访问类的静态的成员

类的非静态成员能访问类的静态成员。

什么时候会把类的数据成员和成员函数用static修饰呢?

想要实现数据共享的,就可以把这个数据用static去修饰,要记得做初始化!

成员函数当要在多个类中都使用到这个函数,就可以把它用static修饰一下,变成静态的成员函数,这样在其他类中可以通过类名::成员函数();

const修饰类的成员

const修饰的数据成员不能在构造函数里面进行初始化;要在构造函数之前去做

如何初始化const修饰的数据成员:采用初始化列表的方式

总结:

类的非静态数据成员都可以使用初始化列表进行初始化。

类的const修饰的数据成员必须使用初始化列表进行初始化。

初始化列表给数据成员初始化的顺序先后是不会影响结果的!

const修饰类的成员函数

const修饰成员函数,本质上修饰的是函数中隐藏的那个this指针。

const修饰的成员函数可以有一个非const修饰的同名函数和他互为重载

const修饰的成员函数只能访问数据成员,并不能修改数据成员的值

const修饰的成员函数是不可以调用非const修饰的成员函数

非const修饰的成员函数能调用const修饰的成员函数。

const修饰的成员函数叫做常函数

const修饰类对象

const修饰的类对象是不可以调用非const修饰的成员函数;可以调用const修饰的成员函数的!

const修饰的对象是常对象,只能调用常函数!

C++内存管理

在C++中,内存分成5个区,他们分别是堆、栈、自由存储区、全局/静态存储区和常量存储区。

**栈,**在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。

,就是那些由new分配的内存块,他们的释放编译器不去管,由我们的应用程序去控制,一般一个new就要对应一个delete。如果程序员没有释放掉,那么在程序结束后,操作系统会自动回收。

自由存储区,就是那些由malloc等分配的内存块,他和堆是十分相似的,不过它是用free来结束自己的生命的。

全局/静态存储区,全局变量和静态变量被分配到同一块内存中,在以前的C语言中,全局变量又分为初始化的和未初始化的,在C++里面没有这个区分了,他们共同占用同一块内存区。

常量存储区,这是一块比较特殊的存储区,他们里面存放的是常量,不允许修改。

C++动态内存分配——new和malloc的用法及区别

malloc()向系统申请分配size字节的内存空间,返回类型为void*类型;

malloc函数使用注意:
1)、malloc返回的是不确定类型的指针,因此在返回前需要做强制类型转换,否则将编译错误;

2)、malloc只管分配内存,并不会初始化,其内存空间中的值可能是随机的。如果分配的这块空间原来没有被使用过,那么其中每个值都可能是0。相反,空间里面可能遗留各种各样的值。

3)、实参为需要分配的字节大小,如果malloc(1),那么系统只分配了1个字节的内存空间,这时注意,如果在这块空间中存放一个int值,由于int类型占4个字节,那么还有3个字节未分配空间,系统就会在已经分配的那1个字节的基础上,依次向后分配3个字节空间,而这就占有了“别人”的3个字节空间,“别人”原有的值就被清空了。

4)、分配的空间不再使用时,要用free函数释放这块内存空间。

5)、释放的是指针指向的内存空间而不是指针本身,释放后指针仍然存在,(诸如像指针这种变量,只有在程序结束时才会被销毁)

malloc函数工作机制
1)malloc函数被调用时,malloc函数沿空闲链表(存在于内存的堆里)寻找一个满足需求的内存块,然后把所需大小的内存块分配给用户,剩下的返回到链表上。

2)调用free函数时,它将用户释放的内存块连接到空闲链上。

3)在malloc函数被调用或多次调用后,空闲链表会被分成很多小的内存片段,当用户申请一块较大的内存空间时,空闲链表上可能没有满足需求的内存块了,这时,malloc函数请求延时,并将空闲链表内的小内存片段整理成大的内存块,最终返回。

4)如果无法获得符合要求的内存块,malloc函数会返回NULL指针,因此在调用malloc动态申请内存块时,一定要进行返回值的判断。

new和delete

在程序执行期间,申请用于存放T类型对象的内存空间,并依初始值列表赋以初值。

结果:
成果-》T类型的指针指向新分配的内存;
失败-》抛出异常。

两者区别

1、属性
new和delete是C++关键字,需要编译器支持;malloc和free是库函数,需要头文件支持。

2、参数
使用new操作符申请内存分配时无须指定内存块的大小,编译器会根据类型信息自行计算。而malloc则需要显式地指出所需内存的尺寸。

3、返回类型
new操作符内存分配成功时,返回的是对象类型的指针,类型严格与对象匹配,无须进行类型转换,故new是符合类型安全性的操作符。而malloc内存分配成功则是返回void * ,需要通过强制类型转换将void*指针转换成我们需要的类型。

4、自定义类型
new会先调用operator new函数,申请足够的内存(通常底层使用malloc实现)。然后调用类型的构造函数,初始化成员变量,最后返回自定义类型指针。delete先调用析构函数,然后调用operator delete函数释放内存(通常底层使用free实现)。

malloc/free是库函数,只能动态的申请和释放内存,无法强制要求其做自定义类型对象构造和析构工作。

5、重载
C++允许自定义operator new 和 operator delete 函数控制动态内存的分配。

6、内存区域
new做两件事:分配内存和调用类的构造函数,delete是:调用类的析构函数和释放内存。而malloc和free只是分配和释放内存。

new操作符从自由存储区(free store)上为对象动态分配内存空间,而malloc函数从堆上动态分配内存。自由存储区是C++基于new操作符的一个抽象概念,凡是通过new操作符进行内存申请,该内存即为自由存储区。而堆是操作系统中的术语,是操作系统所维护的一块特殊内存,用于程序的内存动态分配,C语言使用malloc从堆上分配内存,使用free释放已分配的对应内存。自由存储区不等于堆,如上所述,布局new就可以不位于堆中。

7、分配失败
new内存分配失败时,会抛出bac_alloc异常(要用try-catch)。malloc分配内存失败时返回NULL。

8、内存泄漏
内存泄漏对于new和malloc都能检测出来,而new可以指明是哪个文件的哪一行,malloc确不可以。

面向对象的三大特性

封装:

把一类事物的属性和行为提取出来,组合在一起,用类这种 自定义的数据类型把他们包起来 隐藏对象的属性和实现细节,仅对外公开接口和对象进行交互 。

使用的类时候进行实例化。实例化对象分配内存的方式是字节对齐,可以实例化多个对象,且每个对象都有一份自己的数据成员,但是成员函数在内存中只占一块空间,它们属于这个类,不属于某一个对象,所有对象都可以使用的,是共享的。

继承:

在原有类的基础上进行扩展,也可以将一些事物的相同属相或方法封装为一个类,不同的部分通过继承进行扩充实现。

多态:

静态多态是指在编译阶段就能知道执行哪一个函数体 运算符重载与函数重载

动态多态是在运行阶段才能知道执行哪一个函数体 。通过虚函数表实现,虚函数表的实现原理是, 函数指针数组 ,保存的类中虚函数的地址,使用时保存数组的起始地址 。

解决了继承中父类指针或者引用操作子类对象,只能操作子类从父类继承过来成员。

在继承关系中,子类重写父类虚函数,通过父类的引用或者是指针去操作子类对象,去调用虚函数的时候才会触发多态

C++中的结构体与C++中类的区别?

关键字是不同的,类用class,结构体用 struct;

类中的成员默认访问权限是private的,而结构体中的成员默认是public

Private的成员在类的外部是不可访问的;public的成员在外部是可以访问的!:

类内部的成员的访问权限有三种:

private: 私有的, 在类内是可以访问的,在类外是不可以访问的

public: 公有的 在类内以及类外都是可以访问的!

protected: 受保护的!

什么是类外:{}之外,都是类外

什么是类内:{}之内,都是类内

类定义变量的初始化的方式不能按照结构体定义变量初始化的方式进行初始化

类定义的变量的不叫变量,叫对象

结构体定义的变量就是叫变量!

:创建类的目的是为了提高代码的复用性,使程序变得模块化

构造函数可以使用虚函数吗?为什么?

不能,构造函数的作用是,给对象的数据成员分配内存空间并且做初始化的操作。系统自动帮我们执行。如果我们自己没有写构造函数,系统会帮我们自己动生成一个空的构造函数。写 成虚函数后系统不知道初始化谁。

析构函数可以使用虚函数吗?为什么?

可以,父类指针释放子类对象时,如果子类对象实例化开辟了空间,父类是没有办法释放子类空间的,这样会造成内存泄漏,内存泄漏是指申请了空间却不释放。

类与类之间数据成员共享

全局变量。类提供接口。友元机制。定义静态数据成员。

c++ 类受保护成员的访问: 1:将类定义成友元类。2:将类定义成要访问类的基类(即继承要访问类)

抽象类与接口类的区别

**抽象类:**至少有一个纯虚函数,可以有实现的方法,可以进行变量定义

**接口类:**所有的函数必须被声明为纯虚函数,没有变量声明

受保护的类成员如何访问

1、将受保护的类作为基类

2、通过友元访问

指针与引用的区别

指针是用来保存地址的,引用是用来给变量起别名的,指针会给变量分配空间,引用不会给变量分配空间,指针可以初始化,也可以不初始化,避免野指针一般初始化为NILL,引用必须初始化,指针指向的地址可以发生变化,引用初始化后不可以更改。

引用本身的目的是为了弱化指针,但是指针有多级指针,引用没有

多态

静态多态:

通过运算符重载与函数重载实现:

运算符重载:有两种实现方式,

成员函数重载:

数据类型 operator 运算符(操作数){}

友元函数重载:

函数类型 operator 运算符(操作数){}

函数重载:

函数名相同,功能相似,参数不同(参数的个数不同、参数类型不用、参数类型的顺序不用),与返回值类型无关的一组函数构成重载!

动态多态:

在C++中,如果一个类中有虚函数,那么编译器就会为该类生成一个虚表(virtual table),虚表中存储了所有虚函数的地址。当对象被创建时,会在对象中分配一个指针指向该类的虚表。

当调用该类的虚函数时,通过对象指针或引用找到该对象所属的虚表,并根据虚函数的索引在虚表中查找对应的函数地址,然后进行函数调用。因为虚表中存储的是函数地址,所以可以在运行时动态地决定调用哪个函数,从而实现动态多态性。

动态多态的实现条件:

必须有继承,一个父类,多个子类

父类中要有虚函数,子类要重写父类的虚函数

必须通过父类的引用或者是指针去操作子类对象,去调用虚函数的时候才会触发多态,实现动态的多态!

动态多态的实现原理

虚函数表, 也就是一个表里面可以保存多个虚函数的地址!

映射到我们的代码中: 函数指针数组。一个类中一旦有了虚函数,这个类就会有一个函数指针数组存在!函数指针数组中存放的就是这个类中虚函数的地址!

创建对象的时候,就会给函数指针数组分配空间,我们只需要保存数组名即可!也就是只要分配4字节(32位)的内存空间就可以了!保存数组的起始地址。

调用虚函数,就是去当前这个类的虚函数表中去找对应的函数,函数地址不同,执行的就是不同的函数体!

C++为什么不执行子类的析构函数,就会造成内存泄漏?

当通过父类指针去释放子类对象,发现析构函数只执行了父类的析构,并没有执行子类的析构,导致创建子类对象的时候,有在子类的构造中采用new的方式去动态分配空间,不执行析构,就没有办法释放对应的堆区空间

虚函数与纯虚函数

定义一个函数为虚函数,不代表函数为不被实现的函数。

定义他为虚函数是为了允许用基类的指针来调用子类的这个函数。

定义一个函数为纯虚函数,才代表函数没有被实现。

定义纯虚函数是为了实现一个接口,起到一个规范的作用,规范继承这个类的程序员必须实现这个函数。

纯虚函数是在基类中声明的虚函数,它在基类中没有定义,但要求任何派生类都要定义自己的实现方法。在基类中实现纯虚函数的方法是在函数原型后加 =0:

纯虚函数最显著的特征是:它们必须在继承类中重新声明函数(不要后面的=0,否则该派生类也不能实例化),而且它们在抽象类中往往没有定义。

定义纯虚函数的目的在于,使派生类仅仅只是继承函数的接口。

纯虚函数的意义,让所有的类对象(主要是派生类对象)都可以执行纯虚函数的动作,但类无法为纯虚函数提供一个合理的默认实现。所以类纯虚函数的声明就是在告诉子类的设计者,“你必须提供一个纯虚函数的实现,但我不知道你会怎样实现它”。

抽象类是一种特殊的类,它是为了抽象和设计的目的为建立的,它处于继承层次结构的较上层。

(1)抽象类的定义: 称带有纯虚函数的类为抽象类。

(2)抽象类的作用: 抽象类的主要作用是将有关的操作作为结果接口组织在一个继承层次结构中,由它来为派生类提供一个公共的根,派生类将具体实现在其基类中作为接口的操作。所以派生类实际上刻画了一组子类的操作接口的通用语义,这些语义也传给子类,子类可以具体实现这些语义,也可以再将这些语义传给自己的子类。

map

底层的实现原理是红黑树,有key与value构成,key值唯一

红黑树:为了解决二叉树出现的极端问题,引入了颜色属性,是一种特殊的平衡二叉树。

访问方式:迭代器访问

智能指针

底层原理引用计数, 智能指针在申请堆内存空间的同时,会为其配备一个整形值(初始值为 1),每当有新对象使用此堆内存时,该整形值 +1;反之,每当使用此堆内存的对象被释放时,该整形值减 1。当堆空间对应的整形值为 0 时,即表明不再有对象使用它,该堆空间就会被释放掉。

目的:可以帮我们把new出来的空间做自动的释放!

共享智能指针

shared_ptr: 同时可以有多个智能指针变量指向同一块堆区的内存空间!

当其中的一个智能指针变量生命周期结束了,并不会影响这个堆区空间的释放,只有所有指向这块堆区空间的智能指针变量生命周期全部都结束了,对应的堆区空间也会被自动的释放掉!

独享智能指针

unique_ptr:同一时间只能有一个独享智能指针指向这块堆区空间,独享智能指针的生命周期结束了,对应的堆区空间也就被释放了!

弱型智能指针

weak_ptr:弱型智能指针操作堆区空间是没用的,必须和共享智能指针配合使用,弱型智能指针生命周期结束不影响堆区空间,只有共享智能指针生命周期结束,对应堆区空间也会被释放掉!

右值相关操作

编译器允许我们为 num 左值建立一个引用,但不可以为 10 这个右值建立引用。

虽然 C++98/03 标准不支持为右值建立非常量左值引用,但允许使用常量左值引用操作右值。也就是说,常量左值引用既可以操作左值,也可以操作右值 。

C++11 标准新引入了另一种引用方式,称为右值引用,用 “&&” 表示。

一般用于 移动语义和完美转发

移动语义: 是指将一块内存单元(可以是变量的内存单元也可以是临时对象的内存单元)从一个对象转移到另一个对象。

std::move:唯一的功能是将一个左值强制转化为右值引用,继而可以通过右值引用使用该值,以用于移动语义。从实现上讲,move基本等同于一个类型转换。

C++多线程—— thread

C++11提供了语言层面上的多线程,包含在头文件中。它解决了跨平台的问题,提供了管理线程、保护共享数据、线程间同步操作、原子操作等类。C++11 新标准中引入了5个头文件来支持多线程编程。

创建线程:将函数添加到线程中

join()和detach()的理解

**join()**函数是一个等待线程完成函数,主线程需要等待子线程运行结束了才可以结束

**detach()**函数称为分离线程函数,使用detach()函数会让线程在后台运行,说明主线程不会等待子线程运行结束才结束。

**守护线程(**daemon threads),UNIX中守护线程是指,没有任何显式的用户接口,并在后台运行的线程。这种线程的特点就是长时间运行;线程的生命周期可能会从某一个应用起始到结束,可能会在后台监视文件系统,还有可能对缓存进行清理,亦或对数据结构进行优化。另一方面,分离线程的另一方面只能确定线程什么时候结束,发后即忘(fire andforget)的任务就使用到线程的这种方式

this_thread是一个类

函数使用说明
get_idstd::this_thread::get_id()获取线程id
yieldstd::this_thread::yield()放弃线程执行,回到就绪状态
sleep_forstd::this_thread::sleep_for(std::chrono::seconds(1));暂停1秒
sleep_until如下一分钟后执行吗,如下

C++多线程——mutex

类型说明
std::mutex最基本的 Mutex 类。
std::recursive_mutex递归 Mutex 类。
std::time_mutex定时 Mutex 类。
std::recursive_timed_mutex定时递归 Mutex 类。

std::mutex 是C++11 中最基本的互斥量,std::mutex 对象提供了独占所有权的特性——即不支持递归地对 std::mutex 对象上锁,而 std::recursive_lock 则可以递归地对互斥量对象上锁。

mutex常用操作:

  • lock():资源上锁
  • unlock():解锁资源
  • trylock():查看是否上锁,它有下列3种类情况:

(1)未上锁返回false,并锁住;

(2)其他线程已经上锁,返回true;

(3)同一个线程已经对它上锁,将会产生死锁。

死锁:是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。

同一个mutex变量上锁之后,一个时间段内,只允许一个线程访问它。

lock_guard

创建lock_guard对象时,它将尝试获取提供给它的互斥锁的所有权。当控制流离开lock_guard对象的作用域时,lock_guard析构并释放互斥量。lock_guard的特点:

  • 创建即加锁,作用域结束自动析构并解锁,无需手工解锁
  • 不能中途解锁,必须等作用域结束才解锁
  • 不能复制

unique_lock

简单地讲,unique_lock 是 lock_guard 的升级加强版,它具有 lock_guard 的所有功能,同时又具有其他很多方法,使用起来更加灵活方便,能够应对更复杂的锁定需要。unique_lock的特点:

  • 创建时可以不锁定(通过指定第二个参数为std::defer_lock),而在需要时再锁定
  • 可以随时加锁解锁
  • 作用域规则同 lock_grard,析构时自动释放锁
  • 不可复制,可移动
  • 条件变量需要该类型的锁作为参数(此时必须使用unique_lock)

所有 lock_guard 能够做到的事情,都可以使用 unique_lock 做到,反之则不然。那么何时使lock_guard呢?很简单,需要使用锁的时候,首先考虑使用 lock_guard,因为lock_guard是最简单的锁。

wait

当前线程调用 wait() 后将被阻塞(此时当前线程应该获得了锁(mutex),不妨设获得锁 lck),直到另外某个线程调用 notify 唤醒了当前线程。在线程被阻塞时,该函数会自动调用 lck.unlock() 释放锁,使得其他被阻塞在锁竞争上的线程得以继续执行。另外,一旦当前线程获得通知(notified,通常是另外某个线程调用 notify 唤醒了当前线程),wait()函数也是自动调用 lck.lock(),使得lck的状态和 wait 函数被调用时相同。

wait_for

与std::condition_variable::wait() 类似,不过 wait_for可以指定一个时间段,在当前线程收到通知或者指定的时间 rel_time 超时之前,该线程都会处于阻塞状态。而一旦超时或者收到了其他线程的通知,wait_for返回,剩下的处理步骤和 wait()类似。

wait_for 的重载版本的最后一个参数pred表示 wait_for的预测条件,只有当 pred条件为false时调用 wait()才会阻塞当前线程,并且在收到其他线程的通知后只有当 pred为 true时才会被解除阻塞。

线程池

在一个程序中,如果我们需要多次使用线程,这就意味着,需要多次的创建并销毁线程。而创建并销毁线程的过程势必会消耗内存,线程过多会带来调动的开销,进而影响缓存局部性和整体性能。线程的创建并销毁有以下一些缺点:

  • 创建太多线程,将会浪费一定的资源,有些线程未被充分使用。
  • 销毁太多线程,将导致之后浪费时间再次创建它们。
  • 创建线程太慢,将会导致长时间的等待,性能变差。
  • 销毁线程太慢,导致其它线程资源饥饿。

线程池维护着多个线程,这避免了在处理短时间任务时,创建与销毁线程的代价。

线程池的实现

线程池ThreadPool类包含了一个线程向量threads、一个任务队列tasks、一个互斥锁mutex和一个条件变量cv。构造函数ThreadPool(size_t num_threads)会创建num_threads个线程,并将它们加入到线程向量threads中。每个线程会无限循环,如果任务队列tasks为空且线程池已经被停止,则线程会退出循环,否则会从任务队列tasks中取出一项任务并执行。任务的类型是std::function<void()>,这种类型可以表示任何可调用对象,包括函数指针、函数对象、Lambda表达式等。

析构函数~ThreadPool()会将线程池设置为停止状态,并通知所有线程退出循环。然后等待所有线程退出,最后销毁线程池。

addTask()方法用于向任务队列tasks中添加一个任务。它首先使用std::unique_lock[std::mutex](javascript:void(0))对互斥锁mutex进行加锁,然后将任务放入任务队列中,最后使用std::condition_variable::notify_one()通知一个等待中的线程有新的任务可执行。注意,这里使用了std::forward()来进行完美转发,从而避免了不必要的拷贝操作。

测试代码中,我们创建了一个包含8个任务的线程池,并使用addTask()方法将每个任务添加到任务队列中。每个任务都会打印自己的ID和线程ID。

#include <iostream>
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>

class ThreadPool {
public:
    ThreadPool(size_t num_threads) {
        for (size_t i = 0; i < num_threads; ++i) {
            threads.emplace_back([this] {
                while (true) {
                    std::unique_lock<std::mutex> lock(mutex);
                    cv.wait(lock, [this] { return !tasks.empty() || stop; });
                    if (stop && tasks.empty()) {
                        return;
                    }
                    auto task = std::move(tasks.front());
                    tasks.pop();
                    lock.unlock();
                    task();
                }
            });
        }
    }

    ~ThreadPool() {
        {
            std::unique_lock<std::mutex> lock(mutex);
            stop = true;
        }
        cv.notify_all();
        for (auto& thread : threads) {
            thread.join();
        }
    }

    template <typename F>
    void addTask(F&& task) {
        {
            std::unique_lock<std::mutex> lock(mutex);
            tasks.emplace(std::forward<F>(task));
        }
        cv.notify_one();
    }

private:
    std::vector<std::thread> threads;
    std::queue<std::function<void()>> tasks;
    std::mutex mutex;
    std::condition_variable cv;
    bool stop = false;
};

// 测试代码
void printNum(int num) {
    std::cout << "Thread id: " << std::this_thread::get_id() << ", num = " << num << std::endl;
}

int main() {
    ThreadPool pool(4);
    for (int i = 0; i < 8; ++i) {
        pool.addTask([i] { printNum(i); });
    }
    return 0;
}

4种类型转换函数

转换类型操作 作用
const_cast 只有一种用途,去掉类型的const或volatile属性
static_cast 无条件转换,静态类型转换(类似强转)
dynamic_cast 子类转父类。有条件转换,动态类型转换,运行时检查类型安全(转换失败返回NULL)
reinterpret_cast 仅重新解释类型,但没有进行二进制的转换

QT

QT内存管理机制

所有的QObject类对象及其派生类对象都可以通过指定父对象来托管内存,QWidget继承自QObject,所以它及其派生类对象能够使用内管管理机制。当父窗口释放时,会在析构函数中遍历自己所有的子窗口,将它们释放。

怎样理解QT事件、信号与槽

QT信号与槽(Signals and Slots)是QT框架中的一种实现模式,用于在不同对象之间进行通信和交互。它是一种基于事件驱动的通信机制,其中信号是一种事件,槽是一种响应信号的方法。

在QT中,可以通过定义信号和槽的方式,使得不同对象之间能够建立起联系,实现事件通知和处理。一般情况下,一个对象会定义一个或多个信号,当这个对象的内部状态发生变化时,会发出对应的信号,其他对象通过连接这个信号,就可以接收到通知。同时,其他对象也可以定义一个或多个槽,当接收到信号时,执行槽中的代码,完成对信号的处理。

QT中的信号和槽的实现机制是通过元对象系统(Meta-Object System)来实现的。元对象系统是QT框架中的一种运行时类型信息系统,可以对QT对象进行动态识别和访问,实现了一些C++所不支持的机制,如信号和槽、动态属性和元对象信息等。

通常情况下,通过connect函数将信号和槽连接起来。connect函数有两个参数,第一个参数是发出信号的对象指针,第二个参数是接收信号的对象指针,它们之间通过信号和槽建立联系。当发出信号时,会自动调用接收信号的对象的对应槽函数,从而完成对信号的处理。

总的来说,QT信号和槽是一种基于事件驱动的通信机制,它通过连接信号和槽,实现不同对象之间的通信和交互。这种机制使得编写QT程序更加灵活、可维护性更高,也是QT框架的一个重要特性。

信号与槽是用于对象之间的通信的,是 Qt 的核心机制。为此 Qt 引入了一些关键字,他们是slots、signals、emit (用户自定义的一个信号)

事件产生的有QEvent对象产生->event函数做事件分发->不同事件类型执行不同的事件处理函数->触发对应的信号->槽函数关联->执行对应的槽函数。

信号是在事件中被触发的

在限制编辑框输入时,本质是进行事件处理,自定义编辑框类,或者自定义任何一个类,继承QT原有的类,实现某一功能。

事件的类型与产生:

系统事件主要由用户操作输入,然后由系统驱动捕捉并放入到系统消息队列中,主要有鼠标点击键盘输入等事件

程序事件程序事件主要由程序自身产生,比如定时器事件、重绘事件等,是通过sendEvent(QObject *receiver, QEvent *event)->同步,事件不会被放入消息队列,等事件被处理完成后才返回;postEvent(QObject *receiver, QEvent *event)->异步的,事件会被放入事件队列中,然后立即返回.

一个窗口会有两个队列,其中一个队列保存系统事件,队列由系统维护;另一个是程序事件队列,主要保存程序本身产生的事件,由程序自身维护。

事件循环主要分两步:从程序事件队列取出事件,然后发送到指定的窗口或窗口部件,直至程序事件队列处理为空,处理系统事件队列中的事件,直至队列为空

实现方式: 通过多态来实现的

QEvent是所有事件类的父类,QWidget是所有与用户交互类的父类

描述Qt下Tcp通信的整个流程 :
服务器端: 创建用于监听的套接字 ->给套接字设置监听 ->如果有连接到来, 监听的套接字会发出信号newConnected ->接收连接, 通过nextPendingConnection()函数, 返回一个QTcpSocket类型的套接字对象(用于通信) ->使用用于通信的套接字对象通信 ->发送数据: write ->接收数据: readAll/read
客户端: 创建用于通信的套接字->连接服务器: connectToHost ->连接成功与服务器通信 ->发送数据: write -> 接收数据: readAll/read.

QT中的connect函数的五个参数

在QT中,connect函数是用来连接信号和槽的关键函数。它有五个参数,分别是:

  1. sender:发出信号的对象指针。可以是QObject派生类的指针,也可以是信号所在的对象的this指针。在使用sender时,需要注意信号所在的对象必须使用Q_OBJECT宏进行声明。
  2. signal:信号函数的指针。信号函数是在发生特定事件时自动调用的函数,它必须是一个无返回值的函数。signal参数的类型应该和信号函数的类型相同,可以使用SIGNAL宏将信号函数的函数名转换为字符串类型。
  3. receiver:接收信号的对象指针。接收信号的对象必须可以响应信号函数,即需要有与信号函数相同的函数声明。在使用receiver时,需要注意接收信号的对象必须使用Q_OBJECT宏进行声明。
  4. slot:槽函数的指针。槽函数是用来响应信号的函数,它的参数类型和个数应该与信号函数的参数类型和个数相同。slot参数的类型应该和槽函数的类型相同,可以使用SLOT宏将槽函数的函数名转换为字符串类型。
  5. type:连接类型。QT中有三种连接类型,分别是Qt::DirectConnection、Qt::QueuedConnection和Qt::AutoConnection。默认情况下,connect函数使用的是Qt::AutoConnection连接类型。

总的来说,通过connect函数可以将发出信号和接收信号的对象连接起来,实现信号和槽之间的通信和交互,是QT中一种非常有用的函数。

QT中如何使用线程

自定义一个线程类,这个类继承QThread类->重写run函数->实例化自定义线程类的对象->启动线程 start()会自动调用run函数->线程退出 quit->线程销毁 wait()

QMainWindow与QWidget

QMainWindow中在setUi时自动为用户创建了一个菜单栏、工具栏、中心窗口和状态栏。而QWidget是没有

QMainWindow从外到内依次是菜单栏、状态栏、工具栏、停靠窗口、中心窗口。

学习QT时使用过的控件

  1. QLabel:显示文本、图片等。
  2. QPushButton:按钮控件。
  3. QCheckBox:复选框控件。
  4. QRadioButton:单选按钮控件。
  5. QLineEdit:单行文本输入框。
  6. QTextEdit:多行文本输入框。
  7. QComboBox:下拉框控件。
  8. QSpinBox:数字选择框控件。
  9. QSlider:滑动条控件。
  10. QProgressBar:进度条控件。
  11. QTabWidget:选项卡控件。
  12. QTreeWidget:树形控件。
  13. QListWidget:列表控件。
  14. QCalendarWidget:日历控件。
  15. QDockWidget:浮动窗口控件。
  16. QMenu/QMenuBar/QAction:菜单和菜单项控件。
  17. QGraphicsView/QGraphicsScene/QGraphicsItem:图形控件。

QT中如何自定义一个控件

  1. 创建一个新类,继承QWidget或其子类,例如QLabel、QPushButton等。
  2. 在新类的头文件中声明需要的成员变量、函数和信号槽等。
  3. 实现构造函数和析构函数,在构造函数中初始化控件的属性和样式等。
  4. 在新类中重写需要的绘图函数(如paintEvent),实现自定义绘制。
  5. 在新类中处理鼠标、键盘等事件,实现响应用户输入的功能。
  6. 可以添加需要的属性、方法和信号槽等,方便其他代码调用。
  7. 编译项目,使用新类创建实例并显示在界面上。

上传视频到服务器要注意的问题

  1. 确认Linux服务器是否支持视频格式和编解码器

    在将视频上传到Linux服务器之前,需要确认Linux服务器是否支持该视频的格式和编解码器。如果不支持,上传的视频可能无法正常播放或者需要安装相应的解码器才能播放。可以通过在Linux服务器上使用ffprobeffmpeg等工具来查看视频的编码格式和参数。

  2. 确认文件上传的权限和路径

    在上传文件到Linux服务器时,需要确认文件上传的权限和路径。在Linux系统中,文件夹和文件的权限是非常重要的,不同的权限会影响文件的读写和执行。在上传视频文件之前,需要确保目标文件夹的权限和上传的视频文件的权限都是正确的。另外,还需要确认文件上传的路径是否正确,以免文件上传到错误的路径。

  3. 确认网络传输的稳定性和速度

    在视频上传过程中,需要确保网络传输的稳定性和速度。如果网络不稳定或者传输速度较慢,上传的视频文件可能会损坏或者上传时间较长。可以通过使用压缩文件的方式来减少上传时间,或者使用专业的网络传输工具来提高传输速度和稳定性。

  4. 确认Linux服务器的存储容量

    在上传视频文件之前,需要确保Linux服务器的存储容量足够。如果服务器的存储容量不足,上传的视频文件可能会失败。可以通过使用Linux系统的df命令来查看服务器的存储容量。

总的来说,将QT视频上传到Linux服务器需要注意视频格式和编解码器、权限和路径、网络传输的稳定性和速度以及服务器的存储容量等问题,以确保视频能够顺利上传并正常播放。

汇编语言

汇编语言中分支语句if…else与 switch 区别

switch是优于if…else的,原因是在case很多的情况下,switch会先判断default,然后 跳转表只用一次跳转即可完成整个判断过程,相对于if…else按固定顺序进行依次判断可省去很多指令。

Linux

shell

shell是一个命令行解释器,将用户命令解析为操作系统所能够识别的指令,从而实现用户与操作系统的交互

shell命令:是用户向系统内核发出控制请求,与之交互的文本流

shell脚本:一堆命令的集合

数据库

数据库的基本概念和原理,如ACID等;

数据库是指按照数据结构来组织、存储和管理数据的仓库,并且由于数据的多样性,数据库还需要提供对数据进行管理、维护和查询的相关功能。

ACID是指数据库事务的四个特性,即原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability)。

  1. 原子性(Atomicity):指事务是一个不可分割的工作单位,要么全部执行,要么全部不执行,如果在执行过程中出现错误,则所有操作都将回滚到事务开始前的状态。
  2. 一致性(Consistency):指事务执行前后,数据库从一个一致性状态到另一个一致性状态,不会破坏数据库的完整性和约束条件。
  3. 隔离性(Isolation):指多个事务并发执行时,每个事务都应当与其他事务隔离,一个事务的执行不应该对其他事务产生影响。
  4. 持久性(Durability):指事务完成后,对数据的修改应该被持久化到数据库中,即使发生系统崩溃或断电等意外事件,修改的数据也不会丢失。

除了ACID特性之外,数据库的其他基本概念和原理包括:数据库的范式理论,关系型数据库的模型和语言,数据库的索引和查询优化,数据库的备份和恢复,数据库的存储引擎,数据库的事务和锁机制,数据库的高可用方案等等。

C++中MySQL的安装和配置;

在C++中使用MySQL需要安装MySQL数据库系统,并安装相应的C++库文件,然后进行配置。下面是安装和配置MySQL的步骤:

  1. 下载并安装MySQL数据库系统。

    可以到MySQL官网(https://dev.mysql.com/downloads/mysql/)下载MySQL Community Server安装程序,选择合适的版本和操作系统,并按照提示安装。

  2. 安装MySQL C++ Connector库文件。

    在使用C++连接MySQL时,需要安装MySQL C++ Connector库文件。可以到MySQL官网(https://dev.mysql.com/downloads/connector/cpp/)下载对应的库文件。安装完成后,将库文件添加到C++项目中。

  3. 配置MySQL服务。

    安装完成后,需要配置MySQL服务。可以按照以下步骤进行配置:

    • 打开命令行窗口,输入mysql -u root -p命令登录MySQL服务器。
    • 输入MySQL管理员密码。
    • 输入CREATE USER 'username'@'localhost' IDENTIFIED BY 'password';命令创建新用户,并设置密码。
    • 输入GRANT ALL PRIVILEGES ON *.* TO 'username'@'localhost' WITH GRANT OPTION;命令授权给新用户全部权限。
    • 输入FLUSH PRIVILEGES;命令刷新权限。
    • 输入EXIT;命令退出MySQL服务器。
  4. 在C++项目中连接MySQL。

    在C++项目中连接MySQL,需要使用MySQL C++ Connector库文件提供的API。可以按照以下步骤进行连接:

    • 包含MySQL C++ Connector头文件。
    • 使用sql::Driver接口创建一个MySQL连接对象。
    • 使用连接对象创建一个sql::Statement对象。
    • 使用sql::Statement对象执行SQL语句,并获取结果。

以上就是在C++中安装和配置MySQL的步骤,具体实现可以参考MySQL C++ Connector文档和示例代码。

数据库的表结构设计,包括数据类型、索引等;

数据库的表结构设计是数据库系统设计中的一个重要部分,它决定了数据在数据库中的存储方式、查询效率等。表结构设计包括以下方面:

  1. 数据类型

数据库中的每个列都要指定数据类型,以确定该列可以存储的数据类型和范围。常见的数据类型包括整型、浮点型、字符型、日期型等。在选择数据类型时,需要根据实际需求和存储空间进行权衡,避免浪费或不足。

  1. 主键

每个表都应该有一个主键,用于唯一标识该表中的每行数据。主键通常是一个整型列,自增长或者由应用程序生成。主键可以加速查询、避免数据重复等。

  1. 索引

索引是一种数据结构,用于加速数据库中的数据查询。在选择索引时,需要考虑查询的频率、查询的效率、表的大小等因素。常见的索引类型包括B-tree索引、哈希索引等。

  1. 外键

外键是一种关系型数据库的基本概念,用于建立表与表之间的关系。外键通常指向其他表的主键,用于保证数据的完整性和一致性。

  1. 视图

视图是一种虚拟的表,它是基于表或其他视图的查询结果组成的,并且可以像表一样使用。视图可以简化数据的存取、保护数据、实现数据隐藏等。

  1. 存储过程和触发器

存储过程是一组预编译的SQL语句,可以在客户端中调用。触发器是一种特殊的存储过程,当满足特定条件时会自动执行。存储过程和触发器可以提高数据库的性能、实现数据的复杂操作等。

  1. 数据库设计范式

范式是数据库中表的设计规范,用于规范表之间的关系,避免数据冗余和不一致。常见的范式包括第一范式、第二范式、第三范式等。

需要注意的是,在设计数据库的表结构时,应该根据实际需求进行综合考虑,权衡各种设计因素,确保数据的存储和查询效率、数据的完整性和一致性,以及数据的安全性和可维护性。

数据库的SQL语句操作,如增删改查等;

  1. 创建表

    创建表是数据库的基本操作,可以使用以下语句创建一个表:

    CREATE TABLE tablename (
       column1 datatype,
       column2 datatype,
       column3 datatype,
       ....
    );
    

    其中,tablename表示表名,column1column2column3等表示表的列名,datatype表示数据类型。

  2. 插入数据

    插入数据可以使用以下语句:

    INSERT INTO tablename (column1, column2, column3, ...) VALUES (value1, value2, value3, ...);
    

    其中,tablename表示表名,column1column2column3等表示表的列名,value1value2value3等表示要插入的数据值。

  3. 更新数据

    更新数据可以使用以下语句:

    UPDATE tablename SET column1=value1, column2=value2, ... WHERE condition;
    

    其中,tablename表示表名,column1column2等表示要更新的列名,value1value2等表示要更新的值,condition表示更新条件。

  4. 删除数据

    删除数据可以使用以下语句:

    DELETE FROM tablename WHERE condition;
    

    其中,tablename表示表名,condition表示删除条件。

  5. 查询数据

    查询数据可以使用以下语句:

    SELECT column1, column2, ... FROM tablename WHERE condition;
    

    其中,column1column2等表示要查询的列名,tablename表示表名,condition表示查询条件。

MySQL的存储引擎,如InnoDB、MyISAM等;

MySQL支持多种存储引擎,每种存储引擎都有其优缺点和适用场景。以下是MySQL常见的几种存储引擎:

  1. InnoDB:InnoDB是MySQL默认的事务型存储引擎,支持ACID事务、行级锁和外键等特性,适用于需要高并发、高可靠性、支持事务的应用场景。
  2. MyISAM:MyISAM是MySQL的一种非事务型存储引擎,不支持ACID事务和外键,但具有查询速度快、占用空间小等优点,适用于读写比例低、对数据完整性要求不高的应用场景。
  3. MEMORY:MEMORY是MySQL的一种内存型存储引擎,将数据存储在内存中,查询速度非常快,但容易受到内存大小限制,适用于缓存、临时表等应用场景。
  4. NDB Cluster:NDB Cluster是MySQL的一种集群存储引擎,支持数据分片和数据复制,适用于需要高可用性和高扩展性的应用场景。
  5. ARCHIVE:ARCHIVE是MySQL的一种归档存储引擎,适用于只需要存储数据而不需要频繁查询的应用场景,如日志记录等。

需要注意的是,使用不同的存储引擎可能会对应用程序的性能、数据一致性和可靠性产生不同的影响,应根据具体应用场景选择合适的存储引擎。

MySQL的事务处理和锁机制;

MySQL的事务处理和锁机制是保证数据完整性和并发性的重要手段。

  1. 事务处理:

事务是指一组对数据库进行操作的操作序列,这些操作要么全部执行,要么全部不执行,是一个不可分割的工作单位。MySQL通过ACID(原子性、一致性、隔离性、持久性)来保证事务的完整性。

  1. 锁机制:

MySQL通过锁机制来实现并发控制,保证多个用户同时访问数据库时的数据完整性和一致性。MySQL的锁分为共享锁和排它锁,共享锁用于读取数据,排它锁用于修改数据。MySQL支持以下锁机制:

  • 行级锁:在数据行级别上加锁,可以减少锁的冲突,提高并发性。
  • 表级锁:在整个表上加锁,对于大量修改操作的表,性能较差。
  • 页级锁:在数据页级别上加锁,适用于大量数据的情况,但容易产生锁冲突。

MySQL还提供了多种锁算法,如表锁算法、行锁算法、间隙锁算法等,可以根据具体情况选择适合的锁算法。同时,MySQL还支持事务隔离级别,可根据需求选择不同的隔离级别,包括读未提交、读已提交、可重复读和串行化。

数据库备份和恢复,如备份策略、备份工具等;

  1. 备份策略: 备份策略是指针对数据库的备份和恢复操作,制定的一些规则和流程。常见的备份策略有以下几点:

(1)备份频率:备份频率是指备份的时间间隔,备份频率越高,数据恢复的损失就越小,但是备份所需的时间和空间也就越多。一般来说,数据库备份的频率需要根据数据的更新频率来确定。

(2)备份类型:备份类型是指备份的方式,常见的备份类型有完整备份、差异备份和增量备份。完整备份是指备份整个数据库,差异备份和增量备份都是指只备份数据库中发生变化的数据。

(3)备份存储:备份存储是指备份数据的存储位置,典型的备份存储方式包括本地存储、远程存储和云存储等。

  1. 备份工具: 备份工具是指用于数据库备份和恢复的软件,常见的备份工具有以下几种:

(1)系统自带备份工具:不同的数据库系统自带不同的备份工具,如SQL Server自带的SQL Server Management Studio(SSMS)和SQL Server Backup Wizard等。

(2)第三方备份工具:第三方备份工具是由其他公司或个人开发的备份软件,如Redgate SQL Backup、Veeam Backup and Replication等。

(3)云备份工具:云备份工具是指将备份数据存储在云存储中,如Amazon S3和Microsoft Azure Backup等。

数据库的优化,如索引优化、查询优化、存储优化等;

数据库的优化是指通过一系列技术手段,提高数据库的性能、可靠性和安全性,主要包括索引优化、查询优化和存储优化。

  1. 索引优化: 索引是数据库中用于加速查询的数据结构,索引优化可通过以下几种方式实现:

(1)创建合适的索引:合适的索引能够大幅提高查询效率,而不合适的索引则会拖慢查询速度。因此,需要根据实际情况创建适当的索引,避免过多或过少的索引。

(2)优化索引数据结构:索引数据结构包括B-tree、B+tree、哈希表等,针对不同的数据类型和查询场景,选择合适的索引数据结构能够提高查询效率。

(3)定期重建索引:索引随着数据的不断修改,会产生碎片,影响查询效率。因此,需要定期重建索引,清理碎片,提高查询效率。

  1. 查询优化: 查询优化是指通过调整查询语句、调整查询顺序、减少查询数据量等方式,提高查询效率。以下是几种常见的查询优化方法:

(1)尽量减少使用“”:查询尽量减少使用“”,只查询所需的字段,避免查询不必要的数据,提高查询效率。

(2)避免使用子查询:子查询会导致查询效率低下,尽量避免使用子查询,可以使用JOIN查询代替。

(3)使用EXPLAIN分析查询:使用EXPLAIN命令可以分析查询语句的执行计划,找出效率低下的地方,进行优化。

  1. 存储优化: 存储优化是指通过调整存储结构、优化存储方式等方式,提高数据库的性能和效率。以下是几种常见的存储优化方法:

(1)合理使用表分区:表分区可以将大表分割成多个小表,提高查询效率和管理效率。

(2)使用压缩技术:压缩可以减小数据存储空间,提高存储效率。

(3)使用存储引擎:不同的存储引擎对于不同的场景,有不同的优化效果,需要根据具体情况选择合适的存储引擎。

MySQL的主从复制和高可用性方案;

MySQL的主从复制和高可用性方案是保证数据可靠性和可用性的重要手段。

  1. 主从复制:

主从复制是指将一个MySQL数据库实例(主库)的数据复制到另一个MySQL数据库实例(从库)上,使得从库和主库的数据保持一致。主库负责进行插入、更新和删除操作,从库只负责读操作。MySQL的主从复制可以通过以下步骤实现:

  • 在主库上开启二进制日志(binlog)功能,记录所有的修改操作;
  • 在从库上设置主库信息,包括主库的IP地址、用户名、密码等;
  • 在从库上启动复制进程,将主库的数据复制到从库上。

主从复制的优点在于可以提高数据库的读性能,同时也可以用于备份和灾难恢复。

  1. 高可用性方案:

MySQL的高可用性方案可以保证数据库的可用性,主要包括以下几种方案:

  • 主从复制+自动故障切换:在主从复制的基础上,通过自动故障切换(failover)来保证数据库的可用性,当主库出现故障时,自动将从库提升为主库,确保服务的连续性。
  • MySQL Cluster:MySQL Cluster是一种基于InnoDB存储引擎的高可用性方案,通过将数据分片存储在不同的节点上,实现数据的高可用性和负载均衡。
  • MHA:MHA(MySQL High Availability)是一种基于主从复制的高可用性方案,可以自动进行主从切换和故障恢复,保证数据库的可用性。

数据库的安全性,如用户管理、权限管理、防止SQL注入等。

数据库的安全性是非常重要的,包括用户管理、权限管理、防止SQL注入等方面,以下是一些常见的数据库安全性措施:

  1. 用户管理:数据库的用户管理是指对用户账号的管理,包括创建、修改和删除用户账号等。为了保证安全性,应该设置安全的密码策略,包括密码复杂度、密码长度、密码过期时间等,并定期检查和清除不必要的用户账号。
  2. 权限管理:权限管理是指对用户或角色的权限进行管理,包括数据表、存储过程、触发器、视图等的操作权限。应该授予最小权限原则,即只给予用户所需的最少权限,以避免敏感数据被误操作或泄漏。
  3. 防止SQL注入:SQL注入是指攻击者通过构造恶意的SQL语句来获取数据库中的敏感数据或执行非法操作。为了防止SQL注入,可以使用参数化查询、输入验证、过滤非法字符等方法进行预防。
  4. 数据库加密:数据库加密是指对数据库中的敏感数据进行加密处理,以保证数据的机密性。可以使用存储引擎的加密功能、外部加密工具、加密文件系统等方法进行加密处理。
  5. 数据库审计:数据库审计是指记录数据库中的操作流程,并对操作进行监控和审计,以保证数据库的安全性。可以使用安全审计工具、日志分析工具、监控报警系统等方法进行审计监控。
  • 5
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: VS2017和Qt是一种非常流行的开发工具和框架,可以帮助开发者轻松创建各种应用程序。在VS2017中,可以使用C++或其他语言来编写应用程序,同时Qt提供了丰富的UI库和功能模块,可以让开发者更加便捷地创建桌面和移动平台的应用程序。 在Qt方面,Qt Creator是一个非常好用的开发环境,可以让开发者更加轻松地创建和管理项目,在创建新项目时,可以轻松选择模板和添加依赖项。Qt还提供了许多其他的工具和组件,例如Qt Designer可以帮助开发者在不需要编写代码的情况下创建用户界面,Qt Quick用于创建动态的用户界面并将其与C++代码集成到一起。 在实际开发中,VS2017和Qt结合使用可以得到很好的效果。开发者可以使用VS2017来编写C++代码,并通过Qt来创建用户界面和添加功能模块。同时,VS2017也可以通过Qt的插件来支持Qt项目的开发和管理。 总之,VS2017和Qt提供了非常强大的开发工具和框架,可以帮助开发者轻松创建各种应用程序,无论是桌面应用程序还是移动平台应用程序。这两个工具有很好的兼容性和互补性,可以让开发者更加高效地完成项目。 ### 回答2: VS2017是微软公司推出的一款强大的集成开发环境,可用于C++和其他编程语言的开发Qt是一种跨平台的GUI应用程序框架,也可以使用C++编写。 在VS2017中开发Qt应用程序的实践中,我们可以采用Qt插件,让VS2017能够识别和编译Qt代码。安装插件后,我们可以使用VS2017的工具集来创建、管理和构建Qt项目,从而提高开发的效率。 对于初学者来说,使用VS2017开发Qt程序可能会感到有些困难,但是掌握了必要的知识和技能后,我们就能够灵活运用这些工具,轻松地开发出各种功能强大的Qt应用程序。 总之,VS2017提供了许多有用的开发工具,可用于Qt项目的设计、创建和调试,当与Qt框架配合使用时,这些工具可以帮助我们快速、高效地完成开发任务。 ### 回答3: VS2017是一个较为流行的集成开发环境,它提供了强大的工具和功能,可以快速地开发和调试应用程序。而QT则是一个跨平台的GUI应用程序开发框架,它具有良好的可移植性和开发效率。 在使用VS2017进行QT开发实战时,我们可以利用VS2017提供的QT插件来进行QT程序的开发。这样可以大大地减少开发工作量,并加快开发效率。同时,利用VS2017的调试功能也能够快速地发现和解决程序中的问题。 此外,VS2017还支持使用第三方插件进行QT开发,例如:Visual Qt,它是一个免费的插件,支持Windows,Linux,Mac OS X等多种平台。它提供了完整的QT设计器和源码编辑器,使得开发QT程序更加方便和快速。 总的来说,使用VS2017进行QT开发实战可以大大地提高开发效率和程序质量。同时,VS2017也提供了许多便利的工具和功能,使开发更加简单和有趣。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值