C/C++ 面试大纲

C

程序运行

预处理: 展开头文件 替换宏定义的内容 去注释 条件编译
gcc -E cc.c -o cc.i

编译: 将c程序的指令和数据分开,分别映射到不同的程序段,生成汇编代码
gcc -S cc.i -o cc.s

汇编:将汇编代码转化为机器文件(二进制),为每一个源文件生而成一个
目标文件
gcc -c -o cc.s -o cc.o

链接: 将多个目标文件以及所需要的库文件链接成最终的可执行性文件
gcc cc.o - o hello

运行:
./hello

简易编译:
gcc cc.c -o hello
./hello

数组

arr 即代表构造类型,也代表首元素的地址
arr = &arr[0]
sizeof(arr) 整个数组的大小

int arr[10];
sizeof(arr): 40
sizeof(int[10]):40

  • 降维 实现: * arr = arr[0]
    &升维 实现 :&arr[0] = arr

a[i] == *(a+i)
a[i][j] == ((a+i)+j)

指针

指针大小
32 位机的情况下,无论是什么类 型大小均是 4。而 64 位机大小均是 8。这是由当前机型的地址总线决定的

指针常量,有类型的地址常量
char a = 1 ;
printf(“&a = %p\n”, &a);
printf(“a = %d\n”, ((char)0x0060FEAF));

指针变量,用以存放指针的量, 我们叫作,指针变量
定义:type * variable;

int *p = NULL;
printf(“%p\n”,p); // 0000000000000000
//printf(“%d\n”,p); //挂掉
printf(“%p\n”,&p); //000000000061FE18
printf(“%d\n”,
((int *)0x000000000061FE18)); // 0

初始化以及间接访问:
char p = (char)0x123456; //0x123456 是数值,(char*)0x123456 是指针
*p = ‘a’; // error

所以通常作法是,把一个己经开辟空间的变量的地址赋给指针变量。

int main()
{
int a = 100; // 变量a已开辟空间,main函数未结束
int *pa = &a;
printf(“*pa = %d\n”,*pa);
}

指向关系:
谁指向了谁,即保存了谁的地址。

野指针
一个指针变量,如果,指向一段无效的空间,则该指针称为野指针,常见情型有两种,一种是未初化的指针,一种 是指向一种己经被释放的空间。

如何避免野指针,初始化为null
define NULL ((void *)0)
既读不出东西,也不写进东西去

void 即无类型(非没有返回值),可以赋给任意类型的指针,本质即代表内存的最小单位,在 32 位机上地位等同于 char。

