编辑 | blame | 历史 | 原始文档
结构体

1、概念:结构体是由一系列具有相同类型或不同类型的数据构成的数据集合,它允许将不同类型的数据组合在一起,形成一个新的、更复杂的数据类型。可以把结构体看作是一个 “数据容器”,用于存储多个相关的数据项,这些数据项被称为结构体的成员
2、字节对齐:

(1)、结构体各成员的起始位置相对于结构体变量的起始位置的偏移量应该是这种类型的倍数。

(2)、结构体变量占总字节数应该是结构体各成员最大类型所占字节数的倍数。

2、读入或输出数据时,不能整体的读入或者整体输出,只能逐个结构体成员读入或输出。

3、相同类型的结构体变量能够相互赋值。 	

c++中类和结构体的区别

默认访问权限不同

1.类:默认私有

2.结构体:默认共有

### this指针

**this指针的本质是指针常量,this指针的指向是不可以修改的,但是指向的内容的值是可以修改的**

**实际是:Person * const this;**

类内的非静态成员函数只会诞生一份函数实例,也就是说多个同类型类对象共用一份代码,怎么区分哪些用户调用呢,就是用this指针

1.解决名称冲突

​	形参和成员变量同名时,用this区分

2.返回对象本身用*this

​	使用链式调用返回值必须是对象本身

​	cout就属于链式编程,返回的对象就是ostream,可以无限追加调用

​	返回对象类型必须是类的引用类型,不能返回类类型,因为使用函数返回类的话实际返回的是一个临时副本p',然后p'链式调用返回p''
===========================================
	1、	柔性数组:
	如:
	struct Test
	{
		int type;
		int len;
		char d[0];  // 柔性数组,长度为0的数组,只能位于结构体最后一个成员,所以每个结构体最多只能有一个;柔性数组能表示任意长度的内容,也可以叫做不定长数组,含有柔性数组的结构体,也可以叫做不定长结构体。
	};
	
	由于数组长度为0,所以不占结构体内存,其内存在结构体的末尾连续。
	
	因为柔性数组在结构体末尾连续,所以访问数组时,可以使用结构体对象直接访问,不需要做其他修正操作。
	
	内存模型如下:
	--------------
	|int|int| ...[d数组内容,不占结构体内存]
	--------------
	sizeof(Test) -> 8
	-------------------------------
	
	2、另一种表示结构体内容长度可变的操作:
		指针,成员变量为指针的方式
		
	如:
		struct TestNext
		{
			int type;
			int len;
			char *a;  
			char *b; // 若使用指针来指向不定长的内容,可以有多个指针,且位置可以在结构体任意位置;唯一的缺点是由于指向的是一块内存地址,在不同进程/系统里,内存地址会有变化,当传输给另一端之后,需要做地址重定向
		};
		
		-------------TestNext内存模型如下:32位系统下
		|int|int|char*|char*|...a内存块...|...b内存块...|
		 4    4   4     4     后面跟着2个指针对应的内存块
		-----------------
		TestNext * test = (TestNext*)buffer;
		重定向修正:
			第一个char* 的指向:这个内存块的首地址+结构体大小,及a的地址为  a = test+sizeof(TestNext);
			
			第二个指向是: b = a + strlen(a)+1;
		
		
	-----------------------------------
		内容不定长结构体使用场景:
		
		每次要往结构体里面存放的内容长度是变化的,同时又不想浪费额外的内存空间时,就可使用;
		比如:一些查询结果的存储,会因为查询条件不同,而使得结果的内容长度不同,这个时候使用不定长结构体非常合适。
		
		若内容是整体变化的,那就可以视为一个整体,通过自定义结构体来把整体表示出来,使用结构体类型的柔性数组即可表示。
		
		若内容有多个变化,且不能视为一个整体,每一个变化都可以使用指针来表示,这样的话使用多个指针来表示不定长内容即可。
		
	--------------------------------
		代码验证:
		
		结构体声明:
		struct Test
		{
			int type;
			int len;
			char data[0];  // char 数组,也可以换成自定义类型的数组
		};
		
		使用时,想把这个字符串"mayibase is very good"放到结构体中
		
		char mayi[] = "mayibase is very good";
		int len = sizeof(Test)+sizeof(mayi);
		Test *t = (Test*)malloc(len);
		t->type = 100;
		t->len = len;
		strcpy(t->data,mayi);
		
		// 接着就可以把这个结构体发送给另一端了
		
		-------------------------------------
		
		另一种带多个指针的:
		
		struct TestNext
		{
			int type;
			int len;
			char *a;   // 这些指针,也可以缓存自定义类型的指针
			char *b; 
		};
		// 要求携带\0
		char first[] = "mayibase is very good";
		char second[] = "0701class is very good";
		int total_len = sizeof(TestNext)+strlen(first)+1+strlen(second)+1;
		TestNext *tn = (TestNext*)malloc(total_len); // 一开始开辟足够大小的内存
		tn->type = 100;
		tn->len = total_len;
		memcpy((char*)(tn)+sizeof(TestNext),first,sizeof(first));
		memcpy((char*)(tn)+sizeof(TestNext)+sizeof(first),second,sizeof(second));
		tn->a = first; // 发送端给指针意义不大,在另一端会失效
		tn->b = second;
		
2、## 引用


数组名与指针对应是好事吗?确实是一件好事。将数组地址作为参数可以节省复制整个数组所需的时间和内存。如果数组很大,则使用拷贝的系统开销将非常大;程序不仅需要更多的计算机内存,还需要花费时间来复制大块的数据。另一方面,使用原始数据增加了破坏数据的风险。



​	在C++中,如果将类的对象作为参数传递给函数时,通常会使用引用,这样可以避免对象的拷贝开销,特别是对于大型对象而言。当你将一个类对象通过引用传递给函数时,即使类的成员变量是私有的,函数仍然可以访问和操作这些私有变量。这是因为私有变量的访问控制只是在类外起作用,类内部的成员函数(包括成员函数作为参数的函数)可以自由访问类的私有成员

### 引用和指针和值变量的区别

1.必须在声明引用时将其初始化,不像指针那样先声明再赋值

2.引用初始化时右边不能是表达式,值变量可以

### 函数形参使用引用时创建临时变量的情况

1.老版本C++比如实参和形参引用的值类型不同时,会通过临时变量接收强制转化后的值进行交换

2.我遇到的VS中则是直接通过函数重载形参类型实现了结果

```cpp
void swap(int &a,int &b) {
	int tmp;
	tmp = a;
	a = b;
	b = tmp;
}
int main()
{
	long long a = 3;//形参int 实参long long,函数根据实参自动重载了
	long long b = 5;
	swap(a, b);
	cout << "a = " << a << endl;
	cout << "size of a = " << sizeof(a) << endl;
    return 0;
}
```

### 引用的实质

1. 所以,引用变量在功能上等于一个指针常量,即一旦指向某一个单元就不能在指向别处。

2. 在底层,引用变量由指针按照指针常量的方式实现。

3. 既然引用占内存,为什么我却无法获得引用的地址啊。

   1. 之所以不能获取引用的地址,是因为编译器进行了内部转换。

      ```c++
      int a = 99;
      int &r = a;
      r = 18;
      cout<<&r<next = head;//头插
删除:删除的节点用临时变量保存,先断开连接,再删除
    if (!head) {
        return nullptr;
    }
    ListNode* temp = head;
    head = head->next;
    delete temp;
return head;
双向链表:前指针域、后指针域、以及数据域

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

7.内存区域:
      1,栈区:存放函数的形参和局部变量。
    2,堆区:用malloc函数分配的空间,分配到堆区,程序员必须通过free函数来释放。
         否则内存泄漏。malloc的函数原型:
         void * malloc(int Size);
         作用:在堆上分配Size字节的内存空间。
         返回值:返回分配的内存空间的首地址。
   3,常量文本区:常量文本区的内容不能被改变,否则崩溃。
 4,全局静态区:函数外定义的变量,叫全局变量,分配到全局静态区。
            初始化的全局变量分配到data段,未初始化的全局变量分配到bss段。
5.程序代码区:保存程序每条指令的。
堆栈的区别:1.内存分配方式,栈内存系统自动释放和分配,堆手动分配和释放 2.栈的内存有限,在编译的时候确定,堆相对较大。3.数据共享性,栈定义的变量在当前的函数类使用,堆可以在不同的函数之间共享

Static的作用:1.修饰局部变量时,局部变量被初始化一次2.修饰全局变量和函数时,限制在当前文件的范围内使用 3.修饰类的成员变量,表示属于类,所有对象共享这个变量,在类外初始化 4.修饰类的成员函数时, 该函数属于类本身,不依赖于类的对象。静态成员函数只能访问静态成员变量和其他静态成员函数,不能访问非静态成员。

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

8  malloc free new delete 的区别:
1.Malloc 、free通常在c语言使用,new delete 在c++使用
2.Malloc 只是简单分配一块内存,new和delete会调用析构和构造函数
3.类型安全性:malloc 返回 void* 类型的指针,需要进行显式的类型转换才能赋值给其他类型的指针,new 运算符会根据所分配对象的类型自动返回相应类型的指针,无需进行显式的类型转换。
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

9 类
默认构造: 默认无参和带默认形参值的构造函数。
           MyClass(int x, int y = 2, int z = 3)//形参具有默认值,需要在最右侧

拷贝构造函数:拷贝构造函数的主要作用是在创建对象时,将一个已有对象的数据成员的值复制到新对象中,从而实现对象的复制。ClassName(const ClassName& other) {
        // 拷贝成员变量
}
 调用场景:1. 用一个对象初始化另一个对象 2. 对象作为函数参数按值传递 3. 函数返回对象
  浅拷贝和深拷贝:默认的拷贝构造函数是浅拷贝,只会复制值,遇到指针时会出现悬空指针的问题。 自定义拷贝构造函数,分配新的内存,将原对象指针的内容拷贝到新的内存。需注意同时也要重载赋值运算函数
虚析构函数:虚析构函数是将析构函数声明为 virtual 的析构函数。当基类的析构函数被声明为虚析构函数时,通过基类指针删除派生类对象时,会先调用派生类的析构函数,再调用基类的析构函数。
实例化的三种方式:1.MyClass obj;//栈实例化 2.Circle* circlePtr = new Circle(2.0);//堆上实例化对象 3.Point p2(p1);//拷贝实例化
三大权限:public (类外可调用) protected(它介于 public 和 private 之间,主要用于在继承体系中让派生类能够访问基类的某些成员,同时又对外部代码隐藏这些成员。)       private(只能类使用,派生类无法使用)
隐式转换:1.当类具有一个单参数的构造函数(或者除了第一个参数外其余参数都有默认值)时,编译器可以使用这个构造函数将该参数类型的对象隐式转换为类的对象。

      2. 类型转换运算符引发的隐式转换可以通过定义类型转换运算符,使类的对象能够隐式转换为其他类型。

显示调用:使用 explicit 禁止隐式转换
静态属性:静态属性属于类本身,所有类的对象共享同一个静态属性。
静态属性一般在类的外部进行初始化,格式为 DataType ClassName::staticVariable = initialValue;。
整数类型或枚举类型的常量静态属性可以在类的内部进行初始化。

静态方法:不依赖对象,只能访问静态成员

This 指针 、final、delete:
This指针:在 C++ 中,this 指针是一个隐含于每一个非静态成员函数中的特殊指针。它指向调用该成员函数的对象,通过 this 指针可以访问调用对象的成员变量和成员函数,从而区分成员变量和局部变量。
Final: 一是用于修饰虚函数,表示该虚函数不能在派生类中被重写;二是用于修饰类,表示该类不能被继承。 virtual void func() final {
        std::cout << "Base::func()" << std::endl;
}//修饰虚函数
class Base final {...}//修饰类
Delete: 一是用于释放使用 new 运算符动态分配的内存,避免内存泄漏;二是用于禁用特定的函数,如默认构造函数、拷贝构造函数等。
      // 禁用默认构造函数
MyClass() = delete;
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

10 继承
   继承方式可以是 public(公有继承)、private(私有继承)或 protected(保护继承),默认的继承方式是 private。
   公有继承(public):父类的 public 成员在子类中仍然是 public 的,父类的 protected 成员在子类中仍然是 protected 的,父类的 private 成员在子类中不可直接访问。
私有继承(private):父类的所有成员在子类中都变成 private 的,子类的子类无法再访问这些成员。
保护继承(protected):父类的 public 和 protected 成员在子类中都变成 protected 的,父类的 private 成员在子类中不可直接访问。
实例化过程:
构造函数:创建子类对象时,会先调用父类的构造函数,再调用子类的构造函数。
析构函数:销毁子类对象时,会先调用子类的析构函数,再调用父类的析构函数
多继承:先声明的先继承
虚函数:虚函数的主要作用是实现运行时多态性。用virtual修饰
虚函数表:每个有虚函数的类都一个虚函数表;在创建包含虚函数的类的对象时,对象的内存布局中会包含一个虚表指针,该指针会在对象构造时被初始化为指向该类的虚函数表。
静态多态:函数重载
定义:编译时多态是指在编译阶段就确定了要调用的函数或操作,主要通过函数重载和运算符重载来实现。
优点:编译时多态的执行效率高,因为函数调用在编译阶段就已经确定,没有额外的运行时开销。
缺点:缺乏灵活性,一旦编译完成,调用的函数就固定了。
动态多态:重写
定义:运行时多态是指在运行阶段才确定要调用的函数或操作,主要通过继承和虚函数(在 C++ 中)、抽象类和接口(在 Java 中)来实现。
优点:运行时多态提供了更高的灵活性,允许程序在运行时根据对象的实际类型来动态地选择要调用的函数。
缺点:由于需要在运行时进行函数调用的动态绑定,会带来一定的性能开销。
纯虚函数:virtual void pureVirtualFunction() = 0;
      基类不实现:纯虚函数在基类中只有声明,没有函数体,即基类不会为纯虚函数提供具体的代码实现。
      派生类实现:包含纯虚函数的类是抽象类,不能实例化对象。派生类必须实现基类中的所有纯虚函数,否则派生类也会成为抽象类,同样不能实例化。
抽象类(包括纯虚函数)的两种方式:
1.使用纯虚函数
 2.使用包含未实现接口的类,接口类中只包含纯虚函数,派生类需要实现这些接口。

虚继承:解决菱形继承带来的冗余和二义性问题
       class 派生类名 : virtual 继承方式 基类名 {
    // 派生类的成员
};
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

11.模板
   11.1 函数模板
定义:
template 
类型 函数名(形参列表)
{
    函数体
}
class可以typename关键字代替,作用相同,无区别;
<>括号中的参数称为模板形参;
将函数模板中的模板形参实例化的参数称为模板实参 ;
用模板实参实例化的函数称为模板函数;
模板形参需要调用该模板函数时提供的模板实参来初始化模板形参,一旦编译器确定了实际的模板实参类型就称他实例化了函数模板的一个实例。
不能省略类型参数的场景:template   T add(T a,Tb)
1.从模板函数实参获取的信息有矛盾//add(2.3,3)
2.需要获得特定类型的返回值,不管参数的类型// add(1.3,7.8)
3.虚拟类型参数没有出现在模板函数的形参中//
    
  4.函数模板含有常规形参


11.2类模板
定义:
template 
class 类名
{
        类体
};
模板类
在定义了类模板后,可以根据需要生成相应的模板类。
利用模板类创建对象的格式如下:
Array  array;
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
12、
## STL

### STL的诞生

- 长久以来,软件界一直希望建立一种可重复利用的东西
- C++的面向对象和泛型编程思想,目的就是复用性的提升。
- 大多情况下,数据结构和算法都未能有一套标准,导致被迫从事大量重复工作
- 为了建立数据结构和算法的一套标准,诞生了STL

### STL基本概念

- STL(Standard Template Library,**标准模板库**)
- STL从广义上分为:**容器(container) 算法(algorithm)迭代器(iterator)**
- **容器**和**算法**之间通过**迭代器**进行无缝连接。
- STL几乎所有的代码都采用了**模板类或者模板函数**

###  STL六大组件

STL大体分为六大组件,分别是:**容器、算法、迭代器、仿函数、适配器(配接器)、空间配置器**

**容器:**各种数据结构,如vector、list、deque、set、map等,用来存放数据

**算法:**各种常用的算法,如sort、find、copy、for_each等

**迭代器:**扮演了容器与算法之间的胶合剂。

**仿函数:**行为类似函数,可作为算法的某种策略。

**适配器:**一种用来修饰容器或者仿的数或选代器接口的东西

**空间配置器:**负责空间的配置与管理

### STL中容器、算法、迭代器

**容器:**置物之所也
STL容器就是将运用最广泛的一些数据结构实现出来

常用的数据结构:数组,链表,树,栈,队列,集合, 映射表 等

这些容器分为**序列式容器和关联式容器**两种:

**序列式容器:**强调值的排序,序列式容器中的每个元素均有固定的位置

**关联式容器:**二叉树结构,各元素之间没有严格的物理上的顺序关系

**算法:**问题之解法也
有限的步骤,解决逻辑或数学上的问题,这一门学科我们叫做算法(Algorithms)

1、非可变序列算法:指不直接修改其所操作的容器的算法。

2、可变序列算法:指可以修改其所操作的容器的算法。

3、排序算法:包括对序列进行排序和合并的算法,搜索算法以及有序序列上的集合操作。

4、数值算法:对容器内容进行数值计算



**迭代器:**容器和算法之间粘合剂
提供一种方法,使之能够依序寻访某个容晶所含的各个元素,而又无需暴露该容器的内部表示方式

每个容器都有自己专属的选代器

迭代器使用非常**类似于指针**,初学阶段我们可以先理解选代器为指针

1、正向迭代器:容器类名::iterator 迭代器名;

2、常量正向迭代器:容器类名::const_iterator 迭代器名;

3、反向迭代器:容器类名::reverse_iterator 迭代器名;

4、常量反向迭代器:容器类名::const_reverse_iterator 迭代器名;12.  STL
12.1 string容器
   遍历的三种方式:for循环、范围遍历、迭代器遍历:for(string:: reverse_itreator........)

迭代器{正向{常量:cbegin(),cend()   非常量:begin()  end()
      反向{常量:crbegin(),crend()   非常量:rbegin()  rend()
	s1.erase(it,it+3);  删除三个元素
S1.erase(1,1)//从下标为1开始删,删除n个,默认删完
类型转换:
          Stoi(“18”)
          Stoi(“18.3”)
          To_string(2)
          To_string(23.3)
12.2 list
    使用erase时,注意迭代器失效
    For( auto it=l.begin();it!=l.end();)
{
If(*it==val){
It=l.erase(it);}
Else{
It++;}}

12.3 map容器

增加的方式:  map[100]=”xuac,c232”  //没有就会新建
              Map.insert(makpair(“xuac”,”cdah”);//存在会失败


12.4  vector、stack、queue、deque、set

12.5 算法:冒泡
     排序:快排、红黑树
     查找:二分查找
12.6 迭代器的种类
12.6.1输入迭代器
 输入迭代器是最基本的迭代器类型,它只能单方向向前移动,并且只能读取所指向的元素一次。也就是说,对同一个元素不能进行多次读取操作。
支持的操作包括:解引用(*)、前置和后置自增(++)、相等和不相等比较(==、!=)。
//std::find 函数就可以使用输入迭代器。
//auto it = std::find(vec.begin(), vec.end(), 3);  // vec.begin() 返回的是输入迭代器
12.6.2 输出迭代器
  出迭代器也是单方向向前移动,但它的主要作用是向所指向的位置写入元素,并且同样只能对每个位置进行一次写入操作。
支持的操作有:解引用赋值(*it = value)、前置和后置自增(++)。
常用于单遍写入算法,例如 std::copy 函数在将元素复制到目标容器时就可以使用输出迭代器。
   

   12.6.3 前向迭代器
    前向迭代器继承了输入迭代器和输出迭代器的功能,它可以单方向向前移动,并且可以多次访问同一个元素。
支持输入迭代器和输出迭代器的所有操作,同时允许对同一个元素进行多次读写。
  //适用于需要多次遍历容器中元素的算法,例如某些链表操作。

   12.6.4 双向迭代器
双向迭代器在向前迭代器的基础上增加了向后移动的能力,它可以在容器中双向移动。
除了支持前向迭代器的所有操作外,还支持前置和后置自减(--)操作。
//适合需要双向遍历容器的算法,例如 std::reverse 函数就需要双向迭代器。

  12.6.5 随机访问迭代器
     随机访问迭代器是功能最强大的迭代器类型,它继承了双向迭代器的所有功能,并且支持随机访问元素。
支持的操作包括:算术运算(+、-)、复合赋值(+=、-=)、下标访问([])以及比较大小(<、>、<=、>=)。
//适用于需要快速随机访问容器元素的算法,例如 std::sort 函数通常要求使用随机访问迭代器。
  
自带find函数的容器:
  1.std::set 和 std::multiset
特点:这两个容器基于红黑树实现,存储的元素会自动排序。std::set 中元素唯一,std::multiset 允许元素重复。它们的 find 函数可以在o(logn)  的时间复杂度内查找指定元素。
  2. std::map 和 std::multimap
特点:同样基于红黑树实现,存储键值对。std::map 中键是唯一的,std::multimap 允许键重复。它们的 find 函数根据键来查找元素,时间复杂度也是 o(logn)。
3. std::unordered_set 和 std::unordered_multiset
特点:这两个容器基于哈希表实现,不保证元素的顺序。std::unordered_set 中元素唯一,std::unordered_multiset 允许元素重复。它们的 find 函数平均时间复杂度为o(1) ,最坏情况下为 o(n)。
4. std::unordered_map 和 std::unordered_multimap
特点:基于哈希表存储键值对,不保证元素顺序。std::unordered_map 中键唯一,std::unordered_multimap 允许键重复。find 函数根据键查找元素,平均时间复杂度为 o(1),最坏为o(n) 。

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
13.智能指针
1.可以作为容器的元素,但是shared_ptr当容器的元素时,需要使用移动语义
2.设计一个智能指针

## 智能指针

```
智能指针是一个类,它封装了一个原始的C++指针,以管理所指对象的生命期。解决动态内存自动释放问题。

