new用法总结
new int;//开辟一个存放整数的存储空间,返回一个指向该存储空间的地址(即指针)
new int(100);//开辟一个存放整数的空间,并指定该整数的初值为100,返回一个指向该存储空间的地址
new char[10];//开辟一个存放字符数组(包括10个元素)的空间,返回首元素的地址
new int[5][4];//开辟一个存放二维整型数组(大小为5*4)的空间,返回首元素的地址
float *p=new float (3.14159);//开辟一个存放单精度数的空间,并指定该实数的初值为//3.14159,将返回的该空间的地址赋给指针变量p
友元函数
友元函数和普通函数区别:它能够操作类中的私有成员;
友元函数是指某些虽然不是类成员却能够访问类的所有成员的函数。类授予它的友元特别的访问权。
类外定义。
inline 内联函数
该函数的使用目的是为了节省调用函数开辟的栈空间,即直接调用函数。一般使用在函数体比较简单的情况下,如果函数体比较复杂,函数的执行本身开辟的栈空间要大于调用函数开辟的栈空间,调用inline已无意义。且inline使用在函数定义时,声明前使用无意义。
template声明模板
template <class T>/template <typename T>
关于继承
C++类成员如果不做说明,默认是private。
不管什么继承,子类都可以访问基类的protect和public成员。private继承只是表明继承关系是private的,通俗的说,private继承是指“外界”不知道那个类还有个父类,而不是子类自己也不知道他有个父类。就像私有成员一样,外界不知道类有私有成员,不代表类自己也不了解自己的私有成员。
Base objBase; // 基类对象
Derived objDer; // 派生类对象
Base* ptr = &objBase; // ptr 指向 Base 对象
ptr = &objDer; // ptr 执行 Derived 对象的 Base 部分
Base &ref = objDer; // ref 绑定到 Derived 对象的 Base 部分
初步了解C++继承,point:
1.子类public继承父类后,不能直接访问父类的private变量,但可以通过public提供的接口(如果有)来访问。
2.带有继承以及组合的类的构造函数顺序是: 父类的构造函数->组合成员对象的构造函数 ->子类自己的构造函数;
3.虚继承:理解:比如现在有一个沙发床,它既有床的属性又有沙发的属性,它们都有长宽高的属性,但是我们却只需要知道它的一个状态的属性。
4.private, public, protected 访问标号的访问范围。
private:只能由(1)该类中的函数、(2)其友元函数访问。 不能被任何其他访问,该类的对象也不能访问。
protected:可以被(1)该类中的函数、(2)子类的函数、以及(3)其友元函数访问。但不能被该类的对象访问。
public:可以被(1)该类中的函数、(2)子类的函数、(3)其友元函数访问,也可以由(4)该类的对象访问。
Lambda表达式:https://www.cnblogs.com/DswCnblog/p/5629165.html
[capture list] (params list) mutable exception-> return type { function body }
各项具体含义如下
- capture list:捕获外部变量列表
- params list:形参列表
- mutable指示符:用来说明是否可以修改捕获的变量
- exception:异常设定
- return type:返回类型
- function body:函数体
此外,我们还可以省略其中的某些成分来声明“不完整”的Lambda表达式,常见的有以下几种:
序号 | 格式 |
---|---|
1 | [capture list] (params list) -> return type {function body} |
2 | [capture list] (params list) {function body} |
3 | [capture list] {function body} |
其中:
- 格式1声明了const类型的表达式,这种类型的表达式不能修改捕获列表中的值。
- 格式2省略了返回值类型,但编译器可以根据以下规则推断出Lambda表达式的返回类型: (1):如果function body中存在return语句,则该Lambda表达式的返回类型由return语句的返回类型确定; (2):如果function body中没有return语句,则返回值为void类型。
-
格式3中省略了参数列表,类似普通函数中的无参函数。
STL模板类map和pair
可以简单理解为:map可以当做一个容器(装载具有一定格式的数据);pair可以理解为元素(放入到容器的的一个个个体),发现pair并没有单独行动的典型用法,正常都是配合map来使用(即把pair这个元素插入到map这个容器里面)
map<uint32_t, string> temp;
4 1. temp[1] = "template";
5 2.temp.insert(pair<uint32_t, string>(1, "template"));
6 3.temp.insert(make_pair(1, "template"));
7
8 pair实质上是一个结构体,其主要的两个成员变量是first和second,因此有了
for(const auto& i : temp) {
9 cout << "first = " << i.first; // i 也就是一个pair;
10 cout << "second = " << i.second;
11 }
12 pair需要指定构造的类型,make_pair可以隐式转换,即将1 转成uint32_t, template转成string类型。
关于子类在继承父类的构造函数时的一些情况:来源:;https://blog.csdn.net/koudan567/article/details/49805735
1. 子类没有定义构造方法,则调用父类的无参构造方法;
2.如果子类定义了构造方法,不论是无参数的还是带参数的,在创建子类对象的时候,首先执行父类的无参构造方法,然后执行自己的构造方法;
3.在创建子类对象的时候,如果子类的构造函数没有显示的调用父类的构造函数,则会调用父类的默认无参构造函数。
此处父类的默认无参构造函数是当用户没有定义的时候,编译器默认生成的构造函数。
4.在创建子类对象时,如果子类的构造函数没有显示调用父类的构造函数,自己提供了无参构造函数,则会调用父类自己的无参构造函数。 函数实现同第二种。
5.在创建子类对象时,如果子类的构造函数没有显示的调用父类的构造函数只定义了自己的有参构造函数,则会出错。(如果父类只有有参构造方法,则子类必须显示的调用此有参构造方法)
关于在类中声明自身类型成员变量问题
C++定义类时,不能在类中声明自身类型的成员变量,这是由于当实例化一个类的对象时,编译器会根据类的定义来分配相应的存储空间 。也就是说,在创建对象前,一定要有完整的类定义,这样编译器才能正确的计算所需空间。即以下情况会报错:
class Single
{
private:
Single single;
.......
}
下面情况不会报错:
class Single
{
private:
static Single single;
.......
}
在类定义时,类中声明指向自身类型的指针或引用作为内部成员没有问题,由于指针和引用所占存储空间大小与类型无关,所以编译器可以计算分配空间,所以正确。 下面一段代码,则编译通过:
class Single
{
private:
Single *single;
.......
}
大括号{ }内声明的变量,作用域只属于大括号内。
纯虚函数和虚函数
1.纯虚函数.表示为: class A: { virtual void fun() = 0; ...}在父类中不必实现函数体,但是子类中必须对其进行实现,否则编译报错。拥有纯虚函数的类是不能用来实例化的,只有子类实现了纯虚函数才有可能被实例化。
虚函数 必须在父亲类中进行实现(意为必须有{ },否则链接会报错)。子类中如果有实现,子类调用该函数时,则调用子类的实现方法;否则调用父类的实现方法。
一个对象用父类声明,用子类实例化
这个实例是子类的,但是因为声明时是用父类声明的,所以你用正常的办法访问不到子类自己的成员,只能访问到从父类继承来的成员。
在子类中用override重写父类中用virtual申明的虚方法时 ,实例化父类调用该方法,执行时调用的是子类中重写的方法;
类定义的先后次序问题
A类定义在B类的后面,且B类的一个属性成员是A类对象的指针或者引用,则在B类的定义之前应该先对A类进行一个声明。
class A;
class B { A a; }
class A { ... }
智能指针shared_ptr和make_shared, weak_ptr,
shared_ptr<int> aa = make_shared<int>();
weak_ptr 是一种不控制对象生命周期的智能指针, 它指向一个 shared_ptr 管理的对象. 进行该对象的内存管理的是那个强引用的 shared_ptr. weak_ptr只是提供了对管理对象的一个访问手段. weak_ptr 设计的目的是为配合 shared_ptr 而引入的一种智能指针来协助 shared_ptr 工作, 它只可以从一个 shared_ptr 或另一个 weak_ptr 对象构造, 它的构造和析构不会引起引用记数的增加或减少.
std::shared_ptr<int> sp(new int(10));
std::weak_ptr<int> wp(sp);
wp = sp;
printf("%d\n", wp.use_count()); // 1
wp.reset();
printf("%d\n", wp); // 0
智能指针和普通指针转换
std::shared_ptr<int> ptr_test2 = std::make_shared<int>();
int* pTest2 = ptr_test2.get(); //shared_ptr转普通指针
int* pTest = new test();
std::shared_ptr<int> ptr_test = std::shared_ptr<int>(pTest); //普通指针转shared_ptr
多线程同步之信号量
信号量Sem表示可用资源的数量,当Sem > 0,线程可以使用某资源,进入临界区,则Sem - 1进行P操作,可用资源数减少1,退出临界区后,Sem +1进行V操作,可用资源数加1. 可用资源数表示多个线程/进程可以同时使用该资源。一台电脑有4个打印机,则Sem最大为4,最多允许4个打印线程同时工作。
分析:这里我们要让三个线程按顺序依次打印ABC,即当一个线程在打印A时,另外两个线程不能够打印,而且打印完A以后,接下来必须打印B。我们使用三个信号量分别控制A B C的打印,打印完A就释放B的信号量,打印完B就释放C的信号量,打印完C就释放A的信号量。具体见下面代码:
#include<string>
#include<iostream>
#include<process.h>
#include<windows.h>
using namespace std;
HANDLE hsem1,hsem2,hsem3; //hsem1:线程A等待的信号量,hsem2:线程2等待的信号量......
//线程绑定的函数返回值和参数是确定的,而且一定要__stdcall
unsigned __stdcall threadFunA(void *)
{
for(int i = 0; i < 10; i++){
WaitForSingleObject(hsem1, INFINITE);//等待信号量
cout<<"A";
ReleaseSemaphore(hsem2, 1, NULL);//释放信号量
}
return 1;
}
unsigned __stdcall threadFunB(void *)
{
for(int i = 0; i < 10; i++){
WaitForSingleObject(hsem2, INFINITE);//等待信号量
cout<<"B";
ReleaseSemaphore(hsem3, 1, NULL);//释放信号量
}
return 2;
}
unsigned __stdcall threadFunC(void *)
{
for(int i = 0; i < 10; i++){
WaitForSingleObject(hsem3, INFINITE);//等待信号量
cout<<"C";
ReleaseSemaphore(hsem1, 1, NULL);//释放信号量
}
return 3;
}
int main()
{
//创建信号量
hsem1 = CreateSemaphore(NULL, 1, 1, NULL);
hsem2 = CreateSemaphore(NULL, 0, 1, NULL);
hsem3 = CreateSemaphore(NULL, 0, 1, NULL);
HANDLE hth1, hth2, hth3;
//创建线程
hth1 = (HANDLE)_beginthreadex(NULL, 0, threadFunA, NULL, 0, NULL);
hth2 = (HANDLE)_beginthreadex(NULL, 0, threadFunB, NULL, 0, NULL);
hth3 = (HANDLE)_beginthreadex(NULL, 0, threadFunC, NULL, 0, NULL);
//等待子线程结束
WaitForSingleObject(hth1, INFINITE);
WaitForSingleObject(hth2, INFINITE);
WaitForSingleObject(hth3, INFINITE);
//一定要记得关闭线程句柄
CloseHandle(hth1);
CloseHandle(hth2);
CloseHandle(hth3);
CloseHandle(hsem1);
CloseHandle(hsem2);
CloseHandle(hsem3);
}
std::thread
sleep方法执行后,处于阻塞状态,线程会交出cpu,对该对象的锁没有交出,其他线程也无法访问该对象。
线程执行sleep方法,主线程不会像join方法那样等待子线程执行完再执行,mian线程会和子线程同时执行。
yeild方法
public static void yield()暂停当前正在执行的线程对象,并执行其他线程。
调用yield方法会让当前线程交出CPU权限,让CPU去执行其他的线程。它跟sleep方法类似,同样不会释放锁。但是yield不能控制具体的交出CPU的时间,另外,yield方法只能让拥有相同优先级的线程有获取CPU执行时间的机会。注意,调用yield方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只需要等待重新获取CPU执行时间,这一点是和sleep方法不一样的。
std::this_thread::yield() 的目的是避免一个线程(that should be used in a case where you are in a busy waiting state)频繁与其他线程争抢CPU时间片, 从而导致多线程处理性能下降.
区别和联系:
它们的相同点在于:
1. 都会暂缓执行当前线程;
2. 如果已经持有锁,那么在等待过程中都不会释放锁;
不同点在于:
1. Thread.sleep()可以精确指定休眠的时间,而Thread.yield()依赖于CPU的时间片划分,在我的电脑上大约为20微秒;
2. Thread.sleep()会抛出中断异常,且能被中断,而Thread.yield()不可以;
当前线程可以调用其他线程的join()方法,调用后,就会进入阻塞状态(该状态既停止当前线程,但并不释放所占有的资源)。
1、当线程刚进入可运行状态(即就绪状态),发现将要调用的资源被synchronized(同步),获取不到锁标记,将会立即进入锁池状态,等待获取锁标记(这时的锁池里也许已经有了其他线程在等待获取锁标记,这时它们处于队列状态,即先到先得)一旦线程获得锁标记后,就转入可运行状态,等待os分配CPU时间片;
2、当一个线程执行到wait()方法时,它就进入到一个和该对象相关的等待池中,同时失去了对象的锁。它被一个notify()方法唤醒时,等待池中的线程就被放到锁池中。该线程从锁池中获得锁,然后回到wait()前的中断现场。
3、当线程调用wait()方法后会进入等待队列(进入这个状态会释放所占有的所有资源,与阻塞状态不同),进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用notify()或notifyAll()方法才能被唤醒(wait(1000)时可以自动唤醒)(由于notify()只是唤醒一个线程。但我们由于不能确定具体唤醒的是哪一个线程,也许我们需要唤醒的线程不能够唤醒,)因此在实际使用时,一般都用notifyAll(),方法唤醒所有线程(),当线程被唤醒后会进入锁池,等待获得锁标记
同步/异步,阻塞、非阻塞
同步和异步关注的是消息通信机制 (synchronous communication/ asynchronous communication)
所谓同步,就是在发出一个*调用*时,在没有得到结果之前,该*调用*就不返回。但是一旦调用返回,就得到返回值了。
换句话说,就是由*调用者*主动等待这个*调用*的结果。
而异步则是相反,*调用*在发出之后,这个调用就直接返回了,所以没有返回结果。换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果。而是在*调用*发出后,*被调用者*通过状态、通知来通知调用者,或通过回调函数处理这个调用。
阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态.
阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。
非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。
同步的定义看起来跟阻塞很像,但是同步跟阻塞是两个概念,同步调用的时候,线程不一定阻塞(由于线程还在执行当前的代码段,所以不一定阻塞),调用虽然没返回,但它还是在运行状态中的,CPU很可能还在执行这段代码,而阻塞的话,它就肯定不在CPU中跑这个代码了。这就是同步和阻塞的区别。同步是肯定可以在,阻塞是肯定不在。
举个通俗的例子:
你打电话问书店老板有没有《分布式系统》这本书,如果是同步通信机制,书店老板会说,你稍等,”我查一下",然后开始查啊查,等查好了(可能是5秒,也可能是一天)告诉你结果(返回结果)。
而异步通信机制,书店老板直接告诉你我查一下啊,查好了打电话给你,然后直接挂电话了(不返回结果)。然后查好了,他会主动打电话给你。在这里老板通过“回电”这种方式来回调。
异步和非阻塞的定义比较像,两者的区别是异步是说调用的时候结果不会马上返回,线程可能被阻塞起来,也可能不阻塞,两者没关系。非阻塞是说调用的时候,线程肯定不会进入阻塞状态。
还是上面的例子,
你打电话问书店老板有没有《分布式系统》这本书,你如果是阻塞式调用,你会一直把自己“挂起”,直到得到这本书有没有的结果,如果是非阻塞式调用,你不管老板有没有告诉你,你自己先一边去玩了, 当然你也要偶尔过几分钟check一下老板有没有返回结果。
同步、异步:
概念:消息的通知机制
解释:涉及到IO通知机制;所谓同步,就是发起调用后,被调用者处理消息,必须等处理完才直接返回结果,没处理完之前是不返回的,调用者主动等待结果;所谓异步,就是发起调用后,被调用者直接返回,但是并没有返回结果,等处理完消息后,通过状态、通知或者回调函数来通知调用者,调用者被动接收结果。
阻塞、非阻塞:
概念:程序等待调用结果时的状态
解释:涉及到CPU线程调度;所谓阻塞,就是调用结果返回之前,该执行线程会被挂起,不释放CPU执行权,线程不能做其它事情,只能等待,只有等到调用结果返回了,才能接着往下执行;所谓非阻塞,就是在没有获取调用结果时,不是一直等待,线程可以往下执行,如果是同步的,通过轮询的方式检查有没有调用结果返回,如果是异步的,会通知回调。
经典故事案例:
人物:老张
道具:普通水壶(水烧开不响);响水壶(水烧开发出响声)
案例:
1、同步阻塞:
老张在厨房用普通水壶烧水,一直在厨房等着(阻塞),盯到水烧开(同步);
2、异步阻塞:
老张在厨房用响水壶烧水,一直在厨房中等着(阻塞),直到水壶发出响声(异步),老张知道水烧开了;
3、同步非阻塞:
老张在厨房用普通水壶烧水,在烧水过程中,就到客厅去看电视(非阻塞),然后时不时去厨房看看水烧开了没 (轮询检查同步结果);
4、异步非阻塞:
老张在厨房用响水壶烧水,在烧水过程中,就到客厅去看电视(非阻塞),当水壶发出响声(异步),老张就知道 水烧开了。
unique_lock和lock_guard
在与条件变量结合使用的时候,使用unique_lock,因为lock_guard和unique_lock是强关联的,在使用中不允许释放,而与条件变量的使用中,条件不满足的情况下需要释放锁