指针运算
种数值加类型运算
int a = 0x0001;
printf(“a = %#x a+1 = %#x\n”,a,a+1); //0x1 0x2
int p = (int)0x0001;
printf(“p= %#x p+1 = %#x\n”,p,p+1); // 0x1 0x5
只有当指针指向一串连续的存储单元时,指针的移动才有意义。

访问:
数组名代表数组首元素的地址,可以用下标法和本质法进行访问,指针法访问(冲锋枪)
int array[10] = {1,2,3,4,5,6,7,8,9,0};
for(int i=0; i<10; i++)
{
printf(“%d\n”,array[i]);
}
for(int i=0; i<10; i++)
{
printf(“%d\n”,*(array+i));
}
int *p = array;
printf(“%d\n”,*p++); // p++:等价于(p++)
printf(“%d\n”,(*p)++); // 1 2 3 4 5 6 7 8 9 10 error

二维数组的访问:
int arr[3][4] = {1,2,3,4,10,20,30,40,100,200,300,400};

printf(“%d\n”,arr[1][1]);
printf(“%d\n”,(arr[1]+1));
printf(“%d\n”,
(*(arr+1)+1)); //行,列

//arr[i] 第 i 行,首元素的地址 一级指针
//arr+i 第 i 行的地址 数组指针

arr[0] 值为地址
printf(“%d\n”,*arr[0]);

arr 是数组首元素的地址,所以 arr 的值和 &arr[0]的值相同,
另一方面,arr[0]本身是包含4 个整数的数组,
因此,arr[0]的值同其首元素的地址&arr[0][0]相同

字符串

char *p = “china”; // china 不可以需修改
char arr[] = “china”; // china 可以修改
printf(“%s\n”,p);
printf(“%s\n”,arr);

“china” 存储在rodata段,不可以修改, 指针不可以修改,拷贝到字符指针数组中,可以修该

char *p = “china”;
char arr[10] = “china”;

strlen(“china”) : 5
sizeof(“china”): 6

strlen( p) :5
sizeof( p): 4

strlen(arr): 5
sizeof(arr):10

字符串操作函数
1 求长 strlen
int myStrlen(char *dest)
{
    int count=0;
    while(*dest++)
        count++;
    return count;
}

2 链接 strcat

char *myStrcat(char * dest,char *src)
{
    char * d = dest;
    while(*dest) dest++;
    while(*dest++ = *src++);
    return d;
}


3 拷贝 strcpy

char * myStrcpy(char * dest, char * src)
{
    char * d = dest;
    while(*dest++ = *src++);
    return d;
}

4 比较 strcmp

int mystrCmp(char * s1, char *s2)
{
   for(;*s1 && *s2;s1++,s2++)
   {
      if (*s1 != *s2)
          break;
   }
   return *s1 - *s2;
}
内存模型

命令行参数/环境变量
stack
heap
数据段: 未初始化(bss)、 初始化(rw)
代码段

void * malloc(size_t size);
int * pa = (int *) malloc(10 *sizeof(int));
memset(pa,0,10 * sizeof(int));

int * array = (int *)calloc (10, sizeof(int));

realloc :扩容 第一个参数未待扩容的指针, 第二个参数为扩容后内存的大小
char * pa = (char *) malloc(10);
pa = realloc(pa,30);

内存对齐

内存不对齐: 一个成员变量需要多个机器周期去读
对齐规则:
1 取pack(n) 的值 , 取结构体中类型最大的值, 两者取小作为外对齐的值
2 将外对齐的值 每个结构体成员比较,取小 ,作为内对齐的值
3 设起始地址为0,若地址可以整除 第二步内对齐的值,存放数据,若不整除,找到整除的地址
4 最后进行补空,直到可以整除第一步外对齐的值, 即地址大小为结构体大小

结构体成员,从第一个变量开始,放在结构体的开始地址处

数据的低位字节:最右侧 ; 高位地址:最左侧
大端存储:数据的低位字节放到高地址,高地址放到低地址处

struct stu {
char age;
int num;
}
地址: age < num

内存泄露

使用: 申请 判空 使用 释放 制空
malloc 和 free 配对 使用
malloc 多余 free ,内存泄露, free 多于malloc , 造成double free
谁申请谁释放

检测内存泄露:
1 日志分析
2 单元测试
3 分析工具heob

内存拷贝

strcpy: 实现字符串的拷贝,遇到 /0结束
sprintf: 格式化字符串
memcpy: 实现内存块的拷贝,根据size大小赋值

void memcpy(void * destin, void * sourcre, unsiged n);
以source为起始地址拷贝destin n 个字节

memcpy最快,strcpy次之,sprintf最慢

链表

内存模型:理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接的
应用场景:
动态数据结构,需要频繁插入和删除元素的数据结构
内存管理:实现自定义的内存管理器,动态分配和释放内存块
深度优先搜索:在图形遍历算法中,使用链表来表示节点的邻接关系

头插法:

//头插法  让新来的节点有所指向
void insertHeadList(Node* head,int data) {
    Node * cur = (Node *)malloc(sizeof(Node));
    cur->data = data;

    cur->next = head->next;
    head->next = cur;
}

尾插法:

void insertTailList(Node * head, int data) {

    Node *cur = (Node *)malloc(sizeof(Node));
    cur->data= data;

    while(head->next) {
        head = head->next;
    }
    head->next = cur;
    cur->next = NULL;

}
文件

c标准IO库: fopen 从内存中去读数据
系统函数: open 从用户态切换到内核态,在从内核态切换到用户态

FILE * fopen ( const char * filename, const char * mode );

FILE* fp = fopen(“ascii2.txt”, “w”);
在这里插入图片描述
int fclose ( FILE * stream );
int fputc (int ch, FILE * stream );
int fgetc ( FILE * stream );

int feof( FILE * stream ) 判断文件是否读到文件结尾

int fputs(char *str,FILE *fp) 把 str 指向的字符串写入 fp 指向的文件中。
char *fgets(char *str,int length,FILE *fp) 从 fp 所指向的文件中,至多读 length-1 个字符,送入字符数组 str

int fwrite(void *buffer, int num_bytes, int count, FILE *fp)
把 buffer 指向的数据写入fp 指向的文件中

int fread(void *buffer, int num_bytes, int count, FILE *fp)
fp 指向的文件中的数据读到 buffer 中

char rbuf[1024];
int n;
n = fread(rbuf,1,1024,pf);
printf(“n = %d\n”,n); // n =6

n = fread(rbuf,1024,1,pf);
printf(“n = %d\n”,n); // n =0

void rewind ( FILE * stream ); // 将文件指针重新指向一个流的开头
int fseek ( FILE * stream, long offset, int where) // 文件偏移, offset 偏移量 where 起始偏移的位置

排序

快排

1 先从数列中取出一个数作为基准数(通常取第一个数)。
2 分区过程,将比这个数大的数全放到它的右边,小于或等于它的数全放到它
的左边。
3 再对左右区间重复第二步,直到各区间只有一个数

选定基石,左右交替
右侧:大而移动,小而赋值
左侧:小而移动,大而赋值
最后确定基石位置,如法炮制

优点:极快,移动数据少
缺点: 不稳定和递归
时间复杂度: nlogn

void quickSort(int *p, int left, int right)
{
    if(left < right)
    {
        int pivot = p[left];
        int low = left; int high = right;
        while(low<high)
        {
            while(p[high]>=pivot && low<high)
                high--;
            p[low] = p[high];
            while(p[low] <=pivot && low<high)
                low++;
            p[high] = p[low];
        }
        p[low] = pivot;
        quickSort(p,left,low-1);
        quickSort(p,low+1,right);
    }
}
选择

第一趟第一个元素和后面的元素依次比较,比较n-1趟
时间复杂度: n2

for(int i=0;i<n-1;i++)
{
	for(int j=i+1;j<n-1;j++)
	{
		if(arr[i]  > arr[j])
		{
		     arr[i] ^= arr[j];
			 arr[j] ^= arr[i];
			 arr[i] ^= arr[j];
		}
	}
}

冒泡

第一趟第一个元素和第二元素比较,若升序,大于则交换,比较n-1趟

优点:稳定
时间复杂度: n2

for(int i=0;i<n-1;i++)
{
	for(int j=0;j<n-1-i;j++)
	{
		int count = 0;
		if(arr[j]>= arr[j+1])
		{
		
			count = 1;
		}
	}
	if(count == 0)
		break;
}

折半
int left =0, right = n-1, midIdx;
int idx = -1;
while(left <= right)
{
	midIdx = (left + right)/2;
	if(findData == arr[midIdx])
	{
		idx == midIdx;
	}
	else if (findData >= arr[midIdx])
	{
		left = midIdx +1;
	}
	else if (findData <= arr[midIdx])
	{
		right = midIdx -1;
	}
}
if(idx != -1)
	printf("%d\n",idx);
else
	printf("find none");

C++

封装

封装,可以达到,对外提供接口,屏蔽数据,对内开放数据
统一接口:封装可以将类的内部实现和外部接口分离,从而简化了类的使用方式,使得用户只需要关注如何使用类的接口而不需要关心具体实现。

继承

继承是指一个类可以从另一个类中继承属性和方法,继承是一种"is-a"关系
聚合是指一个对象可以由多个其他对象组成,每个组成对象都可以独立存在,聚合是一种"has-a"关系

如果一个类需要拥有另一个类的所有属性和方法,并且可以在此基础上增加新的属性和方法,则可以选择继承关系;如果一个类需要由多个其他对象组成,则可以选择聚合关系。

多态

多态,允许子类对象能够以自己的方式实现从父类继承的方法,如

实现多态类型的容器:我们可以定义一个父类指针类型的容器,将多个不同子类对象放入其中,这样在程序运行时就可以根据实际情况选择调用哪个子类对象的方法

实现策略模式:策略模式是指定义一系列算法,将每个算法都封装起来,并且使它们之间可以互换。这种情况下就可以通过定义一个父类指针类型的指针来实现多态性,让程序在运行时动态地选择使用哪种算法。

实现模板方法:模板方法是指将具体实现交给子类来完成,而在父类中定义算法的框架。这种情况下就可以通过定义一个父类指针类型的指针来实现多态性,让子类来实现具体的算法

类型转化:

static_cast: 基本类型转化
dynamic_cast: 多态类型转化,基类指针转化为派生类
reinterpret_cast : 不同指针类型转化
const_cast : 去 const 属性

默认构造

class A {}

  • 无参构造
    A() {}

  • 拷贝构造
    A(const A & another)() {}

  • 赋值运算符号重载
    A & operator = (const A & another) {}

  • 析构
    ~A()

继承方式

在这里插入图片描述
全盘接收,除了构造器与析构器。基类有可能会造成派生类的成员冗余,所以说基类是需设计的

构造顺序

派生类名::派生类名(参数总表)
:基类名(参数表),内嵌子对象(参数表)
{
派生类新增成员的初始化语句; //也可出现地参数列表中
}
先是基类的构造顺序,然后是内嵌子对象的构造顺序,之后是派生类的构造顺序,析构顺序与之相反

Student(string name)
:_name(name){}

Birthday(int y)
:_year(y)

class Graduate:public Student {
private:
	Birthday birth;
}


Graduate(string name,float salary,int y)
:Student(name),_salary(salary),birth(y)


虚继承

目的:解决C++中多重继承的问题,子类继承不同父类中相同的成员,存在多次拷贝,1 放浪费存储空间 2 存在二义性

实现方法:
1 提取公共成员组成祖父类
2 各父类虚继承虚基类

多态:

静多态:
函数重载的条件:

  • 函数名相同
  • 参数个数不同,参数的类型不同,参数顺序不同
  • 返回值类型,不作为重载的标准

动多态:
父类中由虚函数
子类覆写父类中的虚函数
子类对象赋值给父类指针,父类指针调用虚函数

纯虚函数: 虚函数被赋值给0

原理:
若类使用虚函数,则会为类生成虚函数表(一维数组,存放了虚函数表的地址),类对象构造时会初始化虚函数表的指针

虚析构函数

含有虚函数的类,析构函数也应该声明为虚函数
将基类的析构函数声明为virtual , 防止内存泄露

定义一个基类的指针p, 在delete p 时,当赋值p的对象是派生类的对象时
a 基类的析构函数是虚函数:
先调用派生类的析构函数,再调用基类的析构函数

b 基类的析构函数不是虚函数:
只会析构派生的析构函数, 基类的析构函数没有调用 (根据指针的类型进行性判断,不会去赋值的对象),造成内存泄露

~Parent()
{
cout << "调用父类 Parent 析构函数 " << endl;
}
~Child()
{
cout << "调用子类 Child 析构函数 " << endl;
}
Parent* parent = new Child();

调用父类 Parent 析构函数


virtual ~Parent()
{
cout << "调用父类 Parent 析构函数 " << endl;
}
virtual ~Child()
{
cout << "调用子类 Child 析构函数 " << endl;
}
Parent* parent = new Child();

调用父类 Parent 析构函数
调用子类 Parent 析构函数

虚构造

从存储空间角度,虚函数对应一个虚函数表,如果将构造函数设置为虚函数,需要到虚函数表中调用,但是对象没有实例化,没有分配内存空间,则不能调用

C++11

lambda

匿名函数
{}
[](int x,int y){return x > y;}
sort(vi.begin(),vi.end(),[](int x,int y){return x > y;});

functor

重载了 operator()的类的对象,在使用中,语法类型于函数。故称其为仿函数
int add(int i,int j)
{
return i + j;
}
auto op = function<int(int,int)>(add);
cout<<op(1,2)<<endl;

移动构造

将类A 对象a 移动赋值给 对象b, std::move() 能把左值强制转换为右值,它通过接收一个右值引用参数来创建新对象,进行移动资源而非进行深拷贝

使用场景
在函数中返回临时对象时,可以通过移动构造函数避免不必要的拷贝操作
在容器中插入临时对象时,可以通过移动构造函数实现高效插入和删除操作
在进行资源管理时,通过移动构造函数可以从一个对象转移资源所有权,提高性能

实现:
class Move
{
Move( Move && another)
{
cout<<" Move( Move && another)"<<endl;
_i = another._i;
another._i = nullptr;
}
int *_i;
}
int main() {
Move a;
Move b(std::move(a));
}
运行结果:
a的构造和析构,打印移动构造,Move( Move && another)

智能指针:

auto_ptr : 代理了被托管的对象指针,管理对象的生命周期

unique_ptr :不可以拷贝也不可以赋值;

shared_ptr: 解决了 auto_ptr 中引用同一个对象,在内部保持一个引用计数,并且仅当引用计数为 0 才能被删除,解决c中指针悬挂的问题,一个指针申请内存,多个指针指向这一块内容,当一个指针释放空间后,多个指针使用不安全

weak_ptr:用来解决shared_ptr循环引用,导致引用计数不能置0,资源无法释放问题

多线程

定义:
void func()
{
}
int main() {
thread t(func);
t.join(); // detach()
}

mutex锁:
lock():调用线程将锁住该互斥量
unlock():解锁,释放对互斥量的所有权
try_lock():尝试锁住互斥量, 如果当前互斥量被其他线程锁住,则当前调用线程返回 false

mutex mtx;
mtx.lock();

lock_guard : 解决死锁问题, 采用RAII思想(资源获取即初始化,在 lock_guard 对象构造时,传入的 Mutex 对象(即它所管理的 Mutex 对象会被当前线程锁住。在 lock_guard 对象被析构时,它所管理的 Mutex 对象会自动解锁。
lock_guard不能拷贝、赋值、移动,只能通过构造函数初始化和析构函数销毁

lock_unique: 采用RAII思想,相对于lock_guard 是可移动的,可以拷贝、赋值、移动;同时,可用于添加条件变量

条件变量:
利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待某个条件为真,而将自己挂起;另一个线程使的条件成立,并通知等待的线程继续,std::condition_variable 需要与 std::unique_lock 配合使用

api:
wait:当前线程调用 wait()后将被阻塞
notify_all: 唤醒所有的 wait 线程

STL

组成: 空间适配器 allocator , 容器 container, 算法 ,迭代器iterator
和仿函数组成

vector

特点:模板实现,变长数组,可以通过下标访问
底层: 动态数组
内存模型: 翻倍增长,申请新的内存空间,拷贝数据,释放旧的内存空间,底层实现为c库中的realloc()函数
api :
在这里插入图片描述
vector c(n) // n个元素
vector c(n,elem) // 初始化为elem
c[idx]
insert(pos,elem)
insert(pos,beg,end)
erase(pos)
erase(beg,end)
clear()

reserve() : 对vector预留内存空间,使用前并不申请空间
resize() : 当>size , 等同与push_back; 当<size,等同与pop_back()
size() : 容器实际大小
capacity() : reserve之后的大小
shrink_to_fit() :调整vector的capatity大小,与size相同

	vecor<int> coll = {1,3,5,6,7,2,4,6,8,10};
	vector<int>::iterator itr;
	for(itr = coll .begin(); itr != coll .end(); itr++)
	{
		cout<<*itr<<endl;
	}
	// 删除元素
	int val= 5;
	pos = find(coll.begin(),coll.end(),val);
	if (pos != coll.end()) {
		coll.erase(pos);
	}
list

特点: 插入和删除效率高
底层: 双向链表结构
内存模型: 通过节点的指针指向其前一个元素和后一个元素
使用场景: 栈,队列,
api :
在这里插入图片描述
remove(val) // 删除重复的元素
remove_if(op)
rease(pos)

erase 删除第2个元素

list<int>li ={1,3,5,7,7};
    list<int>::iterator itr = li.begin();
    advance(itr,1);  // 移动1 及 指向下标为1的元素,不能越界
    li.erase(itr);

合并两个list :

 std::list<int> list1 = {1, 3, 5};
    std::list<int> list2 = {2, 4, 6};
    list1.merge(list2);

list 排序:
c.sort()
c.merge(c2)

splice : 拼接两个list

list<int> li1 = {1,3,5};
list<int> li2 = {2,4,6};
li1.splice(li1.begin(),li2);
//li1: 2 4 6 1 3 5
// li2: null

remove_if 删除>5的元素

	list<int>li ={1,3,5,7,7};
	li.remove_if([](int x){
        if(x > 5)
            return true;
        else
            return false;
    });

    for(auto &i:li) {
       cout<<i<<" ";
    }
    //输出:1 3 5

set

特点: K模型容器,元素不允许修改,可以插入和删除,元素不可以重复,有序
底层: 红黑树
使用场景: 用来排序、去重、查找, 查找的时间复杂度O(logN)
api:
set s1 = {1,3,4,5};
s1.insert(2);
s1.erase(3);
for(auto &i:s1)
cout<<i<< " ";

c.find(val)

multiset

允许存在重复的 key 值节点
c.count(val) : 统计val 出现的次数

哈希表

哈希表中关键码就是数组的索引下标,然后通过下标直接访问数组中的元素
应用场景:快速判断一个元素是否出现集合里

哈希函数:把不同类型的key转化为数值型的索引
哈希碰撞:不同的key 对应的索引相同,通过拉链法和线性探测法解决

常见的三种哈希结构: 数组 set(集合) map(映射)

unorderd_set

底层实现为哈希表,无序,查询效率 O(1),增删效率O(1)

map

特点: K-V模型容器,键值 key 通常用于排序和惟一标识元素,而值 value中 用于存储,key-value 通过 pair 绑定在一起,有序

底层: 红黑树
使用场景: 需要根据键快速查找对应值的场景 ,有序

api :
map<int,string > mis;
mis.insert(pair<int,string>(1,“china”));
mis.insert(make_pair(3,“america”));

遍历:
map<int,string>::iterator itr;
for(itr = mis.begin();itr != mis.end(); ++itr)
{
cout<first<<“:”<second<<endl;
}

multimap

允许 map 中存在 key 值相同的节点

unorderd_map

底层实现为哈希表,无序,查询效率 O(1),增删效率O(1),,适合无序查找

在这里插入图片描述

仿函数

仿函数又为函数对象,,其实就是重载了()操作符的class,对象名可以像函数名一样使用
class func
{
public:
void operator() ()
{
… }
}

class Compare
{
	public:
	bool operator()(int x, int y)
	{
		return x<y;
	}
};
vector<int>vi = {1,2,3,5,6,7,9,10};
sort(vi.begin(),vi.end(),Compare());
算法

sort: 以升序重新排列指定范围内的元素
void sort(RanIt first, RanIt last)

find: 查找,返回位置
InIt find(InIt first, InIt last, const T& val

find_if: 根据函数进行查找
InIt find_if(InIt first, InIt last, Pred pr);

count: 返回相等元素的个数

size_t count(InIt first, InIt last,const T& val, Dist& n);

copy:
copy(InIt first, InIt last, OutIt x)

remove:
remove(FwdIt first, FwdIt last, const T& val)
remove_if

bool func(int & v)
{
return v >= 3?true:false;
}
int main() {
	vector<int> vi = {1,3,10,2,5};
	
	sort(vi.begin(),vi.end(),greater<int>());
	sort(vi.begin(),vi.end(),less_equal<int>())
	
	vector<int>::iterator itr;
	itr = find(vi.begin(),vi.end(),10);
	if(itr != vi.end())
		cout<<*itr<<endl;
	else
		cout<<"find none"<<endl;
	
	itr=find_if(vi.begin(),vi.end(),func);
	if(itr != vi.end())
		cout<<*itr<<end;
	
	vector<int> to(10);
    copy(vi.begin(), vi.end(), to.begin()); // to 拷贝vi
	
	string str1 = "Text with some spaces";
	str1.erase(remove(str1.begin(), str1.end(), ' '), str1.end()); // 取出空格
}

设计模式

设计原则:

单一职责原则: 即一个类只负责一项职责

开闭原则 :如类、模块和函数应该对扩展开放,对修改关闭

依赖倒置原则 : 高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象

接口隔离原则:一个类对另一个类的依赖应该建立在最小的接口上

里氏替换原则:类可以扩展父类的功能,但不能改变父类原有的功能

单例模式
单例模式

确保某一个类只有一个实例

#include <iostream>
using namespace std;
class Singleton
{
public:
    static Singleton* getInstance()
    {
        if(_ins == NULL)
            _ins = new Singleton;
        return _ins;
    }
    static void releaseInstance()
    {
        if(_ins != NULL)
        {
            delete _ins;
            _ins = NULL;
        }
    }
private:
    Singleton(){}
    ~Singleton(){}
    Singleton(const Singleton &){}
    Singleton & operator=(const Singleton&){}
    static Singleton * _ins;
};
Singleton * Singleton::_ins = NULL;
int main()
{
    Singleton * ps = Singleton::getInstance();
    Singleton::releaseInstance();
    return 0;
}

观察者模式

定义对象间一种一对多的依赖关系, 使得每当一个对象改变状态, 则所有依赖于它的对象都会得到通知并被自动更新,也成为发布-订阅模式
在这里插入图片描述
实现: 通过多态实现

策略模式

提供便利的的功能/算法切换

代理模式

为其他对象提供一种代理以控制对这个对象的访问

linux系统编程

系统调用

系统调用的I/O方法和标准C的I/O方法的区别是:
1 基于标准C的函数以字母“f”开头
2 系统调用I/O方法是更低一级的接口,需要进行用户态和内核态的切换
3 系统调用直接处理文件描述符,而标准C函数则处理 FILE* 类型的文件句柄
4 标准C的I/O方法使用自动缓冲技术,使程序能减少系统调用,从而提高程序的性能
5 基于标准C的I/O方法替用户处理有关系统调用的细节,比如系统调用被信号中断的处理等等

api:

  • int open(const char* pathname, int flags);
    flags: O_RDONLY、O_WRONLY和O_RDWR
  • int close(int fd);
  • int fsync(int fd); // 刷新缓冲区
  • ssize_t read(int fd, void *buf, size_t count);
    返回所读取的字节数目
  • ssize_t write(int fd, const void* buf, size_t count);
  • off_t lseek(int fd, off_t offest, int whence); // 定位文件读写位置
    fd: 文件描述符
    offset:移动的偏移量,单位为字节数
    where:SEEK_SET,SEEK_CUR,SEEK_END
  • int fcntl(int fd, int cmd) // 修改问文件属性
  • int chmod(const char *path, mode_t mode) // 更改文件权限
  • int chown(const char *path, uid_t owner, gid_t group); // chown更改所属用户
进程

创建进程:
调用fork函数创建一个新进程。包括代码、数据和分配给进程的资源全部都复制一份,除了进程标识pid不同,其他基本都跟父进程一样,由fork创建的新进程被称为子进程(child process)

pid_t fork();
1)在父进程中,fork返回新创建子进程的进程ID;
2)在子进程中,fork返回0;
3)创建子进程失败,fork返回­1;

终止进程:
void exit(int status);

进程状态:
创建:应用程序启动,获取PCB
就绪:未获得资源处理器
执行:系统调度,获取资源
阻塞:进行了阻塞的操作,如耗时I/O操作
终止:进程结束

当I/O完成时,从阻塞状态进行就绪状态

IPC

其他的通信方式为 管道(有名管道、无名管道),套接字socket

IPC为进程间通信机制, 主要包含消息队列、信号量、共享内存

信号量:
创建信号量:
sem_t *sem_open(const char *name,int oflag, mode_t mode , int value);

等待一个信号量:
检查信号量的值,如果其值小于或等于0,那就阻塞,直到该值变成大于0,然后等待进程将信号量的值减1,进程获得共享资源的访问权限

int sem_wait(sem_t *sem);
int sem_trywait(sem_t *sem); // sem_trywait()函数不会阻塞当前线程

释放下信号量:信号量的值加1,如果有进程阻塞着等待该信号量,那么其中一个进程将被唤醒。
int sem_post(sem_t* sem);

消息队列:
是一个链表。进程(线程)可以往里写消息,也可以从里面取出消息。一个进程可以往某个消息队列里写消息,然后终止,另一个进程随时可以从消息队列里取走这些消息

创建:
mqd_t mq_open(const char *name, int oflag);

发送:
int mq_send(mqd_t mqdes, const char *msg_ptr, size_t msg_len, unsigned msg_prio);

接收:
ssize_t mq_receive(mqd_t mqdes, char *msg_ptr, size_t msg_len, unsigned *msg_prio);

共享内存:
shm_open函数创建了共享内存区域,此时会在/dev/shm/创建共享内存文件.
int shm_open(const char *name, int oflag, mode_t mode);

通过ftruncate函数改变shm_open创建共享内存的大小为页大小
int ftruncate(int fd, off_t length);

通过mmap函数将创建的共享内存文件映射到内存
void* mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset);

通过munmap卸载共享内存
int munmap(void* start,size_t length);

shm_unlink删除内存共享文件
int shm_unlink(const char *name);

多线程
api

编译:gcc ‐o pthread_test ‐lpthread
线程创建:
int pthread_create(pthread_t* thread,const pthread_attr_t * attr, void* (start_routine)(void), void* arg);
线程退出:
void pthread_exit(void *value_ptr)

主进程等待子线程:
int pthread_join(pthread_t thread, void **value_ptr);
第二个参数为一个用户定义的指针,它可以用来存储被等待线程的返回
值,线程阻塞的函数,调用该函数将一直等待到被等待的线程结束为止

int pthread_detach(pthread_t tid);

在进程内某个线程可以取消另一个线程:
int pthread_cancel(pthread_t thread);

线程同步

互斥锁:
pthread_mutex_t

初始化:
int pthread_mutex_init(pthread_mutex_t mutex, const pthread_mutexattr_t attr)

上锁:
int pthread_mutex_lock(pthread_mutex_t *mutex);

解锁:
int pthread_mutex_unlock(pthread_mutex_t *mutex);

销毁锁:
int pthread_mutex_destroy(pthread_mutex * mutex);

读写锁:
读操作可以共享,写操作是独占的,读可以有多个在读,写只有
唯一个在写,同时写的时候不允许读
int pthread_rwlock_rdlock(pthread_rwlock_t*);

自旋锁:
其他线程空转cpu,一直等待进入临界资源

互斥锁:
切换cpu, 让出执行权, 线程阻塞住,调度其他的线程

条件变量:
条件变量被用来阻塞一个线程,当条件不足时,线程通常先解开相应的互斥锁进入阻塞状态,等待条件发生变化。一旦其他的某个线程改变了条件变量,它将通知相应的条件变量唤醒一个 或多个正被此条件变量阻塞的线程。
这些线程将重新锁定互斥锁并重新测试条件是否满足。

条件变量初始化:
int pthread_cond_init(pthread_cond_t * cond, const pthread_condattr_t* attr);

条件变量销毁:
int pthread_cond_destroy(pthread_cond_t *cond);

等待条件变量:
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex)