头文件:#include 
```

![img](./Image-1730682583556-1.png)

### unique_ptr实现方式

```
#include 
#include 
#include 
using namespace std;
class Test {
public:
       Test() {
              cout << "Test()" << endl;
       }
       ~Test() {
              cout << "~Test()" << endl;
       }
       void Output() const {
              cout << "Output()" << endl;
       }
};
template         //类模板
class AutoPtr         //智能指针类
{
public:
       AutoPtr(T* ptr) {            //有参构造
              this->ptr = ptr;
       }
       ~AutoPtr() {          //析构
              if (ptr != nullptr) delete ptr;
       }
       T& operator *() {            //重载指针运算符,使智能指针与普通指针作用一样
              return *ptr;
       }
       T* get() {                   //获取ptr指针,即原始指针
              return ptr;
       }
       T* operator->() {            //重载指向运算符,使智能指针可以访问被管对象的成员函数和成员变量
              return ptr;
       }
private:
       AutoPtr(const AutoPtr&) = delete;          //删除拷贝构造
       AutoPtr& operator=(const AutoPtr&) = delete;              //删除赋值运算符重载
private:
       T* ptr;       //智能指针对象,ptr为被管对象
};
int main()
{                                     //p1智能指针对象new一个Test类对象,Test类对象为被管对象
       AutoPtr p1(new Test);    //智能指针对象访问成员函数用 . ,被管对象访问成员函数用 ->
       p1.get()->Output();
       p1->Output();
       return 0;
}
```

### unique_ptr(独占,一个智能指针指向一个被管对象)

```
#include 
#include 
#include 
using namespace std;
int main()
{
    unique_ptr p1(new int);
    *p1 = 10;
    cout << *p1 << endl;
    unique_ptr s1(new string);
    *s1 = "hello world!";
    cout << *s1 << endl;
    cout << s1->size() << endl;
    return 0;
}
```

### shared_ptr(共享,多个智能指针指向同一个被管对象)

```
shared_ptr允许多个shared_ptr对象指向同一个被管理对象, shared_ptr是通过引用计数来管理对象的,拷贝一个shared_ptr,计数器都会递增,离开作用域时,计数器会递减,如果计数器变为0,它就会自动释放自己所管理的对象。

#include 
#include 
#include 
using namespace std;
int main()
{
shared_ptr p1(new int);
shared_ptr p2(p1);
*p1 = 10;
cout << *p1 << endl;
cout << *p2 << endl;
cout << p1.use_count() << endl;
return 0;
}
------------------------------------
class Test {
public:
       Test() {
              cout << "Test()" << endl;
       }
       ~Test() {
              cout << "~Test()" << endl;
       }
       void Output() const {
              cout << "Output()" << endl;
       }
};
int main() {
       shared_ptr p1(new Test);
       shared_ptr p2 = p1;
       shared_ptr p3;
       p3 = p1;
       cout << p1.use_count() << endl;
       return 0;
}
```

### weak_ptr

```
weak_ptr不能独立使用,必配合shared_ptr使用, weak_ptr是为了解决shared_ptr的引用成环问题。
weak_ptr只进行浅拷贝,引用计数不会发生改变。

#include 
#include 
using namespace std;
class B;
class A
{
public:
A() { cout << "A::A() called" << endl; }
~A() { cout << "A::~A() called" << endl; }
shared_ptr p;                            //解决引用成环问题,将shared_ptr换成weak_ptr
};
--------------------------------------
class B
{
public:
B() { cout << "B::B() called" << endl; }
~B() { cout << "B::~B() called" << endl; }
shared_ptr p;                          //解决引用成环问题,将shared_ptr换成weak_ptr
};
--------------------------------------------
int main()
{
shared_ptr pA(new A);
shared_ptr pB(new B);
//下面两句导致了A与B的循环引用,结果就是A和B对象都不会析构
pA->p = pB;
pB->p = pA;
cout << "pA use_count: " << pA.use_count() << endl;
cout << "pB use_count: " << pB.use_count() << endl;
return 0;
}
```

```
解决方法:
#include 
#include 
using namespace std;
class B;
class A
{
public:
    A() { cout << "A::A() called" << endl; }
    ~A() { cout << "A::~A() called" << endl; }
    weak_ptr p;
};
------------------------------
class B
{
public:
    B() { cout << "B::B() called" << endl; }
    ~B() { cout << "B::~B() called" << endl; }
    weak_ptr p; //!
};
------------------------------
int main()
{
    shared_ptr pA(new A);
    shared_ptr pB(new B);     pA->p = pB;
    pB->p = pA;
    cout << "pA use_count: " << pA.use_count() << endl;
    cout << "pB use_count: " << pB.use_count() << endl;
    return 0;
}
```

```
常规使用:
#include 
#include 
using namespace std;
int main()
{
    shared_ptr p1(new int);
    weak_ptr w1(p1);
    *p1 = 10;
    shared_ptr p2 = w1.lock();
    if (p2) {
        cout << *p2 << endl;
    }
    return 0;
}

```

### 实现一个智能指针

```
#include 
#include 
using namespace std;
class Test{
public:
Test() {
	cout << "Test()" << endl;
}
~Test() {
cout << "~Test()" << endl;
}
void Output() const {
cout << "Output()" << endl;
}
};
-----------------------------------
template 
class AutoPtr
{
public:
AutoPtr(T* ptr) {
	this->ptr = ptr;
}
~AutoPtr() {
	if (ptr != nullptr) delete ptr;
}
T& operator *() {
	return *ptr;
}
T* get() {
	return ptr;
}
T* operator->() {
	return ptr;
}
------------------------------
private:
AutoPtr(const AutoPtr&) = delete;
AutoPtr& operator=(const AutoPtr&) = delete;
private:
T* ptr;
};
-------------------------------------------
int main()
{
AutoPtr p1(new Test);
p1->Output();
return 0;
}
```

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
14、
### 1. `try...catch`语句
`try...catch` 是C++中用于捕获和处理异常的基本机制。程序中可能抛出异常的代码块放在 `try` 块中,而用于处理这些异常的代码则放在对应的 `catch` 块中。

#### 语法结构
```cpp
try {
    // 可能抛出异常的代码
    // ...
    throw exception_object; // 抛出异常对象
}
catch (ExceptionType1 e1) {
    // 处理 ExceptionType1 类型的异常
}
catch (ExceptionType2 e2) {
    // 处理 ExceptionType2 类型的异常
}
// 可以有更多的 catch 块
catch (...) {
    // 处理其他未明确捕获的异常
}
```

#### 示例代码
```cpp
#include 

double divide(double a, double b) {
    if (b == 0) {
        throw std::runtime_error("除数不能为零");
    }
    return a / b;
}

int main() {
    double num1 = 10.0, num2 = 0.0;
    try {
        double result = divide(num1, num2);
        std::cout << "结果: " << result << std::endl;
    }
    catch (const std::runtime_error& e) {
        std::cerr << "异常: " << e.what() << std::endl;
    }
    return 0;
}
```
在上述代码中,`divide` 函数在除数为零时抛出 `std::runtime_error` 异常,`main` 函数中的 `try` 块调用 `divide` 函数,`catch` 块捕获并处理该异常。

### 2. 异常检测工具
#### Valgrind
- **功能**:Valgrind是一个强大的内存调试和性能分析工具套件,其中的 `Memcheck` 工具可以检测程序中的内存错误,包括内存泄漏、使用未初始化的内存、越界访问等。同时,它也能帮助发现一些可能导致异常的内存问题。
- **使用示例**:假设编译生成的可执行文件名为 `my_program`,在终端中运行以下命令:
```bash
valgrind --leak-check=full ./my_program
```
这会让 Valgrind 对 `my_program` 进行全面的内存检查,并输出详细的内存错误和泄漏信息。

#### AddressSanitizer(ASan)
- **功能**:AddressSanitizer 是一个快速的内存错误检测工具,集成在 GCC 和 Clang 编译器中。它可以检测堆、栈和全局内存的越界访问、使用已释放的内存等问题,能有效帮助发现可能引发异常的内存错误。
- **使用示例**:编译时添加相应的编译选项,以 GCC 为例:
```bash
g++ -fsanitize=address -g your_program.cpp -o your_program
```
然后运行生成的可执行文件,若存在内存错误,ASan 会输出详细的错误信息。

### 3. 内存泄漏检测工具
#### Valgrind(Memcheck)
- **原理**:Memcheck 会跟踪程序中所有的内存分配和释放操作,在程序结束时检查是否存在已分配但未释放的内存块。
- **示例输出分析**:当运行 `valgrind --leak-check=full ./my_program` 后,如果存在内存泄漏,Valgrind 会输出类似如下信息:
```plaintext
==1234== LEAK SUMMARY:
==1234==    definitely lost: 4 bytes in 1 blocks
==1234==    indirectly lost: 0 bytes in 0 blocks
==1234==      possibly lost: 0 bytes in 0 blocks
==1234==    still reachable: 0 bytes in 0 blocks
==1234==         suppressed: 0 bytes in 0 blocks
```
这表明程序中存在 4 字节的确定内存泄漏。

#### LeakSanitizer(LSan)
- **功能**:LeakSanitizer 是一个内存泄漏检测工具,同样集成在 GCC 和 Clang 编译器中。它能在程序结束时自动检测并报告内存泄漏情况。
- **使用示例**:编译时添加相应的编译选项,以 Clang 为例:
```bash
clang++ -fsanitize=leak -g your_program.cpp -o your_program
```
运行程序后,若存在内存泄漏,LSan 会输出详细的泄漏信息。

### 4. 安全函数
#### `strcpy_s` 和 `strncpy_s`
- **作用**:在C++中,传统的 `strcpy` 和 `strncpy` 函数存在缓冲区溢出的风险,而 `strcpy_s` 和 `strncpy_s` 是更安全的替代函数。`strcpy_s` 用于将一个字符串复制到另一个字符串缓冲区,同时会检查目标缓冲区的大小,避免溢出;`strncpy_s` 则在复制指定长度的字符时也会进行类似的检查。
- **示例代码**:
```cpp
#include 
#include 

int main() {
    char dest[10];
    const char* src = "Hello";
    errno_t result = strcpy_s(dest, sizeof(dest), src);
    if (result == 0) {
        std::cout << "复制成功: " << dest << std::endl;
    } else {
        std::cerr << "复制失败" << std::endl;
    }
    return 0;
}
```

#### `sprintf_s`
- **作用**:`sprintf` 函数在格式化字符串并输出到缓冲区时可能会导致缓冲区溢出,`sprintf_s` 则是其安全版本。它会检查目标缓冲区的大小,确保不会发生溢出。
- **示例代码**:
```cpp
#include 
#include 