唤醒等待的线程:
int pthread_cond_signal(pthread_cond_t* cond);

信号量:
sem_post() 增加信号量,只有当信号量大于0时,函数 sem_wait() 才能返回,并将信号量的值减1; 当信号量等于0时, sem_wait() 将被阻塞直到信号量的值大于0.

int sem_init(sem_t *sem, int pshared, unsigned value);

  1. sem:指向信号量结构的一个指针
  2. pshared:不为0时此信号量在进程间共享,否则只能为当前进程的所有线程共享。
  3. value:给出了信号量的初始值。

增加信号量:
int sem_post(sem_t* sem);

减少信号量:
int sem_wait(sem_t *sem);

死锁检测

死锁:多个进程,互相争取资源,造成互相等待
产生死锁的四大必要条件:
1 资源互斥
2 占用并请求
3 资源不可剥夺
4 环路等待: 死锁发生时,系统中一定有两个或者两个以上的进程组成一条环路,环路上的每个进程都在等待下一个进程所占有的资源

pthread_mutex_lock() 函数声明开始用互斥锁上锁,此后代码直至调用pthread_mutex_unlock()为止,被上锁,即同一时间只能被一个线程调用执行,当一个线程执行到pthread_mutex_lock()处时,如果该锁此时被另一
个线程使用,那么此线程被阻塞,线程一直阻塞直到另一个线程释放此互斥锁