int main() {
    char buffer[20];
    int num = 123;
    errno_t result = sprintf_s(buffer, sizeof(buffer), "数字是: %d", num);
    if (result > 0) {
        std::cout << "格式化成功: " << buffer << std::endl;
    } else {
        std::cerr << "格式化失败" << std::endl;
    }
    return 0;
}
```
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
15、
### 单例模式
单例模式确保一个类只有一个实例,并提供一个全局访问点来获取这个实例。

#### 饿汉式单例模式
- **特点**:在程序启动时就创建单例实例,线程安全,无需考虑多线程并发问题,但可能会导致资源浪费,因为无论是否使用该实例,它都会被创建。
- **示例代码**:
```cpp
#include 

class SingletonHungry {
private:
    // 构造函数私有化,防止外部创建对象
    SingletonHungry() {}
    // 静态成员变量,在类加载时就创建实例
    static SingletonHungry instance;

public:
    // 提供全局访问点
    static SingletonHungry& getInstance() {
        return instance;
    }

    void doSomething() {
        std::cout << "饿汉式单例模式执行操作" << std::endl;
    }
};

// 初始化静态成员变量
SingletonHungry SingletonHungry::instance;

int main() {
    SingletonHungry& singleton = SingletonHungry::getInstance();
    singleton.doSomething();
    return 0;
}
```

#### 懒汉式单例模式
- **特点**:在第一次使用时才创建单例实例,避免了资源浪费,但在多线程环境下需要考虑线程安全问题。
- **示例代码(非线程安全)**:
```cpp
#include 

class SingletonLazy {
private:
    // 构造函数私有化
    SingletonLazy() {}
    // 静态成员指针,初始化为 nullptr
    static SingletonLazy* instance;

public:
    // 提供全局访问点
    static SingletonLazy* getInstance() {
        if (instance == nullptr) {
            instance = new SingletonLazy();
        }
        return instance;
    }

    void doSomething() {
        std::cout << "懒汉式单例模式执行操作" << std::endl;
    }
};

// 初始化静态成员指针
SingletonLazy* SingletonLazy::instance = nullptr;

int main() {
    SingletonLazy* singleton = SingletonLazy::getInstance();
    singleton->doSomething();
    return 0;
}
```
- **示例代码(线程安全,使用双重检查锁定)**:
```cpp
#include 
#include 

class SingletonLazyThreadSafe {
private:
    // 构造函数私有化
    SingletonLazyThreadSafe() {}
    // 静态成员指针,初始化为 nullptr
    static SingletonLazyThreadSafe* instance;
    // 互斥锁,用于线程同步
    static std::mutex mtx;

public:
    // 提供全局访问点
    static SingletonLazyThreadSafe* getInstance() {
        if (instance == nullptr) {
            std::lock_guard lock(mtx);
            if (instance == nullptr) {
                instance = new SingletonLazyThreadSafe();
            }
        }
        return instance;
    }

    void doSomething() {
        std::cout << "线程安全的懒汉式单例模式执行操作" << std::endl;
    }
};

// 初始化静态成员指针和互斥锁
SingletonLazyThreadSafe* SingletonLazyThreadSafe::instance = nullptr;
std::mutex SingletonLazyThreadSafe::mtx;

int main() {
    SingletonLazyThreadSafe* singleton = SingletonLazyThreadSafe::getInstance();
    singleton->doSomething();
    return 0;
}
```

### 工厂模式

#### 简单工厂模式
- **特点**:定义一个工厂类,根据传入的参数决定创建哪种具体产品类的实例。它将对象的创建和使用分离,但不符合开闭原则(对扩展开放,对修改关闭),因为添加新的产品类时需要修改工厂类的代码。
- **示例代码**:
```cpp
#include 

// 抽象产品类
class Product {
public:
    virtual void use() = 0;
    virtual ~Product() {}
};

// 具体产品类 A
class ConcreteProductA : public Product {
public:
    void use() override {
        std::cout << "使用产品 A" << std::endl;
    }
};

// 具体产品类 B
class ConcreteProductB : public Product {
public:
    void use() override {
        std::cout << "使用产品 B" << std::endl;
    }
};

// 简单工厂类
class SimpleFactory {
public:
    static Product* createProduct(const std::string& type) {
        if (type == "A") {
            return new ConcreteProductA();
        } else if (type == "B") {
            return new ConcreteProductB();
        }
        return nullptr;
    }
};

int main() {
    Product* productA = SimpleFactory::createProduct("A");
    if (productA) {
        productA->use();
        delete productA;
    }

    Product* productB = SimpleFactory::createProduct("B");
    if (productB) {
        productB->use();
        delete productB;
    }

    return 0;
}
```

#### 工厂方法模式
- **特点**:定义一个创建对象的抽象方法,让子类决定实例化哪个具体产品类。它符合开闭原则,添加新的产品类时只需创建对应的具体工厂子类,而无需修改现有代码。
- **示例代码**:
```cpp
#include 

// 抽象产品类
class Product {
public:
    virtual void use() = 0;
    virtual ~Product() {}
};

// 具体产品类 A
class ConcreteProductA : public Product {
public:
    void use() override {
        std::cout << "使用产品 A" << std::endl;
    }
};

// 具体产品类 B
class ConcreteProductB : public Product {
public:
    void use() override {
        std::cout << "使用产品 B" << std::endl;
    }
};

// 抽象工厂类
class Factory {
public:
    virtual Product* createProduct() = 0;
    virtual ~Factory() {}
};

// 具体工厂类 A
class ConcreteFactoryA : public Factory {
public:
    Product* createProduct() override {
        return new ConcreteProductA();
    }
};

// 具体工厂类 B
class ConcreteFactoryB : public Factory {
public:
    Product* createProduct() override {
        return new ConcreteProductB();
    }
};

int main() {
    Factory* factoryA = new ConcreteFactoryA();
    Product* productA = factoryA->createProduct();
    if (productA) {
        productA->use();
        delete productA;
    }
    delete factoryA;

    Factory* factoryB = new ConcreteFactoryB();
    Product* productB = factoryB->createProduct();
    if (productB) {
        productB->use();
        delete productB;
    }
    delete factoryB;

    return 0;
}
```

#### 抽象工厂模式
- **特点**:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。它可以创建多个不同类型的产品,适用于创建产品族的场景。
- **示例代码**:
```cpp
#include 

// 抽象产品类 A
class AbstractProductA {
public:
    virtual void useA() = 0;
    virtual ~AbstractProductA() {}
};

// 具体产品类 A1
class ConcreteProductA1 : public AbstractProductA {
public:
    void useA() override {
        std::cout << "使用产品 A1" << std::endl;
    }
};

// 具体产品类 A2
class ConcreteProductA2 : public AbstractProductA {
public:
    void useA() override {
        std::cout << "使用产品 A2" << std::endl;
    }
};

// 抽象产品类 B
class AbstractProductB {
public:
    virtual void useB() = 0;
    virtual ~AbstractProductB() {}
};

// 具体产品类 B1
class ConcreteProductB1 : public AbstractProductB {
public:
    void useB() override {
        std::cout << "使用产品 B1" << std::endl;
    }
};

// 具体产品类 B2
class ConcreteProductB2 : public AbstractProductB {
public:
    void useB() override {
        std::cout << "使用产品 B2" << std::endl;
    }
};

// 抽象工厂类
class AbstractFactory {
public:
    virtual AbstractProductA* createProductA() = 0;
    virtual AbstractProductB* createProductB() = 0;
    virtual ~AbstractFactory() {}
};

// 具体工厂类 1
class ConcreteFactory1 : public AbstractFactory {
public:
    AbstractProductA* createProductA() override {
        return new ConcreteProductA1();
    }
    AbstractProductB* createProductB() override {
        return new ConcreteProductB1();
    }
};

// 具体工厂类 2
class ConcreteFactory2 : public AbstractFactory {
public:
    AbstractProductA* createProductA() override {
        return new ConcreteProductA2();
    }
    AbstractProductB* createProductB() override {
        return new ConcreteProductB2();
    }
};

int main() {
    AbstractFactory* factory1 = new ConcreteFactory1();
    AbstractProductA* productA1 = factory1->createProductA();
    AbstractProductB* productB1 = factory1->createProductB();
    if (productA1) {
        productA1->useA();
        delete productA1;
    }
    if (productB1) {
        productB1->useB();
        delete productB1;
    }
    delete factory1;

    AbstractFactory* factory2 = new ConcreteFactory2();
    AbstractProductA* productA2 = factory2->createProductA();
    AbstractProductB* productB2 = factory2->createProductB();
    if (productA2) {
        productA2->useA();
        delete productA2;
    }
    if (productB2) {
        productB2->useB();
        delete productB2;
    }
    delete factory2;

    return 0;
}
```
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
16、
### Boost多线程特性

#### 概述
Boost.Thread库为C++提供了跨平台的多线程支持,它封装了底层操作系统的线程机制,使得在不同操作系统上编写多线程程序变得更加简单和统一。

#### 示例代码
以下示例展示了如何使用Boost.Thread创建和管理线程:
```cpp
#include 
#include 

// 线程函数
void threadFunction() {
    for (int i = 0; i < 5; ++i) {
        std::cout << "子线程: " << i << std::endl;
        boost::this_thread::sleep(boost::posix_time::seconds(1));
    }
}

int main() {
    // 创建线程对象
    boost::thread t(threadFunction);

    // 主线程执行的代码
    for (int i = 0; i < 3; ++i) {
        std::cout << "主线程: " << i << std::endl;
        boost::this_thread::sleep(boost::posix_time::seconds(1));
    }

    // 等待子线程结束
    t.join();

    std::cout << "主线程结束" << std::endl;
    return 0;
}
```

#### 代码解释
1. **线程函数**:`threadFunction` 是一个普通的函数,它将在新线程中执行。在这个函数中,我们使用 `boost::this_thread::sleep` 函数让线程休眠1秒。
2. **创建线程**:通过 `boost::thread t(threadFunction);` 创建一个新线程,并将 `threadFunction` 作为线程的入口点。
3. **主线程执行**:主线程继续执行自己的代码,同样使用 `boost::this_thread::sleep` 函数让线程休眠1秒。
4. **等待线程结束**:使用 `t.join()` 函数等待子线程执行完毕,确保主线程在子线程结束后才退出。

### Boost Socket特性

#### 概述
Boost.Asio库是Boost中用于网络编程的库,提供了异步I/O操作的支持,包括Socket编程。它可以用于创建TCP、UDP等各种类型的网络应用程序。

#### TCP服务器示例代码
```cpp
#include 
#include 

using boost::asio::ip::tcp;

// 处理客户端连接的函数
void handleConnection(tcp::socket socket) {
    try {
        boost::asio::streambuf buffer;
        boost::asio::read_until(socket, buffer, '\n');

        std::istream is(&buffer);
        std::string line;
        std::getline(is, line);

        std::cout << "接收到客户端消息: " << line << std::endl;

        std::string response = "服务器已收到消息: " + line + "\n";
        boost::asio::write(socket, boost::asio::buffer(response));
    }
    catch (std::exception& e) {
        std::cerr << "异常: " << e.what() << std::endl;
    }
}

int main() {
    try {
        boost::asio::io_context io_context;
        tcp::acceptor acceptor(io_context, tcp::endpoint(tcp::v4(), 12345));

        while (true) {
            tcp::socket socket(io_context);
            acceptor.accept(socket);
            handleConnection(std::move(socket));
        }
    }
    catch (std::exception& e) {
        std::cerr << "异常: " << e.what() << std::endl;
    }

    return 0;
}
```

#### TCP客户端示例代码
```cpp
#include 
#include 

using boost::asio::ip::tcp;

int main() {
    try {
        boost::asio::io_context io_context;
        tcp::socket socket(io_context);
        tcp::resolver resolver(io_context);
        boost::asio::connect(socket, resolver.resolve("127.0.0.1", "12345"));

        std::string message = "Hello, Server!\n";
        boost::asio::write(socket, boost::asio::buffer(message));

        boost::asio::streambuf buffer;
        boost::asio::read_until(socket, buffer, '\n');

        std::istream is(&buffer);
        std::string line;
        std::getline(is, line);

        std::cout << "收到服务器响应: " << line << std::endl;
    }
    catch (std::exception& e) {
        std::cerr << "异常: " << e.what() << std::endl;
    }

    return 0;
}
```

#### 代码解释

##### 服务器端
1. **创建 `io_context`**:`boost::asio::io_context` 是Boost.Asio库的核心对象,用于管理所有的异步操作。
2. **创建 `acceptor`**:`tcp::acceptor` 用于监听指定端口的连接请求。
3. **接受连接**:使用 `acceptor.accept(socket)` 接受客户端的连接请求,并将连接的套接字传递给 `handleConnection` 函数处理。
4. **处理连接**:`handleConnection` 函数负责读取客户端发送的消息,并向客户端发送响应。

##### 客户端
1. **创建 `io_context` 和 `socket`**:同样需要创建 `io_context` 和 `tcp::socket` 对象。
2. **解析服务器地址**:使用 `tcp::resolver` 解析服务器的地址和端口。
3. **连接服务器**:使用 `boost::asio::connect` 函数连接到服务器。
4. **发送和接收消息**:使用 `boost::asio::write` 函数向服务器发送消息,使用 `boost::asio::read_until` 函数读取服务器的响应。 

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
17、
	线程间的通信
		1、全局变量
		2、信号槽
		3、消息队列
		4、管道[匿名管道、命名管道]
		
	进程间的通信
		前提条件:
			进程间的内存是相互独立的,每个进程都是独立的内存空间
		通信方式:
			1、共享内存--记得上锁排队处理--最快
			2、消息队列
			3、管道 
			4、socket--tcp、udp协议--跨平台、跨电脑
			5、剪切板
			6、D-BUS:是一种信号槽的扩展
	
	Qt:
		类:
		API:start("名字或完整路径"),还可以带启动参数来启动
	
	
	死锁问题
		多线程--访问同一个资源[临界资源]--争夺资源、脏数据--上锁:互斥锁、信号量、条件变量等--互斥的情况下--可能产生死锁
		
		死锁现象:
			1、一把互斥锁,连续2次lock或者异常结束,都会导致死锁
			2、两把互斥锁,两条线程,各自锁上之后,还想拿多方的锁,就会导致相互等待的死锁;
		
		
		死锁条件:4个必要条件
			1、互斥条件
			2、请求保持
			3、不可剥夺
			4、循环等待
		必须要满足4个条件才会导致死锁。
		
		死锁解决方案:
			破坏形成死锁的任意条件即可,如:撤销线程、剥夺资源、超时等待等
			
		预防死锁:
			1、银行家算法--做资源的合理分配
			2、异常死锁的预防:使用守卫锁或者唯一锁,来自动上锁、解锁。
			
	
	任务:
		1、进程间的通信方式:共享内存的示例,把示例代码完善并测试通过
			提示:需要开2个进程来测试
			
		2、设计模式的掌握:单例模式、工厂模式、观察者模式的原理及实现代码;
		
		


=========================================================================================================================================================================================================================
18、			心跳检测:
	作用:为了对长连接进行保活,并且能去除无用连接
		  还能做到断线重连--针对客户端

	tcp本身是有一个keep-alive保活状态,默认是2个小时
	由于默认的时间太久,满足不了我们的业务需求,所以要重设一个心跳检测。
	
	--------------------------
	要实现心跳检测,需要在服务端和客户端进行相应的处理:
	要在30秒内进行保活检测;
	服务端:
		容器:map<套接字,阈值>   map   // 阈值 -- 临界值
		然后使用线程定时轮询容器进行相应的处理;
		对容器进行增删改查的操作:
		增:accept 成功之后
		删:阈值为0时,先关闭套接字,再删除键值对
		改:遍历时的递减;收到正常包时重置阈值
		查:定时遍历,需要单独起一条线程来遍历map
		
		
	
	客户端:
		起一条线程来定时发心跳包;
		设定一个阈值来判断服务器是否回响应,若发现服务器一直不响应,需要断线重连;
	============================================================================================================================
	dyc-------------------------------------
	--------------------------------------------
	en  
	时的 
	--------------
	通知所有在线的客户端
	前提是客户端在线
	--------------------
	PC机器上
	------------------时的  连的时长连接
	【描述界面效果】
	自己做的
	-------------------
	一直在维护更新 有商用的
	----------------------
	【刚刚听错了】
	-----------------------
	【按剧本来】
	-----------------------
	没啥问题
	-----------------
	【吹起来】
	qt 信号槽
	mvd
	socket 
	------------------
	qt 多线程 
	自动 默认 会自动切换连接,单线 -- 直接,多线程--队列 
	直接  单线程 
	队列  多线程 异步
	阻塞队列 多线程 阻塞 
	唯一  防止重复连接
		 就是防止多次连接的时候,只生效一个连接
		 如果使用其他的方式,连接几次就会生效几次
	---------------------
	事件循环
	
	一般先到父部件的事件过滤器
	再到子部件的 event 函数 
	再到子部件的 具体的事件处理函数
	 如果对事件进行忽略,再返回到父部件的具体事件处理
	--------------------
    mvd 模型视图
	
	一般数据量大的时候 使用 
	特别是使用二维表格来展示数据的时候使用
	说具体字母
	M 模型 存储数据
	V 视图 显示数据
	D 代理 提供编辑功能
	mv关联使用 setModel()函数 
	设置一些项的值  setItem()
	----------------------------------
	tcp协议
	类:QTcpSocket 客户端类
		APi: connectToHost 连接服务器
		      判断连接是否成功 
			  发送 write 函数 
			  收 read 或者 readAll
			  需要有个接受信号 readyRead
			  这个信号和自定义槽关联,槽里面收包
			  ---------------------
		【老大 深呼吸 慢慢控制你的脑子 你现在脑子空白了】
	-----------------------------
	只要是开发相关的 都行 
	C++ Qt 都行 
	--------------------
	我擅长 C++ qt 
	----------------------------
	网络 界面 都行 
	嗯  擅长 QWidget 
	QML用得少一些 【好好念字母 别着急】
	--------------------
	啥问题 
	Git  命令行
	--------------------------说啊
	代码为主  有时候也用qt 设计师来辅助
	
	有时候一些底层大的底板  界面相对简单的
	使用设计师工具来处理
	复杂的使用纯代码来实现
	------------------------------
	就是我来负责的---【设计师工具 就是Qt Desinger】
	-------------------
----------------------------------------------------------------------------------------------------------------------------------------------------------
19、
### 文件流操作
C++ 提供了 `fstream` 库来进行文件的读写操作,它包含了三个主要的类:`ifstream`(用于读取文件)、`ofstream`(用于写入文件)和 `fstream`(可同时进行读写操作)。

#### 示例代码:文件读写
```cpp
#include 
#include 
#include 

int main() {
    // 写入文件
    std::ofstream outFile("example.txt");
    if (outFile.is_open()) {
        outFile << "Hello, File!" << std::endl;
        outFile << "This is a test." << std::endl;
        outFile.close();
    } else {
        std::cerr << "无法打开文件进行写入。" << std::endl;
        return 1;
    }

    // 读取文件
    std::ifstream inFile("example.txt");
    if (inFile.is_open()) {
        std::string line;
        while (std::getline(inFile, line)) {
            std::cout << line << std::endl;
        }
        inFile.close();
    } else {
        std::cerr << "无法打开文件进行读取。" << std::endl;
        return 1;
    }

    return 0;
}
```

#### 代码解释
- `std::ofstream outFile("example.txt");`:创建一个输出文件流对象 `outFile`,并打开 `example.txt` 文件用于写入。
- `outFile << "Hello, File!" << std::endl;`:向文件中写入数据。
- `std::ifstream inFile("example.txt");`:创建一个输入文件流对象 `inFile`,并打开 `example.txt` 文件用于读取。
- `std::getline(inFile, line)`:逐行读取文件内容。

### 日志操作
日志操作是记录程序运行过程中重要信息的一种方式,有助于调试和监控程序。可以通过自定义类来实现简单的日志功能。

#### 示例代码:简单日志类
```cpp
#include 
#include 
#include 
#include 

class Logger {
public:
    Logger(const std::string& filename) : logFile(filename, std::ios::app) {
        if (!logFile.is_open()) {
            std::cerr << "无法打开日志文件。" << std::endl;
        }
    }

    ~Logger() {
        if (logFile.is_open()) {
            logFile.close();
        }
    }

    void log(const std::string& message) {
        if (logFile.is_open()) {
            std::time_t now = std::time(nullptr);
            char* dt = std::ctime(&now);
            logFile << "[" << dt << "] " << message << std::endl;
        }
    }

private:
    std::ofstream logFile;
};

int main() {
    Logger logger("app.log");
    logger.log("程序启动");
    logger.log("执行某个操作");
    logger.log("程序结束");
    return 0;
}
```

#### 代码解释
- `Logger(const std::string& filename)`:构造函数,打开指定的日志文件并以追加模式写入。
- `void log(const std::string& message)`:记录日志信息,同时添加当前时间戳。
- `~Logger()`:析构函数,关闭日志文件。

### XML 文件操作
可以使用第三方库如 `pugixml` 来进行 XML 文件的读写操作。

#### 示例代码:使用 `pugixml` 读写 XML 文件
```cpp
#include 
#include "pugixml.hpp"

int main() {
    // 创建 XML 文档
    pugi::xml_document doc;
    pugi::xml_node root = doc.append_child("root");
    pugi::xml_node child = root.append_child("child");
    child.append_attribute("name") = "example";
    child.text() = "Hello, XML!";

    // 保存 XML 文档到文件
    if (doc.save_file("example.xml")) {
        std::cout << "XML 文件保存成功。" << std::endl;
    } else {
        std::cerr << "XML 文件保存失败。" << std::endl;
        return 1;
    }

    // 读取 XML 文件
    pugi::xml_document readDoc;
    if (readDoc.load_file("example.xml")) {
        pugi::xml_node readRoot = readDoc.child("root");
        pugi::xml_node readChild = readRoot.child("child");
        std::cout << "属性值: " << readChild.attribute("name").value() << std::endl;
        std::cout << "文本内容: " << readChild.text().get() << std::endl;
    } else {
        std::cerr << "XML 文件读取失败。" << std::endl;
        return 1;
    }

    return 0;
}
```

#### 代码解释
- `pugi::xml_document doc;`:创建一个 XML 文档对象。
- `pugi::xml_node root = doc.append_child("root");`:添加根节点。
- `doc.save_file("example.xml");`:将 XML 文档保存到文件。
- `readDoc.load_file("example.xml");`:从文件中加载 XML 文档。

### JSON 数据处理
可以使用第三方库如 `nlohmann/json` 来进行 JSON 数据的处理。

#### 示例代码:使用 `nlohmann/json` 处理 JSON 数据
```cpp
#include 
#include 
#include "json.hpp"

using json = nlohmann::json;

int main() {
    // 创建 JSON 对象
    json j;
    j["name"] = "John";
    j["age"] = 30;
    j["hobbies"] = {"reading", "swimming", "running"};

    // 将 JSON 对象写入文件
    std::ofstream outJsonFile("example.json");
    if (outJsonFile.is_open()) {
        outJsonFile << j.dump(4);
        outJsonFile.close();
    } else {
        std::cerr << "无法打开文件进行 JSON 写入。" << std::endl;
        return 1;
    }

    // 从文件中读取 JSON 数据
    std::ifstream inJsonFile("example.json");
    if (inJsonFile.is_open()) {
        json readJ;
        inJsonFile >> readJ;
        std::cout << "姓名: " << readJ["name"] << std::endl;
        std::cout << "年龄: " << readJ["age"] << std::endl;
        std::cout << "爱好: ";
        for (const auto& hobby : readJ["hobbies"]) {
            std::cout << hobby << " ";
        }
        std::cout << std::endl;
        inJsonFile.close();
    } else {
        std::cerr << "无法打开文件进行 JSON 读取。" << std::endl;
        return 1;
    }

    return 0;
}
```

#### 代码解释
- `json j;`:创建一个 JSON 对象。
- `j["name"] = "John";`:向 JSON 对象中添加键值对。
- `outJsonFile << j.dump(4);`:将 JSON 对象以缩进 4 个空格的格式写入文件。
- `inJsonFile >> readJ;`:从文件中读取 JSON 数据到 `readJ` 对象中。 		
----------------------------------------------------------------------------------------------------------------------------------------------------------		
20、
### 学生类的单元测试

```cpp
#include 
#include 
#include "gtest/gtest.h"

// 学生类定义
class Student {
public:
    Student(const std::string& name, char gender, int age)
        : name(name), gender(gender), age(age) {}

    std::string getName() const {
        return name;
    }

    char getGender() const {
        return gender;
    }

    int getAge() const {
        return age;
    }

private:
    std::string name;
    char gender;
    int age;
};

// 测试用例:测试 Student 类的构造函数和成员访问函数
TEST(StudentTest, ConstructorAndGetters) {
    Student student("Alice", 'F', 18);
    EXPECT_EQ(student.getName(), "Alice");
    EXPECT_EQ(student.getGender(), 'F');
    EXPECT_EQ(student.getAge(), 18);
}
```

### 联系人类的单元测试

```cpp
#include 
#include 
#include "gtest/gtest.h"

// 联系人类定义
class Contact {
public:
    Contact(const std::string& name, char gender, const std::string& phoneNumber)
        : name(name), gender(gender), phoneNumber(phoneNumber) {}

    std::string getName() const {
        return name;
    }

    char getGender() const {
        return gender;
    }

    std::string getPhoneNumber() const {
        return phoneNumber;
    }

private:
    std::string name;
    char gender;
    std::string phoneNumber;
};

// 测试用例:测试 Contact 类的构造函数和成员访问函数
TEST(ContactTest, ConstructorAndGetters) {
    Contact contact("Bob", 'M', "1234567890");
    EXPECT_EQ(contact.getName(), "Bob");
    EXPECT_EQ(contact.getGender(), 'M');
    EXPECT_EQ(contact.getPhoneNumber(), "1234567890");
}

// 主函数,运行所有测试
int main(int argc, char **argv) {
    ::testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}
```

### 代码解释
1. **包含头文件**:引入所需的头文件,其中 `gtest/gtest.h` 是 Google Test 的主要头文件。
2. **定义类**:这里重新定义了要测试的 `Student` 类和 `Contact` 类,实际项目中,这些类可能在其他头文件中定义。
3. **编写测试用例**:
    - `TEST(StudentTest, ConstructorAndGetters)`:定义了 `Student` 类的一个测试用例,测试构造函数和成员访问函数。
    - `TEST(ContactTest, ConstructorAndGetters)`:为 `Contact` 类编写了类似的测试用例。
4. **测试框架初始化和运行**:在 `main` 函数中,调用 `::testing::InitGoogleTest` 初始化 Google Test 框架,然后通过 `RUN_ALL_TESTS` 运行所有测试用例。
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
21、
## QT 环境安装

Qt5.9.4

Qt:基于C++可跨平台的App和UI开发框架。

App:Application,应用程序。

UI:User Interface 用户界面。

GUI:Graphics User interface 图形用户界面。

平台:Windows、Ubuntu、CentOS、Android、IOS、鸿蒙、UOS(统信)、Deepin、塞班

跨平台:可在不同平台上直接运行的方式。

两种跨平台方式:

1、程序级:编译好的程序可直接在不同的平台上运行,需安装解释器来支持。如:Java、Python、JS等。

2、源码级:一份代码,无需修改,可直接在不同平台上编译、运行,需编译环境或交叉编译来支持。如:C、C++等。

速度比较:源码级快、程序级慢。
IDE(集成开发环境):Qt Creator 4.5.0

VS 2015助手:Qt VST

工具-->扩展与更新-->联机-->搜索"Qt"-->下载Qt VST-->安装Qt VST-->完成后重启VS2015-->配置Qt版本(msvc2015_64)

安装包下载地址: https://download.qt.io/official_releases/qt/

工程目录:QtProject

**创建工程不能带中文路径。**

类名格式:TestMainWindow

添加控件:ui文件-->拖拽,双击ui文件进入设计师界面

解决按钮文字显示不全

- 方法1、使用布局自适应大小
- 方法2、在主函数设置DPI属性
  - **QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);** 

自适应大小:栅格布局+间隔。

### 驼峰命名法

大驼峰:类名、结构体,例如MyClass

小驼峰:变量名、函数名,例如myClass

凹驼峰:文件、工程,例如my_class

### 常用英文

GUI 即图形用户界面(Graphical User Interface)

## QT基本概念

### Qt是什么?

Qt是一个基于C++,可跨平台的APP和UI开发框架

平台:各类系统

跨平台:可在不同平台上直接运行的方式有两种跨的方式

1. **程序级:**编译好的程序可直接在不同平台上运行,需安装解释器(虚拟机)来支持
2. **源码级:**一份代码,无需修改,可直接在不同平台上编译运行,需编译环境或交叉编译来支持

### Qt版本介绍

Qt安装包:
qt-opensource-windows-x86-mingw492-5.6.1-1.exe

- opensource表示开源版本
- windows-x86表示Windows 32位平台
- mingw表示使用MinGW编译器
- 5.6.1-1是当前版本号

**MinGW**即**Minimalist GNU For Windows**,是将GNU开发工具移植到Win32平台下的产物,是一套Windows上的GNU工具集。用其开发的程序不需要额外的第三方DLL支持就可以直接在Windows下运行。



### Qt分辨率自适应

方法1:设置本地电脑分辨率

方法2:UI模块中自适应

方法3:

```c++
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
```

### C++和Qt的区别

输出:

C++:头文件\    和cout<<

Qt:头文件\ 和qDebug()<<

**Qt中使用cout会在程序执行后才输出,不友好**

**qDebug()会自动换行,多段数据一起输入不需要输入“ ”空格隔开,会自动分隔**



### Qt主函数参数

主函数参数在项目--->Command line arguments中添加,**参数以空格分隔**

```cpp
#include "testmainwindow.h"
#include 
#include 

int main(int argc, char *argv[])
{
    qDebug()<高级(纯代码)->大师级(自定义控件)]

## QT常用控件

explicit关键字:显式的,防止隐式转换。

```
class A
{
    int a;
public:
    explicit A(int x){
        a = x;
    }
};
int main(){
    A a1(5);        //显式调用有参构造
    A a2 = A(5);        //显式调用构造 A(int x)
    A a3 = 5;            //隐式转换,一旦加了explicit 关键字修饰有参构造,隐式转换则失败
}
```

**控件类型**

Layouts:布局

- vertical Layout:垂直布局
- Horizontal layout:水平布局
- Grid Layout:网络布局
- Form Layout:表单布局

Spacers:间隔器

- Horizontal Spacer:水平间隔器
- Vertical Spacer:垂直间隔器

Buttons:按钮

- Push Button:按键
- Tool Button:工具按钮
- Radio Button:收音机按钮
- Check Box:复选框
- Command Link Button:命令链接按钮
- Dialog Button Box:对话按钮框

Item Views (Model-Based):项目视图(基于模型)

- List View:列表视图
- Tree View:树视图
- Table View:表格视图
- Column View:列视图

Item Widgets (Item-Based):项目Widget(基于项目)

- List Widget:列表小部件
- Tree Widget:树状插件
- Table Widget:表格Widget

Containers:容器/集装箱

- Group Box:群组框
- Scroll Area:滚动区域
- Tool Box:工具箱
- Tab Widget:标签组件
- Stacked Widget:堆叠部件
- Frame:框架
- Widget:部件
- MDI Area:MDI区域
- Dock Widget:停靠小部件
- QAxWidget:

Input Widgets:输入小部件

- Combo Box:组合框
- Font Combo Box:字体组合框
- Line Edit:行编辑框
- Text Edit:文本编辑框
- Plain Text Edit:纯文本编辑
- Spin Box:旋转盒
- Double Spin Box:双旋转盒
- Time Edit:时间编辑
- Date Edit:日期编辑
- Date/Time Edit:日期/时间编辑
- Dial:拨号
- Horizontal Scroll Bar:水平滚动条
- Vertical Scroll Bar:垂直滚动条
- Horizontal Slider:
- Vertical Slider:
- Key Sequence Edit:密钥序列编辑

Display Widgets:显示小部件

- Label:标签
  - 设置文本:对象.setText("文本");
  - 取文本:对象.text() --> 返回QString类型的文本;
- Text Browser:文本浏览器
  - 可用追加的方式添加文本:对象.append("");
- Graphics View:图形视图
- Calender Widget:日历
- LCD Number:液晶显示器编号
- Progress Bar:进度条
- Horizontal Line:水平线
- Vertical Line:垂直线
- OpenGL Widget
- QQuikWidget 

**控件属性**

先把空间放到ui界面上,在构造中:

- 读:ui->控件对象->text     直接就是属性名或函数名,或在text前(加一些前缀配合什么样的属性要前缀)
- 写:ui->控件对象->set属性名(参数列表)     修改属性内容 

**复选框:**

- ui->控件对象->is属性名     返回值为bool
- ui->控件对象->has属性名     返回值为bool
- ui->控件对象->属性名     返回值为bool