非阻塞上锁 pthread_mutex_trylock,其他进程进行抢占的时候不会阻塞

定位方法:

  1. pstack pid 输出进程堆栈信息,查找死锁线程:如_lll_lock_wait() 可能死锁相关的线程
  2. gdb 进入该进程:gdb attach 进程pid
  3. 查看线程信息 ,找到死锁线程id: info threads
  4. 进入线程: t 线程id
  5. 查看线程堆栈: bt
  6. 查看可能出错的帧: f num ,如_lock = 2 , 找到死锁对象,和死锁拥有者的线程id(LWP 为线程,)
  7. 打印锁的信息: p mutex , 查看锁的拥有者和上锁次数

分析原因:线程a 持有锁对象,未释放,等待线程b结束(b 中调用pthread_join(a); 线程b等待线程a锁,到导致死锁

线程a: 获得锁,未释放,原因等待线程b解决,pthread_join(b)
线程b: 抢占锁

线程a 和 线程b 使用非阻塞锁 pthread_mutex_trylock

使用valgrind检测死锁:
valgirnd --tool=drd --trace-mutex=yes ./a.out

如何解决:

避免嵌套锁:避免在持有一个锁的情况下再申请另一个锁
线程加锁顺序一致,如有两把锁,线程a 获取锁1,再获取2, 线程b也先获取锁1,在获取锁2
锁超时:使用可超时的锁操作,如果在指定时间内未能获取锁,则放弃等待
死锁检测:通过检测工具来识别死锁情况,并处理

a.线程加锁顺序一致,如有两把锁,线程a 获取锁1,再获取2, 线程b也先获取锁1,在获取锁2
b. 使用非阻塞锁
c. 用完锁后释放锁

coredump

当进程接收到某些 信号 而导致异常退出时,就会生成 coredump 文件。
gdb命令:
gdb coredump core.进程id
调试:
编译时加入-g参数,可以生成core文件
ulimit -c unlimited (不设限制)

进程/线程资源占用过高

通过top 命令查看cpu 资源
定位线程是否有高耗cpu操作

解决1:
如线程是一个死循环,需要要sleep(), 防止一直占用cpu 资源

解决2:
优化线程的代码,避免高cpu 和 耗时IO 操作

解决3:
查看是否有内存泄露的情况,使用valgrind 工具进行检测
linux终端中输入:
valgrind -tool=memcheck ./a.out

解决4:
如何创建的线程数量过多,则使用线程池来控制线程数量

解决5:
如果因为资源限制,可以增加系统资源(如内存,文件标识符限制等)
/etc/security/limits.conf :
soft nofile 65536
hard nofile 65536

linux网络编程

物理层,链路层,网络层,传输层,用户层

socket

客户端:
socket()
connect()
send() // 向socket写入数据
close()

服务器端:
socket()
bind() // 绑定ip地址和端口号
listen()
accept() //接收客户端的请求
recv() // 从socket中读数据
close()

IO模型

1 阻塞IO: 调用函数,等待函数返回,期间什么都不能做
2 非阻塞IO: 非阻塞等待,没有就绪可以做其他的事情
3 信号驱动IO : 安装信号函数,程序继续运行不阻塞,当IO时间就绪,接收到SIGIO信号,处理IO事件
4 多路IO复用: 如select/poll ,会阻塞进程,但是和阻塞IO不同的是者这连个函数可以同时阻塞多个IO,可以同时对多个IO进行读操作、写蒿草的IO进行检测,当知道有数据可读或者可写时,才真正调用IO操作函数
5 异步IO: 可以调用aio_read函数,当内核将数据拷贝到缓冲区后,再通知应用程序,用户可以直接使用数据

同步和异步的区别:
异步是内核将数据拷贝到用户区,不需要用户字节接收数据,直接可以使用;同步时内核通知用户数据到了,然后用户自己调用对应的函数去接收数数据

触发方式:
水平触发(Level-Triggered)和边沿触发(Edge-Triggered)是 epoll 提供的两种不同的事件通知模式

水平触发:满足IO复用条件即触发
边沿触发:新的IO就绪事件到达即触发

如客户端一次发送数据100个字节数据到服务端,服务端一次读50个字节,
服务器设置为水平触发,则recv执行2次,
服务器设置为边缘触发,则recv执行1次

使用场景: 对于发送的数据包较大,则设置为边缘触发触发,循环读取数据;数据包较小,则设置为水平触发

select

作用: 实现多个客户端正常连接服务器,并且每个客户端正常的收发数据



int select(int maxfdp, fd_set *readset, fd_set *writeset,fd_set *exceptset, struct timeval *timeout);


maxfdp:客户端IO的最大socekfd

readfds、writefds、exceptset:判读服务端IO是否可以读,可写,异常

timeout:用于设置select函数的超时时间

poll
int poll(struct pollfd *fds, nfds_t nfds, int timeout);

fds是一个poll函数监听的结构列表. 每一个元素中, 包含了三部分内容: 文件描述符, 监听的事件集合, 返回的事件集合.
nfds表示fds数组的长度
timeout表示poll函数的超时时间, 单位是毫秒

相对与select 优点:
定义简单,只需要数组
pollfd结构包含了要监视的events和内核返回的的revent,不再使用select“参数-值”传递的方式. 接口使用比select更方便
缺点:

poll返回后,需要轮询pollfd来获取就绪的描述符,对IO的数量有限制,IO数量越多,性能越差

epoll
int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

第一个参数是epoll_create()的返回值(epoll的句柄).
第二个参数表示动作,用三个宏来表示.
第三个参数是需要监听的fd.
第四个参数是告诉内核需要监听什么事

op:
EPOLL_CTL_ADD :注册新的fd到epfd中;
EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
EPOLL_CTL_DEL :从epfd中删除一个fd

events:
EPOLLIN : 表示对应的文件描述符可以读 (包括对端SOCKET正常关闭);
EPOLLOUT : 表示对应的文件描述符可以写;
EPOLLET : 将EPOLL设为边缘触发(Edge Triggered)模式, 这是相对于水平触发(Level Triggered)来说的.

epoll 优点:

接口使用方便,不需要每次循环都设置关注的文件描述符, 也做到了输入输出参数分离开
数据拷贝轻量: 只在合适的时候调用 EPOLL_CTL_ADD 将文件描述符结构拷贝到内核中
事件回调机制: 避免select poll使用轮询的方式检测就绪队列, 而是使用回调函数的方式, 将就绪的文件描述符结构加入到就绪队列中, epoll_wait 返回直接访问就绪队列. 这个操作时间复杂度O(1)
没有数量限制: 文件描述符数目无上限

tcp:

tcp特点:面向连接的,可靠的,基于字节流的传输层协议,每个5元组只能建立一条连接,tcp头部需要20个字节、

tcp可靠性:
检验和 、 序列号确认应答、超时重传、最大消息长度、滑动窗口(发送包的间隔),拥塞控制(tcp 慢启动,先少量数据探路,再根据网络状态确定发送的数据量)

tcp 粘包和拆包:
1 服务器分两次读取到两个独立的数据包,分别时D1和D2,没有粘包和拆包
2 服务器一次接收到2个数据包,D1和D2粘合在一起,成为TCP粘包
3 服务器分两次读到两个数据包,第一次读取到了完成的包D1 和 D2包的一部分,第二次读取到了D2包的剩余部分,成为TCP拆包
在这里插入图片描述

三次握手:
第一次:
客户端发送SYN包, seq =x
connect()
第二次;
服务器收到SYN包,发送ACK(ack=x+1)包和SYN包(seq =y)
listen(), 创建半连接队列
第三次;
客户端收到ACK和SYN包,发送ACK包(ack = y+1)
accept() ,创建全连接队列

为什么需要三次握手:
1 确保双端具备收发数据包的能力
2 确定双端按照序列号发送
3 避免无用 的连接初始化,造成资源浪费

四次挥手:
第一次:
客户端发送FIN包,此时客户端不能发送数据
第二次:
服务器收到FIN包,并发送ACK包,等待数据传输完成
第三次:
传输数据完成,服务器发送FIN包,可以断开连接
第四次:
客户端收到FIN包后,发送ACK包,等待一段时间,彻底断开连接

为什么需要此次挥手?
网络应用程序可能有数据要发送,不能直接发送FIN包

udp:

特点: 无连接的,不保证有序,头部仅需要8个字节,支持一对一,一对多,多对多通信,面向报文的连接
在这里插入图片描述

http:

http 特点:

支持客户/服务器模式:
简单快捷: 客户端请求时,只需要传送方法和路径
灵活 : http 允许传输任意类型的数据对现象
无连接 : 每次连接只能处理一个请求,服务器处理完成客户的请求后,并收到客户的应答后,即断开连接
无状态: 对任务没有记忆能力

cookie: 客户端发送给服务器的信息,以文本形式存储在客户端,客户端每次向服务器发送请求时会带上特殊的新信息

session:服务i其使用记录客户端状态的机制,客户端通过cookie存储一个session_id ,然后保存在session库中,并查找对应的信息

方法:
get: 请求服务器资源
post:发送信息服务器
put: 传输文件
head: 获取报文首部,确认url是否有效
delete : 删除文件
options:查看url支持的http方法

区别:
get 在请求url长度有限,post 没有限制

浏览器从输入url 到页面显示的过程:
1 输入网址
2 浏览器查找域名的ip地址
3 浏览器向web服务器发送一个http请求
4 服务器永久重定向响应
5 服务区处理请求
6 服务会返回一个http 响应
7 浏览器显示HTML
8 浏览器发送请求获取html资源

http 和 https 的区别?
http 是基于tcp协议的从www服务器传输超文本到本地浏览器的协议
https 基于 http 加入 SSL层,进行过加密,需要ca申请证书
http 的端口的是80, https的端口是443

http版本:
1.0 :
不支持长连接,需要设置keep-alive参数
1.1:
默认长连接
2.0:
多路并用,一个tcp中的多个http请求是并行的
3.0:
无对头阻塞,建立连接

cookie 和session的区别?
cookie和session都是会话的一种方式 ,获取用户的具体操作,cookie数据存储在客户的浏览器上,session数据存储在服务器上,cookie并不是很安全,session会在一定时间内保存在服务器上,当访问增多时,会占用服务器的性能

linux命令

文件:

mv dir1 new_dir : 重命名文件

ln -s file1 lnk1 : 创建一个指向文件或目录的软链接
ln file1 lnk1 : 创建一个指向文件或目录的物理链接

find / -name file1 : 从 ‘/’ 开始进入根文件系统搜索文件

head -n 20 file1 : 查看文件前20行的内容
tail -n 30 filel : 查看文件后30行的内容

进程:

ps : 查看系统进程

-a : 显示一个终端所有的进程
-e :显示所有进程
-f : 显示所有字段(UID PPIP C STIME字段)
-u : 显示当前用户的进程和内存使用情况
-l : 长格式输出
-r : 只显示正在运行的进程
-h : 不显示标题
-x : 显示没有控制终端的而进程

常用:
ps -ef : 查看所有进程
ps -aux : 查看所有进程
ps -ef | grep 进程名/PID : 查看指定线程

ps -ef :
UID: 用户ID,即进程的拥有者
PID: 进程ID
PPID; 进程的父进程ID
C: cpu占用率

ps -aux:
%MEM:进程占用物理内存的百分比
RSS:进程占用实际物理内存的大小(单位KB)
STAT:进程状态

grep: 过滤,可以通过正则表达式搜索文本
ps -ef | grep mysql

查看CPU/内存占用率最高的进程:
-pcpu 表示降序,+pcpu 表示升序
ps -aux --sort = -pcpu | head -11
ps -aux --sort = -pmem | head -11

指定用户的进程:
ps -u root -ef

分页:
ps -ef | more
q键退出,空格键翻页

stat:
D:睡眠状态(不可被唤醒),常用于I/O情况。
R:进程正在运行
S:睡眠状态(可被唤醒)
T:停止状态
W:内存交互状态
Z:僵尸进程(不存在但暂时无法消除)
<:高优先级
N:低优先级
s:包含子进程
l:多线程
+:位于后台

pstree -p 主线程id : 查看主进主线程和新线程的关系
pstack 线程id : 查看线程栈结构

top : 查看系统性能

网络:

netstat : 监控TCP/IP网络的非常有用的工具

-a : 列出所有连接
-t : TCP协议的连接
-u : 列出UDP协议的连接
-n : 选项禁用方向域名解析功能
-l : 列出正在监听的套接字
-p: 选项查看进程信息
-i : 打印网络接口

hostname : 查看机器名

ifconfig eth0: 显示一个以太网卡的配置
ifup eth0 : 启动网卡
ifdown eth0
ip link show :查看网卡信息

磁盘空间:

df -h :显示已经挂载的分区列表
mount /dev/hda2 /mnt/hda2 :挂载一个叫做hda2的盘

gdb调式

命令:

b : 设置断点, + 行号/函数名
s :单步调试,跳入自定义的函数内部执行
n:单步调式,函数直接执行
finish: 结束当前函数,返回函数调用点 , 函数执行完
return : 在当前位置结束函数执行,返回上一级

c: 继续运行程序,直到下一个断点
u : u 行号,直接运行到该行代码

r : 重新开始运行文件
p: 打印值和地址
i : 查看变量名
watch: 查看变量名是否更改
where: 查看当前执行位置

f : 如 f 5 (frame),切换调用栈的第5层
t : 如 t 10(thread),切换到tid = 10 的线程

q: 退出gdb

多线程:
  1. 编译 g++ main.cpp -o test -g
  2. 查看进行pid ps aux | grep test
  3. 查看线程pid ps -aL | grep test
  4. 查看主进程和线程关系 pstree -p pid
  5. 调试 db attach -p 主线程id
  6. 查看线程编号 info threads
  7. 切换线程 thread id
  8. 查看线程栈结构,找到入口函数 bt
  9. 添加断点 b 函数名
  10. 重新启动程序,运行到断点处 r
  11. 只运行当前线程 set scheduler-locking on 所有线程都运行 set scheduler-locking off
  12. 执行下一步 n
  13. 打印信息 p
  14. list 显示代码
core dump :

ulimit -c unlimited 查看是否产生core文件
disassemble : 打印出错的汇编代码
bt : 查看调用栈

mysql

DDL (数据定义语句):
在这里插入图片描述
数据库:
CREATE DATABASE 数据库名称;
DROP DATABASE 数据库名称;
USE 数据库名称;

表:
CREATE TABLE 表名(
字段名1 数据类型,
字段名2 数据类型,

字段名n 数据类型 – 最后一行不能加逗号!
);
SHOW TABLES;
DESC 表名; #查看表的结构
ALTER TABLE 表名 RENAME TO 新的表名;
ALTER TABLE 表名 ADD 列名 数据类型 [ COMMENT 注释 ]; # 添加列名

DROP TABLE 表名;
TRUNCATE TABLE 表名; # 删除表名并重新创建

DML(数据操作语言) :
插入:
INSERT INTO 表名(列名1,[列名2],[…)VALUES(值1,[值2],[…]); # 值1对应列名1

修改:
UPDATE 表名 SET 列名1=值1

删除:
DELETE FROM 表名 [WHERE 条件];

DQL (数据查询语言):

SELECT 字段列表
FROM 表名列表
WHERE 条件列表
GROUP BY 分组字段
HAVING 分组后条件
ORDER 排序字段
LIMIT 分页限定

查询:
SELECT * FROM 表名 ; #查询表中所有字段
SELECT 字段1,[字段2],[…] FROM 表名 WHERE 条件列表;

模糊查询:
关键字LIKE,同时需要了解常用的通配符:
%(百分号):表示任意长度的字符串
_(下划线):表示任意单个字符

SELECT * FROM Student WHERE Name LIKE ‘马%’;

**排序查询:**ORDER BY
ASC:升序排序
DESC:降序排序

SELECT * FROM student ORDER BY Sage [ASC];

聚合函数:
聚合函数的作用就是:是查询功能更加强大,同时方便用户使用

COUNT()表示统计表的总行数,即遇到重复的值不会加1
ALL:不去重(MySQL默认)
SELECT COUNT(
) FROM student;
SELECT COUNT(Score) FROM student;

分组查询:
SELECT 字段列表 FROM 表名 [WHERE 分组前条件限定] GROUP BY 分组字段名;
#查询男同学和女同学各自的数学平均分
SELECT sex, AVG(math) FROM stu GROUP BY sex;

分页查询:
分页查询使用关键字LIMIT
SELECT 字段列表 FROM 表名 LIMIT 起始索引,查询条目数;

#查询student表中前三条记录
SELECT * FROM student LIMIT 0,3;

内连接
内连接是查询多张表的交集数据,。它是从多张表中查询出满足条件的待查询字段数据,就是多张表先要满足给定的条件,等条件筛选后再进行查询。

#隐式内连接
SELECT 字段列表 FROM 表1,表2… WHERE 条件;
#显示内连接
SELECT 字段列表 FROM 表1 [INNER] JOIN 表2 [JOIN …] ON 条件;

外连接:
外连接就算查询一个表的所有数据和满足条件的其他表数据。

#左外连接
SELECT 字段列表 FROM 表1 LEFT [OUTER] JOIN 表2 ON 条件;
#右外连接
SELECT 字段列表 FROM 表1 RIGHT [OUTER] JOIN 表2 ON 条件;

约束:
约束就是对表中列上的数据进行相应的约束,确保数据的正确性,有效性和完整性。约束语句可归为DDL。

主键约束 保证列的所有数据非空且唯一(主键是一行数据的唯一标识) PRIMARY KEY

非空约束 保证列的所有数据不能为null NOT NULL

唯一约束 保证列的所有数据不重复 UNIQUE、

外键约束 保证数据的一致性和完整性(外键用来连接两个表) FOREIGN KEY

事物的ACID特性:

原子性(Atomicity):事物是数据库的不可分割的逻辑工作单位,要么全做,要么全不做

一致性(Consistency):事物的执行结果必须使数据库从一个一致性状态转变成另一个一致性状态。比如,数据库中有两个用户,张三和李四,张三向李四转账,转账后总的资金是保持不变的

隔离性(Isolation):一个事物的执行不能被其他事物干扰

持续性(Durability,也称永久性 Permanence ):事物一旦提交或回滚,它对数据库的改变是永久的

索引:
索引是一种有序的存储结构,按照一个或者多个列的值进行排序,底层实现为B+树
目的:提升搜索效率
使用场景:where , group by ,order by
代价: 占用存储空间,进行增删改操作耗时

索引的种类:
主键索引: 非空唯一索引,一个表只能由一个主键索引,primary key(index 类名)
唯一索引: 索引列不能出现相同的值,可以有null值,unique(index)

–创建学生表02,指定NAME为普通索引
CREATE TABLE STUDENT02(
ID INT
,NAME VARCHAR(20)
,AGE INT
,INDEX idx_name(name)
)
–查看索引
SHOW INDEX FROM STUDENT02;

–创建学生表03,指定ID为唯一索引
CREATE TABLE STUDENT03(
ID INT
,NAME VARCHAR(20)
,AGE INT
,UNIQUE INDEX idx_name(ID)
)

redis

redis,远程字典服务,通过tcp与redis建立连接,是一种请求回应模式,通过字典的方式建立索引来存储数据
内存数据库,数据存储在内存中
kv数据库,每一个节点对应key和value ,通过哈希表来实现
数据结构数据库: string list hash zset (log2n 有序 调表) set stream hyperloglog(统计)

应用场景;
hash : 存储某一个对象的特征,如朋友圈点赞数 文章标题
list : 朋友圈链表

string : 二进制安全字符串, 包含长度

linux操作:
所有的k都是string v:b不同的数据结构里面,是string类型的
对象类型: string list hash zset set
数据结构类型: string 可能用int 、 raw、embstr存储

string:
set stu li (k: stu v: li) 返回 (ok)
get stu 返回(“li”)

hash: (散列表,对顺序不关注,filed 唯一)
hset num:001 sex man (sex 为filed , man为value)
hget num:001 sex 返回(“man”)

set:
sadd class a b c
smembers class 返回(“a” “b” “c”)

zset:
zadd rank 10 a 20 b 30 c
zrange rank 0 -1 (-1 最后一个) 返回(“a” “b” “c”)

list: (可重复插入 , 插入有序)
lpush list a b c (l: left)
lragne list 0 -1 返回(“c” “b” “a”)

xml

结构:


<?xml version="1.0" encoding="UTF-8"?>
<wang>
        <gender>man</gender>
</wang>

XMLDocument xml;
	//插入声明
	XMLDeclaration* declaration = xml.NewDeclaration();
	xml.InsertFirstChild(declaration);
	//插入节点
	XMLElement* rootNode = xml.NewElement("wang");
	XMLElement* gender = xml.NewElement("gender");
	//插入元素
	XMLText* man = xml.NewText("man");
	
	//对应
	xml.InsertEndChild(rootNode);
	gender->InsertFirstChild(man);
	rootNode->InsertEndChild(gender);
	xml.SaveFile("wang.xml");

json

nlohmann/json

写:


class Address {
public:
    std::string street;
    std::string city;
};
 
class Person {
public:
    std::string name;
    Address address;
};
 
int main() {
    Person person;
    person.name = "John";
    person.address.street = "123 Main St";
    person.address.city = "New York";
 
    // 将Person对象转换为JSON字符串
    nlohmann::json j;
    j["person"]["name"] = person.name;
    j["person"]["address"]["street"] = person.address.street;
    j["person"]["address"]["city"] = person.address.city;
 
    std::string jsonStr = j.dump();
    std::cout << jsonStr << std::endl;
 
    return 0;
}

输出:
{
“person”: {
“name”: “John”,
“address”: {
“street”: “123 Main St”,
“city”: “New York”
},
}

读:

#include <nlohmann/json.hpp>
#include <iostream>
#include <string>
 
int main() {
    std::string json = "{\"person\":{\"name\":\"John\",\"address\":{\"street\":\"123 Main St\",\"hobbies\":[\"Reading\",\"Hiking\"]}}";
    nlohmann::json j = nlohmann::json::parse(json);
 
    std::string name = j["person"]["name"];
 
    std::string street = j["person"]["address"]["street"];
 
    for (const auto& hobby : j["person"]["hobbies"]) {
        std::string hobbyStr = hobby;
        std::cout << "Hobby: " << hobbyStr << std::endl;
    }
    return 0;
}

makefile

自动化编译项目,完成后,只需要一个make命令即可编译

gcc/g++

gcc是GCC中的GUN C Compiler(C 编译器),不能完成连接的过程
g++是GCC中的GUN C++ Compiler(C++编译器)

区别:
对于 .c和.cpp文件,gcc分别当做c和cpp文件编译(cpp的语法规则比c的更强一些)
对于 .c和.cpp文件,g++则统一当做cpp文件编译

使用:
gcc hello.c -lstdc++
./a.out

gcc hello.cpp
./a.out

gcc 参数:
-c 生成目标文件(obj)

三要素: 目标 依赖 命令

如:
all : test
@echo “hello all”

,
simple: main.o test.o
gcc -o simple main.o foo.o
main.o : main.c
gcc -o main.o -c main.c
foo.o : foo.c
gcc -o foo.o -c foo.c
clean:
rm simple main.o foo.o
,

运行:
make :
gcc -o main.o -c main.c
gcc -o foo.o -c foo.c
gcc -o simple main.o foo.o

从左到右的顺序运行

.PHONY: main clean 避免程序所在的路径创建于clean相同的文件名而报错

符号

$@ : 表示一个规则中的目标
$^: 表示规则中所有的先决条件
$< : 表示规则中第一个先决条件

.PHONY:all
all: first second third
	@echo "\$$@ = $@"
	@echo "$$^ = $^"
	@echo "$$< =$<"
first:
	@echo "1 first"
second:
	@echo "2 second"
third:
	@echo "3 third"

输出:
1 first
2 second
3 third
$@ = all
$^ = first second third
$< = first
变量:

a = hello
$(a)

.PHONY:clear
RM = rm
CC = gcc
EXE = simple
OBJS  = main.o  foo.o
$(EXR) : $(OBJS)
	$(CC) -o $(EXE) $(OBJS)
main.o : main.c
	$(CC) -o main.o -c main.c
foo.o : foo.c
	$(CC) -o foo.o foo.c
clean:
	$(RM)  $(EXE) $(OBJS)
自动替换

wildcard 是通配符函数,可以通过它得到我们锁需要的文件形式
$(wildcard pattern)

patsubst 函数 用来进行字符串的替换,形式为
$(patsubst pattern , replacement, text)

.PHONY:clean
CC = gcc
RM = rm
EXE = simple
#SRS = main.c foo.c
SRS = $(wildcard*.c)
#.c 替换对应的。o
#OBJS = foo.o foo2.o main.o
OBJS = $(patsubst %.c,%.o,$(SRS))
$(EXE) : $(OBJS)
	$(CC) -o $@$^
%.o : %.c
	$(CC) -o $@ -c $^
clean:
	$(RM) $(EXE) $(OBJS)

文件路径

头文件:
HEADER_PATH = -I ./include
库文件路径:
LIB_PATH = -L ./lib
依赖库名称:
LIBS = -l pthread

CROSS =
# 定义CC为gcc编译
# 定义g++
CXX = $(CROSS)g++
# 定义debug方式为 -g -02
DEBUG = -g -02
CFLAGS = $(DEBUG) -Wall -c
RM = rm -rf
# 定义 SRS 为当前工程目录下所有的.cpp文件
SRCS = $(wildcard .//*.c) (为./)
# 定义OBJS为SRCS对应的.o文件
OBJS = $(patsubst %.c,%.o,$(SRCS))
# 定义HREADER_PATH 为当前工程中的头文件路径
HEADER_PATH = -I ./include/
# 定义LIB_PATH = -L ./lib/
# 输出LIB_PATH的内容
$(warning LIB_PATH)
# 连接库
LIBS = -lpthread
# 定义生成的版本
VERSION = 1.0.0
# 定义生成的可执行文件的名称
TARGET = simple.$(VERSION)
#告诉编译器生成的可执行文件时库存放的目录,以及库的名字
$(TARGET) : $(OBJS)
	$(CXX) $^ -o $@ $(LIB_PATH) $(LIBS)
$(OBJS):%.o : %.c
#告诉编译器生成的中间文件时头文件所在的目录
	$(CXX) $(CFLAGS) $< -o $@  $(HEADER_PATH)
clean:
	$(RM) $(TARGET) *.o

cmake

跨平台的编译工具,可以用简单的语言描述所有平台的安装,输出各种makefile或者project文件

命令行执行:
mkdir build
cd build/
cmake … (…返回上一级目录)

#单个目录实现
#CMake 最低版本号需求
cmake_minimum_required(VERSION 2.8)
PROJECT(0VOICE)
#手动输入文件
SET(SRC_LSIT main.c)
MESSAGE(STATUS "THIS IS BINARY DIR" $(PROJECT_BINARY_DIR))
MESSAGE(STATUS "THIS IS SOURCE DIR" $(PROJECT_BINARY_RIR))
#添加头文件路径
INCLUDE_DIRECTORIES("${CMAKE_CURRENT_SOURCE_DIR}/dir")
#相对路径的方式
INCLUDE_DIRECTORIES(dir)
MESSAGE(STATUS "CMAKE_CURRENT_SOURCR_DIR->" ${CMAKE_CURRENT_SOURCE_DIR})
# 添加dir子目录
ADD_SUBDIRECTORY("${CMAKE_CURRENT_COURCE_DIR}/dir")
# 添加dir2子目录
ADD_SUBDIECTORY("${CMAKE_CURRRENT_SOURCE_DIR}/dir2")

# hello 为执行文件,SRC_LIS为所依赖的c/cpp
ADD_EXECUTABLE(hello ${SRC_LIST}) 
#生成库
TARGET_LINK_LIBRARIES(hello dir dir2)

# 将执行文件安装到bin目录
INSTALL(TARGETS hello RUNTIME DESTINATION bin)

shell


执行: 
chmod +x  ./test.sh
./test.sh

打印:
echo 

定义变量:
myName = "li"

使用变量:
echo $myName

删除变量:
unset myName

$0  脚本名称
$n  传入参数
$$  进程id

管道符:
命令1 | 命令2
ll -a /etc | more

grep命令:
grep [选项] “内容” 文件/路径
选项:
-i 忽略大小写
-n 输出行号
grep -i "user" /etc/passwd

数组:
array_name=(value0 value1 value2 value3)

字符串:
string="abcd"
echo ${#string} #输出 4

print:
$ printf "%d %s\n" 1 "abc"
1 abc

**if:** 
if [ $a == $b ]
then
echo "a is equal to b"
fi


if [ expression ]
then
...
else
...
fi

**case:**
casein
模式1)
command1
command2
command3
;;
esac

case $Num in
1) echo 'You select 1'
;;
2) echo 'You select 2'
;;
3) echo 'You select 3'
;;
4|5) echo 'You select 4 or 5'
;;
*) echo 'default'
;;
esac