**行编辑框:**

- ui->控件对象->text()  读
- ui->控件对象->setText(字符串)  写

**控件具体属性**

**通用属性**

QObject:对象;

- objectName:对象名称;

QWidget:部件;

- enabled:启用;
- geometry:几何形状;
- sizePolicy:尺寸策略;
- minimumSize:最小尺寸;
- maximumSize:最大尺寸;
- sizelncrement:尺寸增量;
- baseSize:基本尺寸;
- palette:调色板;
- font:字体;
- cursor:光标;
- mouseTracking:鼠标跟踪;
- tabletTracking:平板跟踪;
- focusPolicy:焦点策略;
- contextMenuPolicy:上下文菜单策略;
- acceptDrops:接受拖放;
- toolTip:工具提示;
- toolTipDuration:工具提示持续时间;
- statusTip:状态提示;
- whatsThis:这是什么;
- accessibleName:可访问名称;
- accessibleDescription:可访问描述;
- layoutDirection:布局方向;
- autoFillBackground:自动填充背景;
- styleSheet:样式表;
- locale:区域设置;
- inputMethodHints:输入法提示

**独有属性**

PushButton

- QAbstractButton:抽象按钮;
  - text:文本;
  - icon:图标;
  - iconSize:图标大小;
  - shortcut:快捷键;
  - checkable:可复选;
  - checked:已选中;
  - autoRepeat:自动重复;
  - autoExclusive:自动排他;
  - autoRepeatDelay:自动重复延迟;
  - autoRepeatInterval:自动重复间隔;
- QPushButton:按钮;
  - autoDefault:自动默认;
  - default:默认;
  - flat:扁平。

Label

- QFrame:框架;
  - frameShape:框架形状;
  - frameShadow:框架阴影;
  - lineWidth:线宽;
  - midLineWidth:中线宽;
- QLabel:标签;
  - text:文本;
  - textFormat:文本格式;
  - pixmap:像素图;
  - scaledContents:缩放内容;
  - alignment:对齐方式;
  - wordWrap:自动换行;
  - margin:边距;
  - indent:缩进;
  - openExternallinks:打开外部链接;
  - textlnteractionFlags:文本交互标志;
  - buddy:伙伴。
- API
  - 读
    - text():获取文本内容
    - textFormat(): 用于获取文本的格式类型(如纯文本、富文本)。
    - textInteractionFlags(): 用于获取文本的交互标志(如是否允许点击链接)。
  - 写
  - 
    - setTextFormat(参数);
      - Qt::AutoText:自动确定文本格式(通常为纯文本)。
      - Qt::PlainText:纯文本格式,不支持 HTML 格式。
      - Qt::RichText:富文本格式,支持 HTML 和样式。
      - Qt::MarkdownText:Markdown 格式(从 Qt 5.14 开始支持)。

LineEdit

- QLineEdit:行编辑框;
  - inputMask:输入掩码;
  - text:文本内容;
  - maxLength:最大长度;
  - frame:边框;
  - echoMode:回显模式;
  - cursorPosition:光标位置;
  - alignment:对齐方式;
  - dragEnabled:启用拖动;
  - readOnly:只读;
  - placeholderText:占位符文本;
  - cursorMoveStyle:光标移动风格;
  - clearButtonEnabled:启用清除按钮。

https://blog.csdn.net/GG_Bruse/article/details/136634146

```
https://blog.csdn.net/Miwll/article/details/141201383?utm_medium=distribute.pc_relevant.none-task-blog-2~default~baidujs_baidulandingword~default-0-141201383-blog-137563865.235^v43^pc_blog_bottom_relevance_base7&spm=1001.2101.3001.4242.1&utm_relevant_index=1
```

https://blog.csdn.net/GG_Bruse/article/details/136634146

### PushButton

类继承关系:QObject->QWidget->QAbstractButton->QPushbutton

**geometry:**几何形状

**sizePolicy:**尺寸策略

在Qt中,sizePolicy是每个Qt widgets(包括QPushButton)的一个属性,它决定了当窗口大小改变或者父容器大小改变时,控件(如QPushButton)应该如何调整自身的大小。对于sizePolicy的水平策略(Horizontal Policy),具体来说,它可以影响按钮在水平方向上的尺寸管理行为,主要包括以下几个方面:

- **Fixed**:表示按钮应该保持固定的宽度,无论窗口大小如何变化,按钮都不会随之拉伸或收缩。
- **Minimum**:控件至少需要保持指定的最小宽度,但允许在空间充足的情况下扩展到大于这个最小值。
- **Maximum**:控件的最大宽度不会超过指定值,但在空间不足时仍然可以缩小到更小。
- **Preferred**(也称为MinimumExpanding):控件有一个理想的首选宽度,通常是由控件内容决定的。在布局中,如果空间足够,控件将尝试扩展到这个宽度,但如果其他控件也需要空间,它可能会比首选宽度小。同时,它也会保持一个最小宽度。
- **Expanding**(也称为Ignored):控件在布局中尽可能占据所有可用的空间。如果布局的所有控件都设置了水平扩张策略,它们将会平等地分配额外的空间。

**palette:**调色板

**font:**字体

**cursor:**光标,游标

**mouseTracking:**鼠标追踪

```
mouseTracking属性用于保存是否启用鼠标跟踪,缺省情况是不启用的。没启用的情况下,对应部件只接收在鼠标移动同时至少一个鼠标按键按下时的鼠标移动事件,启用鼠标跟踪的情况下,任何鼠标移动事件部件都会接收。

部件方法hasMouseTracking()用于返回当前是否启用鼠标跟踪,setMouseTracking(bool enable)用于设置是否启用鼠标跟踪。

与鼠标跟踪相关的函数主要是mouseMoveEvent()。
```

**tabletTracking:**平板触控板/笔追踪

```
tabletTracking属性保存是否启用部件的平板跟踪,缺省是不起用的。没有启用平板跟踪的情况下,部件仅接收触控笔与平板接触或至少有个触控笔按键按下时的触控笔移动事件。
如果部件启用了平板跟踪功能,部件能接收触控笔靠近但未真正接触平板时的触控笔移动事件,这可以用于监视操作位置以及部件的辅助操作功能(如旋转和倾斜),并为图形界面提供这些操作的信息接口。

部件方法hasTabletTracking()用于返回当前是否启用平板跟踪,setTabletTracking(bool enable)用于设置是否启用平板跟踪。
```

**focus policy:**焦点策略

- **NoFocus:**不接受键盘焦点。这个属性通常用于那些不需要与用户交互的控件。
- **TabFocus:**通过 Tab 键获得焦点。这个属性用于需要通过 Tab 键进行导航的控件。
- **ClickFocus:** 通过鼠标点击获得焦点。
- **StrongFocus:**通过 Tab 键和鼠标点击都可以获得焦点。
- **WheelFocus:** 通过鼠标滚轮也可以获得焦点。

​       属性	      	应用场景		      适用控件示例
Qt::NoFocus	不需要用户交互的控件	QLabel, QFrame
Qt::TabFocus	表单填写、设置选项	QLineEdit, QSpinBox
Qt::ClickFocus	图像编辑、拖拽操作	QGraphicsView
Qt::StrongFocus	综合应用,如文本编辑器	QTextEdit
Qt::WheelFocus	需要通过滚轮进行精细调整的控件	QSlider



**ContextMenuPolicy:**上下文菜单策略

ContextMenuPolicy 是 [Qt](https://so.csdn.net/so/search?q=Qt&spm=1001.2101.3001.7020) 框架中 QWidget 类的一个枚举类型,它定义了如何响应和处理鼠标右键点击事件,即上下文菜单的策略。上下文菜单是一种临时出现的菜单,通常出现在用户执行特定操作(如右键点击)时,提供与当前上下文相关的操作选项。

- NoContextMenu:这个策略表示禁用上下文菜单。当设置为这个值时,即使用户右键点击控件,也不会弹出菜单。这可以用于那些不需要额外菜单选项的控件。widget->setContextMenuPolicy(Qt::NoContextMenu);



- DefaultContextMenu:这个策略表示使用默认的上下文菜单行为。当用户右键点击控件时,Qt 会自动弹出一个包含 actions() 返回的所有动作(QAction 对象)的菜单。这是大多数控件的默认行为。widget->setContextMenuPolicy(Qt::DefaultContextMenu);



- CustomContextMenu:当你想要自定义上下文菜单时,应该使用这个策略。设置为 Qt::CustomContextMenu 后,你需要重写控件的 contextMenuEvent(QContextMenuEvent *event) 事件处理函数来创建和显示自己的菜单。

  ```cpp
  widget->setContextMenuPolicy(Qt::CustomContextMenu);
   
  // 在控件的类中重写 contextMenuEvent 函数
  void MyClass::contextMenuEvent(QContextMenuEvent *event) 
  {
      QMenu menu(this);
      // 添加自定义的动作到菜单
      QAction *action1 = menu.addAction("Custom Action 1");
      QAction *action2 = menu.addAction("Custom Action 2");
      // 显示菜单
      menu.exec();
  }
  ```



- **ActionsContextMenu:**这个策略是 Qt::DefaultContextMenu 的一个特例,它只显示与当前控件相关联的 QAction 对象。这意味着,只有那些通过 addAction 方法添加到控件中的动作才会出现在上下文菜单中。这通常用于那些具有内置动作的控件,例如按钮或工具栏。

  widget->setContextMenuPolicy(Qt::ActionsContextMenu);

  使用这个策略时,你不需要重写 contextMenuEvent 函数,因为 Qt 会自动为你创建一个包含所有相关动作的菜单。



- **PreventContextMenu:**这个策略用于完全阻止上下文菜单的显示。即使用户执行了通常会引发上下文菜单的操作(如右键点击),也不会有任何菜单弹出。这个选项可以用来确保某些控件不接受任何上下文菜单交互。

  widget->setContextMenuPolicy(Qt::PreventContextMenu);

  这个选项对于那些不应该有任何上下文菜单的控件非常有用,例如,一个简单的标签或静态文本控件。

  

  

     在设计用户界面时,选择合适的 ContextMenuPolicy 对于提供良好的用户体验至关重要。你需要根据控件的功能和预期的用户交互来决定使用哪种策略。例如,对于包含多个操作的复杂控件,Qt::DefaultContextMenu 或 Qt::ActionsContextMenu 可能是合适的选择。对于那些不需要任何上下文菜单的控件,Qt::NoContextMenu 或 Qt::PreventContextMenu 可以确保用户不会期望进行不相关的操作。对于需要完全自定义上下文菜单的控件,Qt::CustomContextMenu 提供了最大的灵活性。



**acceptDrops:**接收拖放

**ToolTip:**工具提示

toolTip属性设置部件的toolTip提示信息,toolTip提示信息在鼠标放到控件上会浮动出一个小框显示提示信息。默认情况下,仅显示活动窗口子部件的toolTip,可以通过在窗口对象设置Qt.WA_AlwaysShowToolTips属性来改变,但不能是需要显示tooTip的部件对象中设置。

如果要控制tooTip显示的行为(如控制显示位置),可以重写部件的event()方法捕获事件类型为QEvent.ToolTip的事件。

缺省值为空,可以通过toolTip()和setToolTip( str)来读取和设置toolTip。

**ToolTipDuring:**工具提示

toolTipDuration 属性控制toolTip显示的时长,单位是毫秒,如果设置为-1,则显示时长根据toolTip内容的长度来计算。
toolTipDuration 缺省值为-1,可以通过toolTipDuration ()和settoolTipDuration ( int msec)来读取和设置toolTipDuration 。

**statusTip:**状态提示

statusTip属性保存statusTip提示信息,statusTip提示信息在鼠标放到控件上时在窗口的状态栏显示提示信息,如果窗口无状态栏则不显示。

statusTip属性 缺省值为空字符串,可以通过statusTip()和setstatusTip ( str)来读取和设置statusTip。

**whatsThis:**whatsThis属性保存部件的帮助信息。whatsThis的帮助信息一般在部件获得焦点后按Shift+F1弹出显示,如果这个快捷键被别的功能占用,则whatsThis的帮助信息可能无法展示。有些对话窗提供一个带问号的按钮可以点击显示whatsThis的帮助信息。

whatsThis属性 缺省值为空字符串,可以通过whatsThis()和setwhatsThis ( str)来读取和设置whatsThis。

**accessibleName:**辅助阅读中显示的部件的名称。对于大多数控件,不需要设置此属性。当控件不提供任何文本时,设置此属性非常重要。默认为一个空字符串。

**accessibleDescription:**控件的描述。描述应该提供相对详细的控件信息。默认为一个空字符串。

**autoFillBackground :** 是否自动填充控件背景。默认为false。



**QAbstractButton**

**shortcut:**快捷键

### Layout布局对象

```cpp
#include
#include
#include
#include
#include//水平布局
#include//垂直布局
#include//窗口,QApplication是程序

int main(int argc,char *argv[]){

QApplication app(argc,argv);//程序对象

QLabel *openLabel = new QLabel;
QLable *infoLabel = new QLabel;
    
QLineEdit *cmdLineEdit = new QLineEdit;
QPushButton *commitButton = new PushButton;
QPushButton *cancelButton = new PushButton;
QPushButton *browseButton = new PushButton;
    
infoLabel->setText("inputsmd:");
openLabel->setText("open");
commitButton->setText("commit");
cancelButton->setText("cancel");
browseButton->setText("browse");
    
QHBoxLayout *cmdLayout =new QHBoxLayout;//水平布局对象
cmdLayout->addWidget(openLabel);
cmdLayout->addWidget(cmdLineEdit);
    
QHBoxLayout *buttonLayout = new QHBoxLayout;
buttonLayout->addWidget(commitButton);
butfonLayout->addWidget(cancelButton);
buttonLayout->addWidget(browseButton);
    
QVBoxLayout *mainLayout =new QVBoxLayout//垂直布局对象
mainLayout->addwidget(infoLabel);//布局对象中添加控件对象使用的addwidget
mainLayout->addLayout(cmdLayout);//布局对象中添加布局对象使用的addLayout
mainLayout->addLayout(buttonLayout);
    
QWidget w;//创建空白窗口对象,注意这里不是指针,要用.调用成员函数
w.setLayout(mainLayout);//窗口对象中添加布局对象
w.show();	

return app.exec();//返回程序执行,死循环,让程序一直处于打开状态
}
```

1.打开QT MinGW 窗口

cd到 cpp文件目录

d:  //先进入所在磁盘,再cd到目录

2.修改环境变量

path中加入:D:\Qt\Qt5.9.4\5.9.4\mingw53_32\bin

3.qmake(cpp文件名) -project

生成qmake.pro工程文件,在其中添加

QT += widgets gui





## 工程结构

**.xml文件**

- 可扩展标记语言,用用尖括号包住的名为标签名,以树形结点的方式存在,并在尖括号内加上键值对(属性)来使标签节点的属性更丰富。
- C++用三方库:tinyxml2开源库解析xml。

### 工程文件

xx.pro: 工程项目配置文件,也是启动文件

xx.pro.user:工程在当前系统用户下的记录文件,会自动生成 

​                         主要记录工程的生成目录,编译器信息等,若将源码打包其他人,要删掉该文件

xx.ui:工程可视化界面设计的文件,记录拖拽生成的界面效果

​            是由Qt Designer【QT设计师工具】产生的

​             编译时会自动的生成目录中产生一个相应的的c++头文件:ui.xx.h

​             build-工程名-编译器-版本

​           【xml文件】可扩展标识语言

​             用尖括号包住的标签名,以树形节点的方式存在

​            并在尖括号内加上键值(属性)对来使得标签节点的属性更丰富

```
 内容
 xx[可嵌套]
c++用三方库
tinyxmlz开源库解析xml
```

### 生成目标的目录[生成目录]

一般和源码目录同级

点击按钮执行事件流程:

- 1、鼠标(硬件)点击按钮
- 2、消息循环捕捉点击事件(系统中)
- 3、通过消息坐标遍历屏幕上的顶层窗口,发送消息给窗口句柄,得到程序进程(窗口句柄:系统给每个窗口分配的唯一ID)
- 4、Qt程序中进行事件循环:a.exec(),捕捉到对应事件后分发给对应部件
- 5、鼠标点击事件发送信号,执行与之关联的槽

**build-工程名-编译器-版本**

build-helloworld-Desktop_Qt_5_6_1_MinGW_32bit-Debug

Qt Creator将项目源文件和编译生成的文件进行了分类存放。

helloworld文件夹中是项目源文件,而现在这个文件夹存放的是编译后生成的文件。进入该文件夹可以看到这里有3个Makefile文件和一个ui_hellodialog.h文件,还有两个目录debug和release。

现在release文件夹是空的,进入debug文件夹,这里有3个.o文件和一个.cpp文件,它们是编译时生成的中间文件,可以不必管它,而剩下的一个helloworld.exe文件便是生成的可执行文件。

 testmainwindow.h 头文件

```
#ifndef TESTMAINWINDOW_H
#define TESTMAINWINDOW_H
#include 
int main(int argc, char *argv[])
{
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
    QApplication a(argc, argv);
    TestMainWindow w;
    w.show();
    return a.exec();        //启动事件循环,捕捉并处理事件,将其分发给对应部件
}
```

testmainwindow.cpp源文件

```
#include "testmainwindow.h"
#include "ui_testmainwindow.h"
#include 
TestMainWindow::TestMainWindow(QWidget *parent) :
    QMainWindow(parent),        //委托构造
    ui(new Ui::TestMainWindow)
{
    ui->setupUi(this);      //ui指针里面的控件初始化(界面上的控件初始化)
}
TestMainWindow::~TestMainWindow()
{
    delete ui;
}
void TestMainWindow::on_pushButton_clicked()        //按钮点击事件
{
    cnt++;        //记录点击次数
    ui->label->setText(QString::number(cnt));        //设置文本,即更改文本内容  QString::number():数字转为QString类型字符串
    qDebug()<label->text();        //取文本内容    
}
```

**添加并使用资源**

添加资源:右击工程名-->添加新文件-->Qt-->Qt Resource File -->改名--> 完成-->添加前缀-->添加文件-->保存

使用资源:

- 1、通过属性选择。
- 2、在样式中使用,用的是资源路径。
- 3、在代码添加资源路径,使用。
  - 使用方式:ui->pushButton->setStyleSheet();

**鼠标点击并移动窗口**

头文件:

```



```

方法一:

```
void WorkMainWindow::mousePressEvent(QMouseEvent *event)    //获取偏移量,鼠标点击事件
{
    m_offect = event->globalPos() - this->pos();        //m_offect为QPoint类型成员变量
}

void WorkMainWindow::mouseMoveEvent(QMouseEvent *event)     //鼠标移动事件
{
   this->move(event->globalPos() - m_offect);
}
```

event->globalPos()  获取鼠标在屏幕中的全局位置。

this->pos()  获取当前窗口的位置。

方法二:

```
//定义鼠标三种状态方法 
protected: 
//鼠标按下 
void mousePressEvent(QMouseEvent *e); 
//鼠标移动 
void mouseMoveEvent(QMouseEvent *e); 
//鼠标释放 
void mouseReleaseEvent(QMouseEvent *e);
private:
QPoint last;

//获取鼠标点定位窗体位置 
void MainWindow::mousePressEvent(QMouseEvent *e) 
{ 
    last = e->globalPos(); 
} 
void MainWindow::mouseMoveEvent(QMouseEvent *e) 
{ 
    int dx = e->globalX() - last.x(); 
    int dy = e->globalY() - last.y(); 
    last = e->globalPos(); 
    move(x()+dx,y()+dy); 
} 
void MainWindow::mouseReleaseEvent(QMouseEvent *e) 
{ 
    int dx = e->globalX() - last.x(); 
    int dy = e->globalY() - last.y(); 
    move(x()+dx, y()+dy); 
}
```

setWindowFlag(Qt::FramelessWindowHint);  删除窗口顶部

## QT默认源码

### QT头文件 mainWindow.h 

```
#ifndef TESTMAINWINDOW_H
#define TESTMAINWINDOW_H

#include 

namespace Ui {//避免同名冲突,使用命名空间
class TestMainWindow;//负责界面相关的事情
}

//同名类负责UI对应功能的实现
class TestMainWindow : public QMainWindow
{
    Q_OBJECT//有了这个宏,才能使用信号与槽函数

public:
    static int a;
    explicit TestMainWindow(QWidget *parent = 0);//显示关键字,防止隐式转换
    ~TestMainWindow();

private slots:
    //按钮的功能槽函数
    void on_pushButton_clicked();

private:
    //ui指针代表着设计界面的对象
    Ui::TestMainWindow *ui;
};

#endif // TESTMAINWINDOW_H

```

```
namespace Ui {
class TestMainWindow;//负责界面相关的事情
}
```

定义了命名空间Ui,并在其中前置声明了TestMainWindow类,这个类就是在testmainwindow.h文件中看到的那个类。因为它与新定义的类同名,所以使用了Ui命名空间。而前置声明是为了加快编译速度,也可以避免在一个头文件中随意包含其他头文件而产生错误。因为这里只使用了该类对象的指针,如:

```
private:
    //ui指针代表着设计界面的对象
    Ui::TestMainWindow *ui;
};
```

**定义了该类对象的指针,这并不需要该类的完整定义,所以可以使用前置声明(后续在cpp文件中加入ui_xxx.h的头文件声明)。**这样就不用在这里添加ui_hellodialog.h的头文件包含,而可以将其放到hellodialog.cpp文件中进行。

```
class TestMainWindow : public QMainWindow
{
    Q_OBJECT//有了这个宏,才能使用信号与槽函数
```

定义类,并指定继承自QMainWindow

定义了Q_OBJECT宏,扩展了普通C++类的功能,比如下一章要讲的信号和槽功能。必须在类定义的最开始处来定义这个宏

```
explicit TestMainWindow(QWidget *parent = 0);//显示关键字,防止隐式转换
```

是显式构造函数,参数是用来指定父窗口的,默认是没有父窗口的。

### QT main.cpp文件

```
explicit:显示的,防止隐式转换
class A{
    int a;
public:
    explicit A(int x){
        a=x;
    }
};
int main(int argc, char *argv[])
{

    A a1(5);//显示调用有参构造
    A a2=A(5);//显示调用构造:A(int x)
    A a3=5;  //隐式转换,一旦加了explicit,关键字修饰有参构造,隐式转换就会失败
    return 0;
}
```

```
标签:label

api:设置文本:对象.setText(“文本”)

​         取文本:对象.Text()->返回QString类型的文本

ui界面控件:通过ui指针找到

​			数字:0~N,每点一次值加一,并在标签上显示最新值

​			数字转字符串:QString::number(数字)
```

**创建应用程序对象**:

- `QApplication`对象负责管理应用程序的生命周期和全局状态。
- 它处理应用程序的初始化、事件循环和资源管理。例如,它负责接收和分发操作系统发送给应用程序的事件,如鼠标点击、键盘输入等。
- **QApplication类对象**,用于管理应用程序的资源,任何一个Qt GUI程序都要有一个QApplication对象。因为Qt程序可以接收命令行参数,所以它需要argc和argv两个参数。

**初始化和显示界面**:

- 在`main`函数中,通常会创建界面类的实例,并调用其`show`方法来显示界面。

- 新建了一个QLabel对象,并将QDialog对象作为参数,表明了对话框是它的父窗口,也就是说这个标签放在对话框窗口中。
- 在默认情况下,新建的可视部件对象都是不可见的,要使用show()函数让它们显示出来。

**进入事件循环**:

- `main`函数通常会调用`QApplication`对象的`exec`方法,进入应用程序的事件循环。
- 事件循环负责不断地接收和处理事件,直到应用程序退出。在事件循环中,当用户与界面进行交互时,相应的信号和槽会被触发,执行相应的业务逻辑。

- QApplication对象进入事件循环,**这样当Qt应用程序在运行时便可以接收产生的事件**,例如单击和键盘按下等事件

**清理资源和退出应用程序**:

- 当应用程序退出时,`main`函数负责清理资源,如释放内存、关闭文件等。
- 通常,在`main`函数的最后,会返回一个退出码,以指示应用程序的退出状态

### QT mainWindow.cpp文件

```
#include "testmainwindow.h"
#include "ui_testmainwindow.h"
#include 

TestMainWindow::TestMainWindow(QWidget *parent) :
    //委托构造
    QMainWindow(parent),
    //开辟新空间给Ui指针赋值
    ui(new Ui::TestMainWindow)
{
    //给界面控件/Ui指针对象初始化,开辟内存
    ui->setupUi(this);//初始化,类中的指针挨个新建
}
TestMainWindow::~TestMainWindow()
{
    //析构堆内存
    delete ui;
}

int TestMainWindow::a = 100;

//槽函数
void TestMainWindow::on_pushButton_clicked()
{
    qDebug()<<"100";
    //通过点击按钮
    qDebug()<label->text();

    static int num;
    ui->label->setText(QString::number(a));
    a++;
}

```

第2行添加了ui头文件,因为在TestMainWindow.h文件中只是使用了前置声明,所以头文件在这里添加。

第7行设置setupUi()函数的参数为this,表示为现在这个类所代表的对话框创建界面。

### 在Qt Creator中使用.ui界面文件 ui_xxx.h

ui界面生成的控件是xxx.ui格式的XML文件

需要通过编译.ui文件:

​     打开命令提示符程序,然后输入:
​     cd C:\Qt\helloworld_2命令进入helloworld_2文件夹中。再**使用uic编译工具,从ui文件生成头文件**。具体命令是:
​     **uic –o ui_hellodialog.h hellodialog.ui** 
​      就像前面看到的那样,ui文件生成的默认头文件的名称是“ui_”加ui文件的名称。这时在helloworld_2目录中已经生成了相应的头文件。

本地磁盘的项目目录的build-helloworld-Desktop_Qt_5_6_1_MinGW_32bit-Debug目录中,就可以看到由.ui文件生成的ui_hellodialog.h头文件。

或是在源文件的构造函数的初始化列表中对Ui::类名使用F2进入

ui头文件中包含了对所有设计界面的控件的初始化定义和重命名,最后使用connectSlotsByName进行关联

```
QMetaObject::connectSlotsByName(ConnectionMainWindow);
```

## qt窗口拖拽事件

### QT系统两大循环

**1.系统的消息循环:**会通过循环执行的消息队列接收输入设备(鼠标、键盘点击)发来的消息,进行对应的中断事件

**2.Qt程序的事件循环:**程序通过事件循环,捕捉对应事件,分发给对应部件,从而**执行与之关联的槽函数**

### 鼠标点击并移动窗口的问题



### 窗口拖拽实现

头文件构造函数中加入坐标成员变量和鼠标点击、鼠标拖拽事件

```cpp
public:
    explicit Login(QWidget *parent = 0);
    ~Login();

    QPoint m_offset;
    void mousePressEvent(QMouseEvent *event);
    void mouseMoveEvent(QMouseEvent *event);
```

源文件中通过计算窗口左上角坐标和鼠标间偏移量来实现移动

qt2022使用globalPosition().toPoint()获取当前鼠标坐标

```cpp
void Login::mousePressEvent(QMouseEvent *event)
{
    //求坐标偏移量
    m_offset = event->globalPos() - this->pos();
}

void Login::mouseMoveEvent(QMouseEvent *event)
{
    //移动窗口,窗口新坐标记录为当前鼠标松开位置-移动的偏移量
    this->move(event->globalPos() - m_offset);
}
```

方法3:定义QPoint last; last记录鼠标点击窗口时的窗口坐标

```cpp
//获取鼠标点定位窗体位置
void MainWindow::mousePressEvent(QMouseEvent *event)
{
    last = event->globalPos();
}

void MainWindow::mouseMoveEvent(QMouseEvent *event)
{
    int dx = event->globalX() - last.x();
    int dy = event->globalY() - last.y();

    last = event->globalPos();
    move(x()+dx,y()+dy);
}

void MainWindow::mouseReleaseEvent(QMouseEvent *event)
{
    //dx,dy是偏移量
    int dx = event->globalX() - last.x();
    int dy = event->globalY() - last.y();
    move(x()+dx,y()+dy);
}
```

## 对象模型

信号本质则为事件。槽的本质是对信号响应的函数

信号呈现方式就是函数(某个事件产生,Qt框架机制会调用对应的信号函数,通知使用者)。

- 1、所谓信号槽(观察者模式)。
- 2、当某一个事件发生之后,则会发出一个信号(signal)。
- 3、处理信号和自己的一函数称为槽(slot)绑定来处理这个处理这个信号。当信号发出时,被连接的槽函数会自动被回调操作。槽函数可以跟一个信号关联,当信号被发射后,**关联的槽函数自动执行。**
- 4、connect():参数1信号发送者;参数2信号;参数3信号接收者;参数4槽函数(接收到信号之后执行的函数)。

### 信号:是一个函数

声明:signals

作用:作为触发者

```
类
signals:
		void 信号名(形参列表);  //只声明,不实现
使用:emit 信号名(实参);
```

类中声明一个信号,例如:

signals:

  void dlgReturn(int);          // 自定义的信号,只声明,不实现

- 声明一个信号要使用signals关键字。
- 在signals前面不能使用public、private和protected等限定符,因为只有定义该信号的类及其子类才可以发射该信号。
- 信号只用声明,不需要也不能对它进行定义实现。
- 信号没有返回值,只能是void类型的。
- 只有QObject类及其子类派生的类才能使用信号和槽机制,使用信号和槽,还必须在类声明的最开始处添加Q_OBJECT宏。

**发射信号**

例如:

void MyDialog::on_pushButton_clicked()  // 确定按钮

{

 int value = ui->spinBox->value();  // 获取输入的数值

 emit dlgReturn(value);        // 发射信号

 close();               // 关闭对话框

}

当单击确定按钮时,便获取spinBox部件中的数值,然后使用自定义的信号将其作为参数发射出去。发射一个信号要使用emit关键字,例如程序中发射了dlgReturn()信号。

信号作用:作为触发者

### 槽:成员函数

声明:slots

作用:作为功能执行者

```
类
private slots:
		void 槽名(形参列表);  //有声明,有实现
		{//功能}
```

声明:

private slots:

 void showValue(int value);

实现:

void Widget::showValue(int value)     // 自定义槽

{

 ui->label->setText(tr("获取的值是:%1").arg(value));

}

作用:作为功能执行者。

- 声明一个槽需要使用slots关键字。
- 一个槽可以是private、public或者protected类型的,槽也可以被声明为虚函数,这与普通的成员函数是一样的,也可以像调用一个普通函数一样来调用槽。
- 槽的最大特点就是可以和信号关联。

### 信号槽:实现对象间的通信

信号和槽用于两个对象之间的通信,信号和槽机制是Qt的核心特征,也是Qt不同于其他开发框架的最突出的特征。

- 在Qt中,使用了信号和槽来进行对象间的通信。
- 当一个特殊的事情发生时便可以发射一个信号,比如按钮被单击;而槽就是一个函数,它在信号发射后被调用,来响应这个信号。
- 在Qt的部件类中已经定义了一些信号和槽,但是更多的做法是子类化这个部件,然后添加自己的信号和槽来实现想要的功能。

**调用顺序:**

1、在Qt4版本里面,当信号被触发时,会依次随机调用,即本次信号会把与信号关联的所有的槽函数都调用一遍,但顺序是随机的,可用STL模板库的map(有序存储)、set(有序存储)容器存储。

2、在Qt5版本里面,则与关联信号的顺序有关,调用顺序与关联顺序一致。可用STL模板库的vector、list、deque、unordered_map(无序存储)、unordered_set(无序存储)容器存储。

### 信号槽的关联函数:

**connect(信号发送者、信号、信号接收者、槽、连接方式)**

函数原型:

bool QObject::connect ( const QObject * sender, const char * signal, const QObject * receiver, const char * method, Qt::ConnectionType type = Qt::AutoConnection )

- 它的第一个参数为发送信号的对象,例如这里的dlg;
- 第二个参数是要发送的信号,这里是SIGNAL(dlgReturn(int));
- 第三个参数是接收信号的对象,这里是this,表明是本部件,即Widget,当这个参数为this时,也可以将这个参数省略掉,因为connect()函数还有另外一个重载形式,该参数默认为this;
- 第四个参数是要执行的槽,这里是SLOT(showValue(int))。
- 对于信号和槽,必须使用SIGNAL()和SLOT()宏,它们可以将其参数转化为const char* 类型。connect()函数的返回值为bool类型,当关联成功时返回true。
- 信号和槽的参数只能有类型,不能有变量,例如写成SLOT(showValue(int value))是不对的。对于信号和槽的参数问题,基本原则是信号中的参数类型要和槽中的参数类型相对应,而且信号中的参数可以多于槽中的参数,但是不能反过来,如果信号中有多余的参数,那么它们将被忽略。

信号发送者是一个指针,拥有信号;信号接收者是一个指针,拥有槽;

连接方式:

- 自动连接(Qt::AutoConnection):默认的,单线程切换到直接连接,多线程切换到队列连接。
- 直接连接(Qt::DirectConnection):单线程,同步,阻塞。
- 队列连接(Qt::QueuedConnection):(单)多线程,异步,非阻塞。
- 阻塞队列连接(Qt::BlockingQueuedConnection):只能多线程,同步,阻塞;若单线程,死锁。
- 唯一连接(Qt::UniqueConnection):防止重复连接(其他效果同自动连接)。

信号发送者和接收者在同一线程内为单线程。

信号发送者和接收者不在同一线程内为多线程。

同步:排队执行。

异步:同时执行。

STL库:vector 5	list 5	deque 5	map 4	set 4

![img](./Image.png)

### 关联函数原型:

connect写法:

1.宏的写法:SIGNAL,SLOT,connect(信号发送者,SIGNAL(信号名(参类型)),信号接收者,SLOT(槽名(参类型)));

```
头文件类中声明:
signals:
    void mySignal();        //信号声明
private slots:
    void mySlot();        //槽声明
    void on_pushButton_clicked();

源文件:
SignalSlotMainWindow::SignalSlotMainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::SignalSlotMainWindow)
{
    ui->setupUi(this);
    connect(this,SIGNAL(mySignal()),this,SLOT(mySlot()));    //信号槽连接
         //信号发送者为指针类型,拥有信号,信号接收者为指针类型,拥有槽函数
}
SignalSlotMainWindow::~SignalSlotMainWindow()
{
    delete ui;
}
void SignalSlotMainWindow::mySlot()        //自定义槽函数
{
     qDebug()<<100; 
}
void SignalSlotMainWindow::on_pushButton_clicked()    //默认槽函数,点击事件
{
     emit mySignal();    //发射信号
}
```

2.一般的函数指针,前提是信号槽无重载;

connect(信号发送者,&类名::信号名,信号接收者,&类名::槽名);

```
connect(d,&D::ShowSignal,this,&WorkMainWindow::ShowSlot);
```

3.若有重载,使用QOverload加载函数指针;

connect(信号发送者,QOverlaod<形参>::of(&类名::信号名),信号接收者,QOverlaod<形参列表>::of(&类名::槽名));

```
connect(d,QOverload<>::of(&D::ShowSignal),this,QOverload<>::of(&WorkMainWindow::ShowSlot));
```

4.若槽功能简单,则可使lambda表达式取代槽;

connect(信号发送者,QOverlaod<形参列表>::of(&类名::类名),信号接收者,【=/&/变量名】(形参列表){...});

```
connect(d,QOverload<>::of(&D::ShowSignal),this,[=](){
        a->show();
        b->show();
        c->show();
        this->show();});
```

**信号与槽实现步骤**

- 1、有信号-->找地方发射信号
- 2、有槽-->实现功能。
  - **注意:信号与槽在一开始无任何关系。**
- 3、关联信号槽-->关联之后,产生关系。
  - 使用connect()函数进行关联,宏的写法:SIGNAL/SLOT,信号发射后就会执行槽函数。

**使用信号槽的条件**

1、类得继承 QObject 类(直接或间接)

2、类首行得使用宏:Q_OBJECT

3、才可以添加自定义信号、槽。

**注册元类型**

当信号槽的参数为自定义类型时,需要注册元类型才能安全使用,特别是在多线程的情况下:

API:qRegisterMataType<类型>("类型");

***自动关联(可做了解):**

**格式:**

void on_对象名_信号名(形参列表……);  //槽函数

**条件:**

- 给对象注册名字:对象.setObjectName("对象");
  - 示例:pushButton = new QPushButton(centralWidget);
  - pushButton->setObjectName(QStringLiteral("pushButton"));
- 调用:connectSlotsByName(对象名)
  - 示例:QMetaObject::connectSlotsByName(ConnectionMainWindow);

**结论:**

1、QObject类是所有Qt类的基类

2、QWidget类是所有可视界面类的基类

**总结:**

**UI相关:**

1、先绘制效果界面。

2、想办法在主界面显示出所有界面。

**功能相关:**

搞清楚谁有信号谁有槽,想清楚在哪里连接信号槽。

一般在主界面关联(标准说法:在拥有信号/槽的对象的类中关联)。

 

对象树(多叉树):类似C++里智能指针的效果,防止Qt的堆内存泄漏。

**字符串截取**

QString str = "abcdefg";

str.left(长度) 从0开始的长度

str.mid(下标,长度) 从下标开始,去m个字符

str.right(长度) 从末尾开始,向前取长度个字符

** 信号:timeout()-->关联槽**

API函数:

- start()/start(毫秒) 启动
- stop() 停止
- 设定周期 setInterval(毫秒)

**字符数值转换**

字符串转数值:str.toInt();  //str为QString类型

数值转字符串:QString::number(数字)

### 信号与槽的重复连接问题

**一、写在点击槽函数内导致重复关联的原因**

1. 多次调用问题:
   - 当把信号与槽的`connect`关联函数写在按钮点击的槽函数(如`on_pushButton_clicked`)中时,每次点击按钮,这个槽函数都会被执行一次。
   - 每次执行都会再次调用`connect`,从而导致信号和同一个槽不断地重复关联。
   - 例如,第一次点击按钮,建立了一个关联;第二次点击,又建立了一个相同的关联,以此类推。

**二、写在构造函数中不会重复关联的原因**

1. 一次性建立连接:
   - 构造函数在对象创建时只被调用一次。
   - 把信号与槽的关联放在构造函数中,在对象初始化时就建立了连接,并且这个过程只发生一次。
   - 后续无论进行多少次按钮点击或其他操作,都不会再次建立相同的连接,因为连接在一开始就已经确定并且不会被重复执行。

综上所述,为了避免重复关联,通常建议在构造函数或其他合适的初始化位置进行信号与槽的连接,而不是在可能被多次调用的槽函数内部进行连接操作。

### 使用信号槽注意事项:

1.类继承QObject类[直接或间接]

2.类首行使用宏:Q_OBJECT

3.添加自定义信号,槽

```
当信号槽的参数为自定义类型时,需要做一些特殊处理才能安全使用,特别是在多线程的情况下;
特殊处理就是:对自定义类型进行元类型注册;
可以直接使用API来注册:如下
 qRegisterMetaType(“MyClass”)
若要使用到引用类型,也需要对引用注册
qRegisterMetaType(“MyClass&”)
```

qRegisterMetaType<类型>(“类型”)

结论:

1.QObject类是所有qt类的基类

2.QWidget类是所有可视化界面类的基类

### qt三大核心

1.信号槽

2.元对象系统

3.事件模型

```
自动关联:
void on_对象名_信号名(形参列表);  //槽函数
```

MFC:微软基础类库,也可以在vs下绘制ui界面【vs内量】

|      | 平台                    | 跨平台 | 通信方式 | 源码 | 循环     |
| ---- | ----------------------- | ------ | -------- | ---- | -------- |
| MFC  | Windows                 | 否     | 回调机制 | 闭源 | 消息循环 |
| Qt   | Linux,Windows,Android | 是     | 信号槽   | 开源 | 事件循环 |

## sender函数

**信号槽传送数据的两种方式**:

1.信号中带参数

2.sender函数+属性系统

**sender函数作用:**返回信号发送者的指针

**sender的使用场景:**可以在槽中拿到对象指针,配合属性系统,可**实现对象间通信**

QSignalMapper

API:

- mapping(参数)
- sethapping(参数,参数)

```
    connect(m_second,SIGNAL(secSignal()),this,SLOT(showtext()));    连接信号槽

void PropertyMainWindow::showtext()
{
    QString str = sender()->property("textData").toString();    //通过信号发送者的对象指针获取textData属性内容,转为字符串型
    ui->label->setText(str);    //修改label的text属性为str
}
```

## 属性对应API

```
先把控件放到ui界面

在构造中ui->控件对象->读->1.属性名当函数名
					   2.加前缀配合使用
				   写->set属性名(参数列表)
标签,按钮,行编辑框 text->text()
					  	setText()
内容:数值,文本,下拉选择
读:属性名()
写:set属性名()

复选框
is属性名--bool
has属性名--bool
属性名--bool
```

## 属性系统

1.作用

- 1、可配合sender()函数实现对象间的通信。
- 2、可用来掌握控件背后的实现原理。

2.添加属性的方式

1、宏:Q_PROPERTY

2、API:

对象.setProperty()  添加/修改

对象.property()    读取属性

**宏的方式**

Q_PROPERTY(类型 属性名 READ 函数名 WRITE 函数名 NOTIFY 信号名……);

类型:需添加对应的私有成员存属性内容(同类型)

属性名:当字符串用

函数名:对私有成员变量读写

信号名:写的时候发射信号通知变更了,变更时携带参数,传递变更的内容。

用map容器存放名值对,存在则覆盖;不存在则新增。

## 对象树:

**本质:**多叉树。利用栈的结构特点去自动销毁父节点对其孩子列表中的对象也一起被销毁

**作用:**

- 防止内存泄漏,与智能指针异曲同工。
- 若对象有界面,还可以控制界面显示层次。(子部件在父部件上显示)
  - 注意:这里的父子是非继承关系,只是树上的节点关系。

**添加对象到树的方式:**

1、实例化时,参数传父节点指针

```
A *a1 = new A(this);      //this作为a1的父节点指针
```

2、使用API来指定父节点指针:对象.setParent(父节点指针);

```
A *a2 = new A;
a2->setParent(this);        //this作为a2的父节点指针
```

**新增C++类如何添加到对象树:**

新添C++类,首先**继承QPushButton**这个有可视化界面的父类

然后在构造函数的定义中**添加 QWidget类的指针**

```cpp
#ifndef MYBUTTON_H
#define MYBUTTON_H
#include 

class MyButton : public QPushButton
{
public:
    MyButton(QWidget *p = 0);
    ~MyButton();
};

#endif // MYBUTTON_H

```

接着去cpp源文件中使用委托构造在类的构造函数的初始化列表中完成初始化

```cpp
#include "mybutton.h"
#include 
MyButton::MyButton(QWidget *p)
    :QPushButton(p)//委托构造,QWidget父类是有界面的基类
{

}

MyButton::~MyButton()
{
    qDebug()<<"delete MyButton";
}

```

如此一来,新的C++类MyButton就可以通过主函数new出来,添加到对象树中进行统一管理了:

```



--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
22、

### 1. `auto` 关键字
`auto` 关键字用于自动推导变量的类型,在编译时根据初始化表达式自动确定变量的类型,从而简化代码编写,尤其适用于类型名较长或复杂的情况。

#### 示例代码
```cpp
#include 
#include 

int main() {
    // 自动推导为 int 类型
    auto num = 10; 
    // 自动推导为 std::vector::iterator 类型
    std::vector vec = {1, 2, 3};
    auto it = vec.begin(); 

    std::cout << "num: " << num << std::endl;
    for (; it != vec.end(); ++it) {
        std::cout << *it << " ";
    }
    std::cout << std::endl;

    return 0;
}
```

### 2. `nullptr` 关键字
`nullptr` 是 C++11 引入的一个新的空指针字面量,用于表示空指针。它可以隐式转换为任何指针类型,解决了 `NULL` 在某些情况下的二义性问题。

#### 示例代码
```cpp
#include 

void func(int* ptr) {
    if (ptr == nullptr) {
        std::cout << "传入的是一个空指针" << std::endl;
    } else {
        std::cout << "传入的不是空指针" << std::endl;
    }
}

int main() {
    int* ptr = nullptr;
    func(ptr);

    return 0;
}
```


### 3. `tuple` 元组
`std::tuple` 是一个固定大小的、可以存储不同类型元素的容器。它可以将多个不同类型的值组合成一个对象,方便数据的传递和处理。

#### 示例代码
```cpp
#include 
#include 

int main() {
    // 创建一个包含 int、double 和 std::string 类型元素的元组
    auto myTuple = std::make_tuple(10, 3.14, "Hello");

    // 获取元组中的元素
    int num = std::get<0>(myTuple);
    double d = std::get<1>(myTuple);
    std::string str = std::get<2>(myTuple);

    std::cout << "num: " << num << ", d: " << d << ", str: " << str << std::endl;

    return 0;
}
```


### 4. 范围 `for` 循环
范围 `for` 循环提供了一种更简洁的方式来遍历容器或数组中的元素,无需手动管理迭代器或索引。

#### 示例代码
```cpp
#include 
#include 

int main() {
    std::vector vec = {1, 2, 3, 4, 5};

    // 范围 for 循环遍历 vector
    for (auto element : vec) {
        std::cout << element << " ";
    }
    std::cout << std::endl;

    return 0;
}
```


### 5. 匿名函数 `lambda`
`lambda` 表达式是一种匿名函数,允许在需要函数对象的地方直接定义一个简短的函数,无需显式定义一个命名的函数或函数对象类。

#### 示例代码
```cpp
#include 
#include 
#include 

int main() {
    std::vector vec = {1, 2, 3, 4, 5};

    // 使用 lambda 表达式对 vector 中的每个元素进行平方操作
    std::for_each(vec.begin(), vec.end(), [](int& num) {
        num = num * num;
    });

    // 输出处理后的 vector
    for (auto element : vec) {
        std::cout << element << " ";
    }
    std::cout << std::endl;

    return 0;
}
```	
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
23、

### G++ 编译器的使用

#### `-o` 和 `-c` 选项的区别
- **`-c` 选项**:只进行编译和汇编操作,不进行链接。它会将每个源文件(`.cpp` 文件)分别编译成对应的目标文件(`.o` 文件),但不会生成可执行文件。例如:
```bash
g++ -c main.cpp utils.cpp
```
上述命令会生成 `main.o` 和 `utils.o` 两个目标文件。
- **`-o` 选项**:用于指定输出文件的名称。可以用于生成可执行文件,也可以用于生成目标文件。例如:
```bash
g++ main.cpp utils.cpp -o my_program
```
此命令会将 `main.cpp` 和 `utils.cpp` 编译、链接成一个名为 `my_program` 的可执行文件。

#### `-g` 选项的目的
`-g` 选项用于在编译时生成调试信息,这些信息会被嵌入到生成的目标文件或可执行文件中。调试信息包含了源代码的行号、变量名、函数名等,使得调试器(如 GDB)能够将可执行文件中的指令与源代码中的行对应起来,方便进行调试。示例命令如下:
```bash
g++ -g main.cpp -o my_program
```

### GDB 调试器的使用

#### 逐语句(Step)和逐过程(Next)执行
- **逐语句执行(Step)**:使用 `step` 或 `s` 命令,它会进入函数内部,逐行执行代码,包括函数调用语句也会进入函数体内部执行。例如,在 GDB 中启动调试后:
```plaintext
(gdb) s
```
- **逐过程执行(Next)**:使用 `next` 或 `n` 命令,它会执行一行代码,但不会进入函数内部,而是将函数调用当作一条语句执行完毕。例如:
```plaintext
(gdb) n
```

#### 给函数设置断点
使用 `break` 或 `b` 命令,后面跟上函数名即可在该函数的入口处设置断点。例如,要在 `main` 函数处设置断点:
```plaintext
(gdb) b main
```

#### 给最后一行设置断点
首先需要知道源文件的名称和总行数。可以使用 `break` 命令,后面跟上源文件名和行号。假设源文件名为 `main.cpp`,可以通过以下命令查看总行数:
```bash
wc -l main.cpp
```
假设总行数为 100,那么在最后一行设置断点的命令为:
```plaintext
(gdb) b main.cpp:100
```

#### 查看所有断点
使用 `info breakpoints` 或 `info b` 命令可以查看当前设置的所有断点信息,包括断点编号、位置、是否启用等。例如:
```plaintext
(gdb) info b
```

### Makefile 的编写与执行

#### 执行一个 Makefile
在包含 Makefile 的目录下,直接在终端输入 `make` 命令即可执行 Makefile 中的规则。如果 Makefile 的文件名不是默认的 `Makefile` 或 `makefile`,可以使用 `-f` 选项指定 Makefile 的文件名。例如:
```bash
make -f my_makefile
```

#### 编写一个 Makefile
一个简单的 Makefile 通常包含目标(target)、依赖(dependency)和命令(command)三部分。以下是一个简单的示例,用于编译一个包含 `main.cpp` 和 `utils.cpp` 的 C++ 项目:
```makefile
# 编译器和编译选项
CXX = g++
CXXFLAGS = -Wall -g

# 目标文件列表
OBJS = main.o utils.o

# 最终可执行文件
TARGET = my_program

# 生成可执行文件的规则
$(TARGET): $(OBJS)
    $(CXX) $(CXXFLAGS) -o $(TARGET) $(OBJS)

# 生成 main.o 的规则
main.o: main.cpp utils.h
    $(CXX) $(CXXFLAGS) -c main.cpp

# 生成 utils.o 的规则
utils.o: utils.cpp utils.h
    $(CXX) $(CXXFLAGS) -c utils.cpp

# 清理规则
clean:
    rm -f $(OBJS) $(TARGET)
```
上述 Makefile 的解释如下:
- `CXX`:指定编译器为 `g++`。
- `CXXFLAGS`:指定编译选项,`-Wall` 表示显示所有警告信息,`-g` 表示生成调试信息。
- `OBJS`:列出所有的目标文件。
- `TARGET`:指定最终生成的可执行文件的名称。
- `$(TARGET): $(OBJS)`:定义生成可执行文件的规则,依赖于所有的目标文件。
- `main.o: main.cpp utils.h` 和 `utils.o: utils.cpp utils.h`:分别定义生成 `main.o` 和 `utils.o` 的规则,依赖于对应的源文件和头文件。
- `clean`:定义清理规则,用于删除生成的目标文件和可执行文件。
24、

### 连接并操作 MySQL 数据库

#### 1. 使用 MySQL API
MySQL API 是 MySQL 官方提供的 C/C++ 接口,能让你直接与 MySQL 数据库进行交互。

**示例代码**
```cpp
#include 
#include 

int main() {
    // 初始化 MySQL 连接
    MYSQL *conn = mysql_init(nullptr);
    if (conn == nullptr) {
        std::cerr << "初始化 MySQL 连接失败: " << mysql_error(conn) << std::endl;
        return 1;
    }

    // 连接到 MySQL 数据库
    if (mysql_real_connect(conn, "localhost", "user", "password", "database_name", 0, nullptr, 0) == nullptr) {
        std::cerr << "连接到 MySQL 数据库失败: " << mysql_error(conn) << std::endl;
        mysql_close(conn);
        return 1;
    }

    // 增删改查操作示例

    // 插入数据
    std::string insertQuery = "INSERT INTO users (name, age) VALUES ('John', 25)";
    if (mysql_query(conn, insertQuery.c_str()) != 0) {
        std::cerr << "插入数据失败: " << mysql_error(conn) << std::endl;
    }

    // 查询数据
    std::string selectQuery = "SELECT * FROM users";
    if (mysql_query(conn, selectQuery.c_str()) == 0) {
        MYSQL_RES *result = mysql_store_result(conn);
        if (result != nullptr) {
            MYSQL_ROW row;
            while ((row = mysql_fetch_row(result))) {
                for (int i = 0; i < mysql_num_fields(result); ++i) {
                    std::cout << (row[i] ? row[i] : "NULL") << " ";
                }
                std::cout << std::endl;
            }
            mysql_free_result(result);
        }
    } else {
        std::cerr << "查询数据失败: " << mysql_error(conn) << std::endl;
    }

    // 更新数据
    std::string updateQuery = "UPDATE users SET age = 26 WHERE name = 'John'";
    if (mysql_query(conn, updateQuery.c_str()) != 0) {
        std::cerr << "更新数据失败: " << mysql_error(conn) << std::endl;
    }

    // 删除数据
    std::string deleteQuery = "DELETE FROM users WHERE name = 'John'";
    if (mysql_query(conn, deleteQuery.c_str()) != 0) {
        std::cerr << "删除数据失败: " << mysql_error(conn) << std::endl;
    }

    // 调用存储过程
    std::string callProcedureQuery = "CALL your_stored_procedure()";
    if (mysql_query(conn, callProcedureQuery.c_str()) != 0) {
        std::cerr << "调用存储过程失败: " << mysql_error(conn) << std::endl;
    }

    // 关闭 MySQL 连接
    mysql_close(conn);
    return 0;
}
```
**代码解释**
- `mysql_init`:初始化 MySQL 连接。
- `mysql_real_connect`:连接到指定的 MySQL 数据库。
- `mysql_query`:执行 SQL 语句。
- `mysql_store_result`:获取查询结果集。
- `mysql_fetch_row`:逐行获取结果集中的数据。
- `mysql_free_result`:释放结果集占用的内存。
- `mysql_close`:关闭 MySQL 连接。

#### 2. 使用 ADO(ActiveX Data Objects)
ADO 是微软提供的一种数据访问技术,可用于连接多种数据库,包括 MySQL。不过在 Linux 环境下使用 ADO 不太方便,一般在 Windows 环境中结合 Visual Studio 开发。以下是一个简单示例:
```cpp
#include 
#import "C:\Program Files\Common Files\System\ado\msado15.dll" no_namespace rename("EOF", "EndOfFile")

int main() {
    CoInitialize(nullptr);
    _ConnectionPtr conn(__uuidof(Connection));
    _RecordsetPtr rs(__uuidof(Recordset));

    try {
        // 连接到 MySQL 数据库
        conn->Open("Provider=MSDASQL;Driver={MySQL ODBC 8.0 Unicode Driver};Server=localhost;Database=database_name;User=user;Password=password;", "", "", adConnectUnspecified);

        // 插入数据
        conn->Execute("INSERT INTO users (name, age) VALUES ('John', 25)", nullptr, adCmdText);

        // 查询数据
        rs = conn->Execute("SELECT * FROM users", nullptr, adCmdText);
        while (!rs->EndOfFile) {
            std::cout << (const char*)_bstr_t(rs->Fields->GetItem("name")->Value) << " "
                      << (long)rs->Fields->GetItem("age")->Value << std::endl;
            rs->MoveNext();
        }

        // 更新数据
        conn->Execute("UPDATE users SET age = 26 WHERE name = 'John'", nullptr, adCmdText);

        // 删除数据
        conn->Execute("DELETE FROM users WHERE name = 'John'", nullptr, adCmdText);

        // 调用存储过程
        conn->Execute("EXEC your_stored_procedure", nullptr, adCmdText);

        // 关闭连接
        rs->Close();
        conn->Close();
    } catch (_com_error& e) {
        std::cerr << "错误信息: " << (const char*)e.Description() << std::endl;
    }

    CoUninitialize();
    return 0;
}
```
**代码解释**
- `CoInitialize`:初始化 COM 库。
- `_ConnectionPtr` 和 `_RecordsetPtr`:用于创建连接对象和记录集对象。
- `conn->Open`:打开数据库连接。
- `conn->Execute`:执行 SQL 语句。
- `CoUninitialize`:释放 COM 库资源。

### 连接并操作 Oracle 数据库

#### 1. 使用 OCI(Oracle Call Interface)
OCI 是 Oracle 提供的底层 C/C++ 接口,用于与 Oracle 数据库进行交互。

**示例代码**
```cpp
#include 
#include 

int main() {
    OCIEnv *envhp;
    OCIError *errhp;
    OCISvcCtx *svchp;
    OCIStmt *stmthp;
    OCIDefine *defnp;
    OCIBind *bndp;
    sword status;

    // 初始化 OCI 环境
    status = OCIEnvCreate(&envhp, OCI_DEFAULT, nullptr, nullptr, nullptr, nullptr, 0, nullptr);
    if (status != OCI_SUCCESS) {
        std::cerr << "初始化 OCI 环境失败" << std::endl;
        return 1;
    }

    // 创建错误句柄
    status = OCIHandleAlloc(envhp, (dvoid **)&errhp, OCI_HTYPE_ERROR, 0, nullptr);
    if (status != OCI_SUCCESS) {
        std::cerr << "创建错误句柄失败" << std::endl;
        OCIHandleFree(envhp, OCI_HTYPE_ENV);
        return 1;
    }

    // 连接到 Oracle 数据库
    status = OCILogon(envhp, errhp, &svchp, (text *)"user/password@database", strlen("user/password@database"));
    if (status != OCI_SUCCESS) {
        std::cerr << "连接到 Oracle 数据库失败" << std::endl;
        OCIHandleFree(errhp, OCI_HTYPE_ERROR);
        OCIHandleFree(envhp, OCI_HTYPE_ENV);
        return 1;
    }

    // 插入数据
    status = OCIHandleAlloc(envhp, (dvoid **)&stmthp, OCI_HTYPE_STMT, 0, nullptr);
    if (status != OCI_SUCCESS) {
        std::cerr << "创建语句句柄失败" << std::endl;
        OCILogoff(svchp, errhp);
        OCIHandleFree(errhp, OCI_HTYPE_ERROR);
        OCIHandleFree(envhp, OCI_HTYPE_ENV);
        return 1;
    }
    status = OCIStmtPrepare(stmthp, errhp, (text *)"INSERT INTO users (name, age) VALUES (:1, :2)", strlen("INSERT INTO users (name, age) VALUES (:1, :2)"), OCI_NTV_SYNTAX, OCI_DEFAULT);
    if (status != OCI_SUCCESS) {
        std::cerr << "准备插入语句失败" << std::endl;
        OCIHandleFree(stmthp, OCI_HTYPE_STMT);
        OCILogoff(svchp, errhp);
        OCIHandleFree(errhp, OCI_HTYPE_ERROR);
        OCIHandleFree(envhp, OCI_HTYPE_ENV);
        return 1;
    }
    // 绑定参数
    char name[] = "John";
    int age = 25;
    status = OCIBindByName(stmthp, &bndp, errhp, (text *)":1", -1, name, sizeof(name), SQLT_STR, nullptr, nullptr, nullptr, 0, nullptr, OCI_DEFAULT);
    if (status != OCI_SUCCESS) {
        std::cerr << "绑定参数 1 失败" << std::endl;
        OCIHandleFree(stmthp, OCI_HTYPE_STMT);
        OCILogoff(svchp, errhp);
        OCIHandleFree(errhp, OCI_HTYPE_ERROR);
        OCIHandleFree(envhp, OCI_HTYPE_ENV);
        return 1;
    }
    status = OCIBindByName(stmthp, &bndp, errhp, (text *)":2", -1, &age, sizeof(age), SQLT_INT, nullptr, nullptr, nullptr, 0, nullptr, OCI_DEFAULT);
    if (status != OCI_SUCCESS) {
        std::cerr << "绑定参数 2 失败" << std::endl;
        OCIHandleFree(stmthp, OCI_HTYPE_STMT);
        OCILogoff(svchp, errhp);
        OCIHandleFree(errhp, OCI_HTYPE_ERROR);
        OCIHandleFree(envhp, OCI_HTYPE_ENV);
        return 1;
    }
    status = OCIStmtExecute(svchp, stmthp, errhp, 1, 0, nullptr, nullptr, OCI_DEFAULT);
    if (status != OCI_SUCCESS) {
        std::cerr << "执行插入语句失败" << std::endl;
        OCIHandleFree(stmthp, OCI_HTYPE_STMT);
        OCILogoff(svchp, errhp);
        OCIHandleFree(errhp, OCI_HTYPE_ERROR);
        OCIHandleFree(envhp, OCI_HTYPE_ENV);
        return 1;
    }

    // 查询数据
    status = OCIStmtPrepare(stmthp, errhp, (text *)"SELECT * FROM users", strlen("SELECT * FROM users"), OCI_NTV_SYNTAX, OCI_DEFAULT);
    if (status != OCI_SUCCESS) {
        std::cerr << "准备查询语句失败" << std::endl;
        OCIHandleFree(stmthp, OCI_HTYPE_STMT);
        OCILogoff(svchp, errhp);
        OCIHandleFree(errhp, OCI_HTYPE_ERROR);
        OCIHandleFree(envhp, OCI_HTYPE_ENV);
        return 1;
    }
    status = OCIStmtExecute(svchp, stmthp, errhp, 0, 0, nullptr, nullptr, OCI_DEFAULT);
    if (status != OCI_SUCCESS) {
        std::cerr << "执行查询语句失败" << std::endl;
        OCIHandleFree(stmthp, OCI_HTYPE_STMT);
        OCILogoff(svchp, errhp);
        OCIHandleFree(errhp, OCI_HTYPE_ERROR);
        OCIHandleFree(envhp, OCI_HTYPE_ENV);
        return 1;
    }
    // 定义列
    char resultName[50];
    int resultAge;
    status = OCIDefineByPos(stmthp, &defnp, errhp, 1, resultName, sizeof(resultName), SQLT_STR, nullptr, nullptr, nullptr, OCI_DEFAULT);
    if (status != OCI_SUCCESS) {
        std::cerr << "定义列 1 失败" << std::endl;
        OCIHandleFree(stmthp, OCI_HTYPE_STMT);
        OCILogoff(svchp, errhp);
        OCIHandleFree(errhp, OCI_HTYPE_ERROR);
        OCIHandleFree(envhp, OCI_HTYPE_ENV);
        return 1;
    }
    status = OCIDefineByPos(stmthp, &defnp, errhp, 2, &resultAge, sizeof(resultAge), SQLT_INT, nullptr, nullptr, nullptr, OCI_DEFAULT);
    if (status != OCI_SUCCESS) {
        std::cerr << "定义列 2 失败" << std::endl;
        OCIHandleFree(stmthp, OCI_HTYPE_STMT);
        OCILogoff(svchp, errhp);
        OCIHandleFree(errhp, OCI_HTYPE_ERROR);
        OCIHandleFree(envhp, OCI_HTYPE_ENV);
        return 1;
    }
    while ((status = OCIStmtFetch2(stmthp, errhp, 1, OCI_FETCH_NEXT, 0, OCI_DEFAULT)) == OCI_SUCCESS) {
        std::cout << resultName << " " << resultAge << std::endl;
    }

    // 更新数据、删除数据和调用存储过程的操作与插入、查询类似,这里省略

    // 释放资源
    OCIHandleFree(stmthp, OCI_HTYPE_STMT);
    OCILogoff(svchp, errhp);
    OCIHandleFree(errhp, OCI_HTYPE_ERROR);
    OCIHandleFree(envhp, OCI_HTYPE_ENV);

    return 0;
}
```
**代码解释**
- `OCIEnvCreate`:初始化 OCI 环境。
- `OCIHandleAlloc`:分配各种 OCI 句柄。
- `OCILogon`:连接到 Oracle 数据库。
- `OCIStmtPrepare`:准备 SQL 语句。
- `OCIBindByName`:绑定 SQL 语句中的参数。
- `OCIStmtExecute`:执行 SQL 语句。
- `OCIDefineByPos`:定义查询结果集中的列。
- `OCIStmtFetch2`:获取查询结果集中的数据。
- `OCILogoff`:断开与 Oracle 数据库的连接。
- `OCIHandleFree`:释放 OCI 句柄占用的资源。

#### 2. 使用 ADO 连接 Oracle
和连接 MySQL 类似,在 Windows 环境下可以使用 ADO 连接 Oracle 数据库,示例代码如下:
```cpp
#include 
#import "C:\Program Files\Common Files\System\ado\msado15.dll" no_namespace rename("EOF", "EndOfFile")

int main() {
    CoInitialize(nullptr);
    _ConnectionPtr conn(__uuidof(Connection));
    _RecordsetPtr rs(__uuidof(Recordset));

    try {
        // 连接到 Oracle 数据库
        conn->Open("Provider=OraOLEDB.Oracle;Data Source=database_name;User ID=user;Password=password;", "", "", adConnectUnspecified);

        // 插入数据
        conn->Execute("INSERT INTO users (name, age) VALUES ('John', 25)", nullptr, adCmdText);

        // 查询数据
        rs = conn->Execute("SELECT * FROM users", nullptr, adCmdText);
        while (!rs->EndOfFile) {
            std::cout << (const char*)_bstr_t(rs->Fields->GetItem("name")->Value) << " "
                      << (long)rs->Fields->GetItem("age")->Value << std::endl;
            rs->MoveNext();
        }

        // 更新数据
        conn->Execute("UPDATE users SET age = 26 WHERE name = 'John'", nullptr, adCmdText);

        // 删除数据
        conn->Execute("DELETE FROM users WHERE name = 'John'", nullptr, adCmdText);

        // 调用存储过程
        conn->Execute("EXEC your_stored_procedure", nullptr, adCmdText);

        // 关闭连接
        rs->Close();
        conn->Close();
    } catch (_com_error& e) {
        std::cerr << "错误信息: " << (const char*)e.Description() << std::endl;
    }

    CoUninitialize();
    return 0;
}
```
**代码解释**和使用 ADO 连接 MySQL 类似,主要区别在于连接字符串的不同。