**for:**
for var in 1 2 3 4 5
do
echo "The value is: $var"
done

while expression
do
...
done


break 跳出循环
continue命令,不执行后面的命令,继续循环

function function_name () {
[commands]
...
[ return value ]
}


myfunc () {
echo "hello world"
}

输出重定向
$ command > file #这样,输出到显示器的内容就可以被重定向到文件。
$ who > users
$ echo "helloworld" > users

输入重定向
command < file #这样,本来需要从键盘获取输入的命令会转移到文件读取内容

输出重定向到 /dev/null
$ command > /dev/null

shell文件包含
. filename
或
source filename

$chomd +x main.sh
./main.sh
http://wenong.so.c

音视频

卡顿原因:音视频不同步,数据包丢失,网络延迟(帧队列满)时间戳异常
解决办法:增加带宽、优化编码参数、恢复时间戳增量 动态缓冲区

黑屏: metadata是否正常;是否有视频sequence header 是否有视频帧数据;时间戳是否单增;是否跟视频数据一致

RTMPPlayer -->
PacketQueue Decode FrameQueue AudioOutSDL (sonic 变速)
–>AVSync(音视频同步)

解决方案:
1 推流的时候,根据推流的数量 和码率,计算缓存时常,如果超过一定的时间(200ms),通过回应函数修改码率
2 通过回调函数 声音变速(sonic)为1.2倍速, 消耗队列数量过大

ffmpeg

播放器框架:
媒体文件 --解复用–音频包/视频包队列—音视频解码—采样帧队列–同步控制—输出视频和音频

AVFormatContext: 封装格式上下文结构体(类似c++中的类),统领全局的结构体
AVInputFormat demuxer : 每种封装格式(FLV)对应的结构体

AVStream:视频文件中的每个视频(音频)流对应的结构体

AVCodecContext: 解编码器上下文结构体,保存了视频编码相关的信息

AVCodec:每种视频(音频)编码器(h264解码器)对应的一个结构体

AVPacket: 存储一帧压缩编码数据

AVFrame: 存储一帧解码后像素(采样(音频))数据

流媒体协议

rtmp

RTMP播放基本流程:

  1. TCP三次握手
  2. RTMP握手
    a 客户端向服务端同时发送C0+C1
    b 服务端确认版本号后,向客户端同时发送S0+S1+S2
    c 客户端接收到S2后发送C2到服务端
  3. connect连接
  4. createStream创建流 ,该通道用于传输视频、音频
  5. play 播放流
  6. deleteStream删除流
rtsp

包含: RTSP(控制命令,play teardown) 、 RTP(负责音视频的数据发送) RTCP 、 SDP

rtp + rtsp -->udp

推流:

  1. option查询服务器可用方法
  2. announce 发布媒体描述信息
  3. setup 建立rtsp 会话
  4. record 请求数据
  5. RTP数据推送
  6. treadown关闭会话,退出

拉流:
1 option查询服务器可用方法
2 describe 发布媒体描述信息
3 setup 建立rtsp 会话
4 play 请求数据
5 RTP数据推送
6 treadown关闭会话,退出

RTP:
组成由报头和有效载荷组成
RTP报头:
V 版本号
序列号 16位,每发送一个报文,序列号增1,结束这根据序列号来检测报文的丢失情况,重新排序报文,恢复数据
时间戳: 占32位,计算延迟和延迟抖动,并进行同步控制,单位是1ms,时间戳的值为1000ms, 单位是90hz, 换成秒为 1、90000 * 1000 = 0.01s

rtsp 和 http 的区别?
rtsp 引入了几种新的方法,如describe play setup
rtsp为每个会话保持状态session , http是无状态连接(没有记忆能力)
rtsp的客户端和服务器端都可以发送request请求, http只能客户端发起请求

Qt

内存管理:

1 使用对象父子关系进行内存管理的原理:

在创建类的对象时,为对象指定父对象指针。当父对象在某一时刻被销毁释放时,父对象会先遍历其所有的子对象,并逐个将子对象销毁释放

2 使用引用计数对内存进行管理:
当某处使用完资源以后,将引用计数减1。当引用计数为0时,即没有任何地方再使用此资源时,真正释放此资源,谁最后使用资源,谁负责释放资源

new申请对象

构造函数中要用new来申请堆窗口对象,不能使用栈对象, 因为栈对象生命周期只在构造函数内,构造函数执行完成后,栈对象就被销毁了,于是看不到该窗口

class Widget : public QWidget
{
public:
	Widget(QWidget *parent = 0); //继承QWidget或者其派生类的时候,一般要实现带一个QWidget*参数的构造
	函数,用来指定其父窗口的对象。
	QPushButton pb* = new QPushButton;
	pb‐>setParent(this);
	
}
子对象不需delete

子类不需要delete, 子窗口可以通过指定父窗口,来托管子窗口内存的释放,而父窗口又通过它的父窗口来销毁对象,顶级窗口一般在main函数中实例化为可自动销毁的栈对象,子窗口无需进行额外
的释放

main

QWidget 在Qt中是所有窗口类的基类
QApplication用来管理界面应用的控制流以及主要的程序设置,只可以实例化一个

int main(int argc, char** argv)
{
	QApplication app(argc, argv);
	QWidget widget; //Qt中每个窗口都是QWidget对象,或者其派生类的对象。
	widget.show();
	return app.exec(); //消息循环,消息泵,阻塞接收桌面系统传递过来的消息。
}

父子窗口

父子窗口是对象间的组合关系
设置父窗口:
void QWidget::setParent(QWidget *parent)

Q_OBJECT 宏

Q_OBJECT 宏是继承自 QObject 的类的私有部分,为该类提供元对象(meta-object)系统所需的底层支持

元对象
动态类型信息:允许在运行时查询对象的类型信息
信号-槽机制:信号槽机制是 Qt 的一种强大且灵活的回调(callback)系统,用于实现对象间的通信
动态属性:允许在运行时向 QObject 添加自定义的属性值

底层实现原理: 元对象编译器,MOC, 负责生成 QObject 子类的元信息,MOC 是一个预处理程序,它在 C++ 编译器处理源代码之前,扫描包含 Q_OBJECT 宏的 QObject 子类源文件,并输出额外的 C++ 代码

使用技巧:

1 自定义类的构造函数包含QObject

CustomClass::CustomClass(QObject *parent)
    : QObject(parent)
{
}

2 插入 Q_OBJECT 宏,将其放置在类的私有部分

class CustomClass : public QObject
{
    Q_OBJECT
public:
    CustomClass(QObject *parent = nullptr);
};
explicit

explicit 的作用是声明类构造函数是显式调用,而非隐式调用

class Test1
{
public:
    Test1(int n)
    {
        num=n;
    }//普通构造函数
private:
    int num;
};
class Test2
{
public:
    explicit Test2(int n)
    {
        num=n;
    }//explicit(显式)构造函数
private:
    int num;
};
int main()
{
    Test1 t1=12;//隐式调用其构造函数,成功
    Test2 t2=12;//编译错误,不能隐式调用其构造函数
    Test2 t2(12);//显式调用成功
    return 0;
}

信号和槽:

信号和槽用于两个QObject对象或者其派生类之间的通信

**实现 **

继承至QObject的派生类

添加宏Q_OBJECT

signals:
指定声明信号区域 ,规定时公有的,没有返回值,不需要定义

slot:
public protect private ,槽函数和信号的参数类型一致

发射信号:emit clicked()
当一个信号发射出时,会立即执行槽函数,等槽函数执行完毕后,在执行emit之后的而代码,如果一个信号和多个槽函数连接,则执行顺序按照链接时的顺序执行,不同线程中,槽函数的执行顺序是随机的

Connect

1 通过槽函数实现

QObject::connect(const QObject *sender, const QMetaMethod&signal, const QObject *receiver, const QMetaMethod &method);

QPushButton pb;
QObject::connect(&pb, SIGNAL(clicked()), &widget, SLOT(close()));

2 通过函数指针实现

connect(this,&A::func1,this,&B::func2);
连接方式:

AutoConnection (默认):接收者和发送者在同一个线程,则自动使用Qt::DirectConnection类型,收者和发送者不在一个线程,则自动使用Qt::QueuedConnection类型

DirectConnection:槽函数会在信号发送的时候直接被调用,槽函数和信号发送者在同一线程

QueuedConnection:信号发出后,信号会暂时被放到一个消息队列中,需等到接收对象所属线程的事件循环取得控制权时才取得该信号,emit语句后的代码将在发出信号后立即被执行,无需等待槽函数执行完毕

BlockingQueuedConnection:于QueuedConnection一致,不过发送完信号后发送者所在线程会阻塞

UniqueConnection:flag可以通过按位或(|)与以上四个结合在一起使用,当某个信号和槽已经连接时,再进行重复的连接就会失败。也就是为了避免重复连接

实现原理

moc查找头文件中的signals,slots,标记出信号和槽

将信号槽信息存储到类静态变量staticMetaObject中,并且按声明顺序进行存放,建立索引

当发现有connect连接时,将信号槽的索引信息放到一个map中,彼此配对。
当调用emit时,调用信号函数,并且传递发送信号的对象指针,元对象指针,信号索引,参数列表到active函数

通过active函数找到在map中找到所有与信号对应的槽索引
根据槽索引找到槽函数,执行槽函数。

优点和不足

优点:类型安全,松散耦合

缺点:
1 同回调函数相比,运行速度较慢
2 一个信号和多个槽相关联,当信号被发射时,与之相关的槽执行顺序不确定
3 避免造成死循环,即槽中发射所接收的相同信号

区别

使用场合不同:
使用控件的时候,一般关注发射信号,在实现控件类的死后,需要进程事件处理

实现过程不同:
事件:支持异步和同步的通信机制,一个事情产生时放入到事件队列中,然后继续执行,非阻塞
信号:底层实现为回调函数,不支持异步调用

同步和异步

在Qt中,同步和异步通常是指函数的执行方式

同步函数会阻塞调用线程,直到函数执行完成并返回结果后才会继续执行下一行代码。在执行同步函数时,程序的运行状态会被暂停,直到函数执行完毕
异步函数则不会阻塞调用线程,而是在另一个线程或事件循环中执行函数,并立即返回。调用异步函数后,程序可以继续执行下一行代码而不必等待函数执行完成

使用同步函数可以方便地获取函数的返回值,但可能会导致程序的运行状态被阻塞。使用异步函数可以避免程序阻塞,但需要使用信号槽或回调函数等机制来处理函数执行完成后的结果

Qt中,一些常见的异步操作包括网络请求、文件读写、定时器等。通过使用信号槽或回调函数等机制

事件处理机制

事件如何调用:(多态)
事件处理函数都是虚函数,我们override(覆写)了基类的事件处理函数, 而Qt在底层实现了使用基类的指针指向我们的派生类对象, 并且在事件触发时,使用基类指针来调用我们override的事件处理函数

事件分发器

Qt 的界面应用程序都是事件驱动的,Qt中的事件有其对应的事件处理函数,事件先由QApplication::exec()消息循环接收,然后传递给对应窗口。

事件分发器负责将事件从一个对象传递到另一个对象,直到事件被处理或被取消,每个继承自QObject或QObject的类都可以在本类中重写bool event(QEvent *e),来实现相关事件的捕获和拦截

bool event(QEvent *e)
bool Widget::event(QEvent *ev)
{
	/*只对鼠标点击事件进行处理,其它的调用基类的event函数*/
	if(ev‐>type() == QEvent::MouseButtonPress ||
	ev‐>type() == QEvent::MouseButtonDblClick)
	{
	qDebug() << __FUNCTION__ << endl;
	//event‐>ignore(); //代表我无视这个消息
	event‐>accept(); //代表我接收了这个消息
	//return false; //这个消息我没有处理.
	return true; //这个消息在我已经处理过了
}
return QWidget::event(ev);
}
事件过滤器

当父窗口收到来自消息循环的某一事件(信号)时,若父窗口有给子控件安装事件过滤器,并重写事件过滤器函数,则事件到来时会判断该事件要发往哪个控件

使用:
给需要事件过滤的子控件安装事件过滤器
重写父窗口的事件过滤器函数

idget::Widget(QWidget *parent)
    : QWidget(parent)
{
	ui->label->installEventFilter(this);
}

bool Widget::eventFilter(QObject* obj, QEvent* e)
{
    //拦截发往label子控件的鼠标按下事件
    if (obj == ui->label)
    {
        if (e->type() == QEvent::MouseButtonPress)
        {
            QMouseEvent* mouseEv = static_cast<QMouseEvent*>(e);
            QString qs = QString("父窗口的事件过滤器中拦截鼠标按下, x = %1, y = %2").arg(mouseEv->x()).arg(mouseEv->y());
            qDebug() << qs;

            return true;
        }
    }
    return QWidget::eventFilter(obj, e);
}
区别

事件分发器:重写event
负责将一个对象发出的事件分发给该对象的所有子对象,以及所有被该对象设置为接受该事件类型的父对象。事件分发器可以确保同一时间只有一个子对象或父对象接收到同一个事件

事件过滤器: 重写eventFilter
一个对象拦截并处理其他对象发出的特定类型的事件,当一个对象发出一个事件时,如果该事件符合当前对象正在拦截的事件类型,那么该对象就会调用 eventFilter() 函数进行处理;否则,它会继续将该事件分发给其他对象。

绘图事件:

void QWidget::paintEvent(QPaintEvent *event)

画家叫做QPainter,当画纸为QWidget的派生类对象时, QPainter只能在QWidget的painteEvent事件处理函数中使用

class Widget : public QWidget
{
	Q_OBJECT
	public:
	Widget(QWidget *parent = 0);
	~Widget();
	void paintEvent(QPaintEvent* event);
};

Widget::Widget(QWidget *parent) : QWidget(parent)
{
	QFont font = this>font(); //这个可以用来设置字体的样式
	font.setPixelSize(10);
	this>setFont(font);
}
void Widget::paintEvent(QPaintEvent* event)
{
	qDebug() << "paintEvent" << endl;
	QPainter painter;
	painter.begin(this);
	painter.setPen(Qt::red);
}
2D绘图

QPainter 还能在任何 QPaintDevice上绘制, 继承至QPaintDevice 还有QPixMap、QImage、QPicture

1、使用 QPixMap 进行绘图,根据硬件显示进行优化.
2、使用 QImage 进行绘图,独立与硬件,保存最原始的图片数据.
3、QPicture,可以记录和重现 QPainter 命令

将所有需要绘制的图形,先绘制在 QPixmap QImage QPicture 中, 绘制完成后,再到paintEvent 中一次性绘制出来,可以大大提高 painterEvent 事件处理器的处理效率

在QPicture上绘图:
先把所有的图形元素都画在QPicture对象上面,然后再通过调用update(),去调度paintEvent绘制出QPicture对象的内容,。QPicture对象可以使用save(),来保存成一个文件

class Widget : public QWidget
{
Q_OBJECT
public:
	Widget(QWidget *parent = 0);
	~Widget();
	void paintEvent(QPaintEvent* ev);
public slots:
	void drawPixmap();
private:
	QPixmap* _pixmap;
};


Widget::Widget(QWidget *parent)
: QWidget(parent)
{
	_picture = new QPicture;
	QPushButton* button = new QPushButton("drawPicture", dialog);
	connect(button, SIGNAL(clicked()), this, SLOT(drawPicture()));

}

void Widget::drawPicture()
{
	qDebug() << __FUNCTION__ << endl;
	QPainter painter;
	painter.begin(_picture);
	painter.drawLine(0, 0, 100, 100);
	painter.drawRect(100, 100, 100, 100);
	painter.end();
	this>update(); //画完之后调用update();
}
多线程

Qt中使用QThread来管理线程,一个QThread对象,就是一个线程。QThread对象也有消息循序exec()函数,用来处理自己这个线程的事件。

创建线程:

第一种:

  1. 首先要继承QThread
  2. 重写虚函数QThread::run
class MyThread : public QThread
{
public:
	void run()
	{
		qDebug() << "QThread begin" << endl;
		qDebug() << "child thread" << QThread::currentThreadId() << endl;
		QThread::sleep(5);
		qDebug() << "QThread end" << endl;
		exec();
	}
}

int main(int argc, char** argv)
{
	MyThread thread;
	thread.start();
	QThread::sleep(5);
	thread.quit(); // QThread的quit可以退出线程的消息循环
	thread.wait();
}

第二种:

  1. 继承 QObject
  2. 实例化一个QThread对象
  3. 实现槽函数.
  4. QObject子类对象通过moveToThread将自己放到线程QThread对象中.
  5. 调用QThread对象的start函数启动线程
  6. 必须通过发射信号来让槽函数在线程中执行,发射的信号存放在线程exec()消息队列中。
class MyWork : public QObject
{
	Q_OBJECT
public slots:
	void workSlot()
	{
		qDebug() << "QThread begin" << endl;
		qDebug() << "child thread" << QThread::currentThreadId() << endl;
		QThread::sleep(5);
		qDebug() << "QThread end" << endl;
	}
};
int main(int argc, char** argv)
{
	QApplication app(argc, argv);
	QThread thread;  // 实例化
	MyWork work;
	work.moveToThread(&thread); // work 放入thread 中
	QObject::connect(&thread, SIGNAL(started()), &work, SLOT(workSlot()));
	thread.start();  // 启动线程
	QThread::sleep(6);
	thread.quit(); //调用quit让线程退出消息循环,否则线程一直在exec循环中
	thread.wait(); //调用完quit后紧接着要调用wait来回收线程资源
	return app.exec();
}

QThreadPool:

QThreadPool 类管理一个线程池,可以用于运行多个并发任务。为了使用线程池,需要创建一个 QRunnable 子类并重写 run() 方法。将要在新线程中运行的代码放置在 run() 方法中。

class CustomRunnable : public QRunnable
{
public:
    void run() override
    {
        // Place the code to run in a separate thread here
    }
};

创建自定义 QRunnable 对象后,将其提交给 QThreadPool 的全局实例,以便在可用线程中运行

CustomRunnable *customRunnable = new CustomRunnable();
QThreadPool::globalInstance()->start(customRunnable);

停止线程:

为工作类添加停止标志:在自定义工作类(QObject 子类)中添加一个停止标志(如 atomic),并在需要的时候设置该标志为 true。这将告知工作类需要停止执行

使用信号请求停止工作:在工作类中添加一个信号(如 stopRequested())。当需要停止线程时,发射此信号。将该信号连接到工作类的一个槽(如 stopWork()),以便在接收到停止请求时设置停止标志。

class CustomWorker : public QObject
{
    Q_OBJECT

public:
    explicit CustomWorker(QObject *parent = nullptr);

public slots:
    void doWork();
    void stopWork() { m_stopWork = true; }

signals:
    void stopRequested();

private:
    std::atomic<bool> m_stopWork{false};
};

在主线程中发射停止信号:当需要停止线程时,发射上一步中添加的停止信号。在发射信号之后,请确保等待线程以等待其完成并退出

emit worker.stopRequested();
customThread.wait();

线程的同步

互斥量:

QMutex mutex; //这对象一般定义在多个线程能访问的地方
mutex.lock(); //多个线程调用这个函数去获取锁,没有获取到的线程,将阻塞等待在这个函数上。
mutex.unlock(); //释放锁

bool QMutex::tryLock(int timeout = 0)

互斥锁 QMutexLocker:
从声明处开始(在构造函数中加锁),出了作用域自动解锁(在析构函数解锁)

QMutex mutex;
void func()
{
QMutexLocker locker(_mutex); //QMutexLocker最好实例化成栈对象,释放之前将QMutex解锁。
}

一旦func()执行完成locker自动释放,释放之前先解锁

信号量: QSemaphore

等待条件:

QWaitCondtion m_WaitCondition;
m_WaitConditon.wait(&m_muxtex, time);
m_WaitCondition.wakeAll();

读写锁:

**定时器:**可以隔一段时间发出信号,通过接收这个信号来处理一些定时任务,Qt中的定时器是QTimer继承自QObject

void start(int msec)
/*
*定时msec毫秒后,发射timeout()信号
*/
qt优缺点

优点:
跨平台,几乎支持所有平台
接口简单,文档详细
开发效率高

缺点: Qt 作为一个软件平台,体积庞大, 可用插件少

文件流和数据流

文件流(QTextStream):操作轻量级数据(int,double,QString)数据写入文本件中以后以文本的方式呈现

数据流(QDataStream):通过数据流可以操作各种数据类型,包括对象,存储到文件中数据为二进制。文件流,数据流都可以操作磁盘文件,也可以操作内存数据。通过流对象可以将对象打包到内存,进行数据的传输

智能指针

QSharedPointer:这是Qt提供的一种引用计数智能指针,用于管理动态分配的对象。当引用计数为0时,对象将被自动删除。可以使用它来管理单个对象或对象数组。

QScopedPointer:这是Qt提供的一种基于作用域的智能指针,用于管理动态分配的单个对象。当指针超出作用域时,对象将被自动删除。

QWeakPointer:这是一种弱引用智能指针,它可以在不增加对象引用计数的情况下访问对象。它通常与QSharedPointer一起使用,以避免形成循环引用。

布局管理器

QHBoxLayout是水平布局管理器
QVBoxLayout是垂直布局管理器

窗口类

QMainWindow的布局:
MenuBar是菜单栏,toolbars是工具栏,Dock Widgets是停口窗口,Central Widget是中央窗口,Status Bar是状态栏

模式对话框

屏蔽同一应用程序中其它窗口事件响应的对话框,就叫做模式对话框,如用于确认信息的操作对话框属于模式对话框

QDailog继承至QWidget,模式窗口显示,调用QDailog::exec()即可

QString与QByteArray

Qt的QString类提供了很方便的对字符串操作的接口,QString没有继承任何Qt基类

QString :


QString str = QString("%1 %2 %3").arg(1).arg(2.0).arg("hello");

str.sprintf("%d %s", 10, "hello");

QByteArray: 字节流

QByteArray byte("123abc小马哥");
QString str(byte);
qDebug() << "byte:" << byte << "str:" << str;

输出结果: byte: “123abc\xE5\xB0\x8F\xE9\xA9\xAC\xE5\x93\xA5” str: “123abc小马哥”

文件读写

Qt中使用QFile类来操作文件的输入/输出。继承至QIODevice,QIODevice又继承至QObject。


QFile file("d:/123.txt");
file.open(QIODevice::ReadOnly);
qDebug() << file.read(10) << endl;

QByteArray byte("hellworld");
file.write(byte);

file.close();


流控文件输入输出可以使用QDataStream

流控写入
```cpp
QFile file("d:/123.txt");
file.open(QIODevice::ReadWrite);
QDataStream stream(&file);
int a = 10;
QString str = "helloworld";
stream << a << str;
file.close();

  • 17
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
1、这是一个C/C++新手基础教程,合适完全不懂程序的新手入坑,半生不熟的程序入坑,其他语言的程序想学C++。2、这是一个C++基础系列教程,本季是作为基础教程的第一集,主要为学习C语言,为学好C++打好坚实的基础,C++包含了C部分,要学好C++,学好C语言是必然的趋势。3、C++入行系列教程是一个由浅入深的一个过程,第一第二集主要学习C语言和C++,后面三集主要教大家如何用C++去写封装一些商业级案例,。第三集主要学SOCKET编程,了解熟悉socket编程。第四集主要学用C++自定义数据库,纯C++编写 第五集主要学习数据结构,学习C++的各个标准模板库。大家如有兴趣可以学习已经制作完毕的C++游戏服务器和Mysql数据库教程。4、C++入行系列教程不单单是讲解语法知识,还有另外讲解C/C++内存,以及内存优化,工作中经常用到的经验,以及避免一些坑。5、本季教程主要内容是讲解编程语言,计算机组成原理,开发环境,C语言基础语法。普及一些基础知识,并且结合工作经验讲解一些案列。教程后面会安排一些工作任务给学员。具体内容看教程课题大纲6、不保证每个人都学的会,良心之作,不喜勿买。7、教程会结合本人20年工作经验讲解工作经常用到的地方,以及新手入职面试需要注意的地方。打好基础学好C++走遍天下都不怕,相信自己,你行的。8、每一集都是单独的,需要单独购买噢,亲,请看清楚,C++系列每一集 并不是必须购买的。新手朋友第一第二集是必须学购买的哈,千万记得。切记切记切记

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

八月的雨季997

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值