- 摘要:
- 知识点内容:
- 封装,继承,多态(重点是多态)
- 虚函数、虚函数表
- 内存管理
- 智能指针 shared_ptr/unique_ptr
- new/delete、malloc/free
- 指针、引用
- static
- allocator类
- size_type、size_t
- map
- multimap和struct使用
- map默认构造
- find、=
- ostream
- 文本查询程序
- 线程
- 1.普通多线程
- 2.互斥量 互斥锁
- 3.异步线程
- 遇到的问题记录:
- 搭建开发环境
- 一个类的对象不能访问该类的私有函数和成员
- 变量 "using" 不是类型名
- vscode中头文件的引用
- C++ 头文件理解
- 随机数生成
- 静态成员函数、成员初始化
- 变量、结构体等初始化问题
- linux中使用g++编译出现问题
- g++ xxx.h: 没有那个文件或目录
- "xxx"找不到标识符 (xxx为自定义的函数)
- 没有与参数列表匹配的构造函数
该文章用于个人参考学习,记录琐碎零散知识点,因参考引用较多,有未标明引用之处,望指出。
知识点内容: 封装,继承,多态(重点是多态)封装、继承、多态是C++、python、java等面向对象的编程语言的面向对象的三个主要特征。
https://blog.csdn.net/weixin_44138807/article/details/108102074
多态:https://blog.csdn.net/qq_53558968/article/details/116886784
C++的多态必须满足两个条件:
1 必须通过基类的指针或者引用调用虚函数
2 被调用的函数是虚函数,且必须完成对基类虚函数的重写
简单例程:
//实现多态实现; #include虚函数、虚函数表using namespace std; class Person { private: int age = 0;//默认年龄为0; public: virtual int get_age() { return age; } void print() { cout << "Person" << endl; } }; class Xiaoming :public Person { private: int age = 15; public: virtual int get_age() { return age; } void print() { cout << "Xiaoming" << endl; } }; class Xiaohong :public Xiaoming { private: int age = 13; public: virtual int get_age() { return age; } void print() { cout << "Xiaohong" << endl; } }; int main() { Person* p; Xiaoming xiaoming; p = &xiaoming;//基类指针指向子类 p->print(); //没有重写虚函数,基类 cout << p->get_age() << endl; //调用了重写虚函数,子类 p->print(); Xiaoming* x; Xiaohong xiaohong; x = &xiaohong; x->print(); cout << x->get_age() << endl; //调用了重写虚函数,子类 x->print(); }
https://blog.csdn.net/sinat_31275315/article/details/107963408
内存管理https://www.cnblogs.com/mrlsx/p/5411874.html
智能指针 shared_ptr/unique_ptrhttps://blog.csdn.net/shaosunrise/article/details/85228823
new/delete、malloc/freehttps://blog.csdn.net/qq_41709234/article/details/123265795
智能指针的使用
https://blog.csdn.net/daaikuaichuan/article/details/89061269
statichttps://blog.csdn.net/qq_42564908/article/details/106466409
allocator类为标准库类 ,定义在头文件memory中
作用:将内存分配和对象构造分离。
不使用alloctor构造:
std::string str{ "s" };//先创建一个string类型对象str
std::string* ps;//创建一个指向string类型的指针
/
}
}
};
QueryResult TextQuery::query(const std::string& sought) const {
static std::shared_ptr> nodata(new std::set);
auto loc = wm.find(sought);
if (loc == wm.end())
return QueryResult(sought, nodata, file);
else
return QueryResult(sought, loc->second, file);
}
int main() {
std::ifstream srcfile("text.txt");//同路径下的txt文件
TextQuery tq(srcfile);
QueryResult qr=tq.query("when");
print(std::cout, qr);
}
线程
参考:C++多线程详细讲解
引用: thread
#include1.普通多线程
使用
thread t1(fun1, a);// thread t1线程实例名称 fun1调用函数 a函数输入参数 t1.join(); // join对主线程进行阻塞;
join(), 当前线程暂停, 等待指定的线程执行结束后, 当前线程再继续。th1.join(),即该语句所在的线程(该语句写在main()函数里面,即主线程内部)暂停,等待指定线程(指定线程为th1)执行结束后,主线程再继续执行。
#include#include using namespace std; void fun1(int a) { cout << "f1" << endl; cout << "f1" << endl; cout << "f1" << endl; cout << "f1" << endl; } void fun2(int a) { cout << "f2" << endl; cout << "f2" << endl; cout << "f2" << endl; cout << "f2" << endl; } void fun3(int a) { cout << "f3" << endl; cout << "f3" << endl; cout << "f3" << endl; cout << "f3" << endl; } int main() { int a = 0; thread t1(fun1, a); thread t2(fun2, a); thread t3(fun3, a); t1.join(); t2.join(); t3.join(); //t1.detach(); //分离子线程 //t2.detach(); //分离子线程 //t3.detach(); //分离子线程 return 0; }
几次运行结果:
几个线程的顺序并不是调用顺序,并且没有上锁(lock),函数执行时也是互相抢占cpu执行权。
detach():
2.互斥量 互斥锁当使用detach()函数时,主调线程继续运行,被调线程驻留后台运行,主调线程无法再取得该被调线程的控制权。当主调线程结束时,由运行时库负责清理与被调线程相关的资源。
使用锁的原因,是一个进程内的多线程之间是数据共享的,各线程均可读写修改数据,可能导致出现错误。
如下为一个多线程的计数例程:
#include#include int gCount = 0; void thread_count() { int i = 0; int tmp = 0; while (i < 5000) { tmp = gCount; i++; printf("1:thread id:%u, count:%d n",std::this_thread::get_id(), gCount); gCount = tmp + 1; //printf("2:thread id:%u, count:%d n", std::this_thread::get_id(), gCount); } printf("error"); } int main() { std::thread t1(&thread_count); std::thread t2(&thread_count); t1.join(); t2.join(); }
但运行结果正常,多次运行也很少会出现线程间的数据干扰:
原因:现在的计算机计算速度太快了,线程之间的干扰不够严重。
需要加大线程之间的干扰力度,有一种比较重要且容易实现的手段:触发线程间切换 。
参考:Linux线程互斥与同步—互斥锁(mutex)的原理及其实现机制
知识科普
内核态:操作系统的模式,如果用户或某程序进入了内核态,那么它的权限就会不受约束,可以做任何事。操作系统向外提供系统调用接口方便进行用户态到内核态的转变。
用户态:一般用户的模式,用户或某程序在此状态下只能调用用户代码,权限受约束。当用户想调用系统接口,执行内核代码,就要从用户态变成内核态。触发线程间切换:在线程执行函数代码中多次进行系统调用,使其不断地从用户态到内核态。这样多个线程之间就会相互干扰。
一个简单的系统调用就是:printf
增加使用printf也就增加了 线程间切换,加大了线程干扰:
#include#include int gCount = 0; // pthread_mutex_t_lock = PTHREAD_MUTEX_INITIALIZER; void thread_count() { int i = 0; int tmp = 0; while (i < 5000) { tmp = gCount; i++; printf("1:thread id:%u, count:%d n",std::this_thread::get_id(), gCount); gCount = tmp + 1; printf("2:thread id:%u, count:%d n", std::this_thread::get_id(), gCount); } printf("error"); } int main() { std::thread t1(&thread_count); std::thread t2(&thread_count); t1.join(); t2.join(); }
运行:
结果显示是出现了数据的干扰。
因此要解决问题,归根结底就是要解决线程之间互相干扰的问题,从而保证临界资源的原子性。
使用互斥锁保证线程的数据安全。
2.1 lock() unlock()
需要引用并实例化
#includemutex m;//必须实例化m对象,不要理解为定义变量
void fun1(int a)
{
m.lock(); //lock
cout << "fun1" << endl;
cout << "a" << a << endl;
cout << "a+1" << a + 1 << endl;
m.unlock();//unlock需与lock配对
}
使用 lock() unlock() 对计数例程进行修改:
#include#include #include using namespace std; mutex m;//实例化m对象,不要理解为定义变量 int gCount = 0; void thread_count() { int i = 0; int tmp = 0; while (i < 5000) { m.lock();//在lock() unlock()之间的程序是不会被其他线程抢占资源的 tmp = gCount; i++; printf("1:thread id:%u, count:%d n",std::this_thread::get_id(), gCount); gCount = tmp + 1; printf("2:thread id:%u, count:%d n", std::this_thread::get_id(), gCount); m.unlock(); } //printf("error"); } int main() { std::thread t1(&thread_count); std::thread t2(&thread_count); t1.join(); t2.join(); }
运行结果:
运行结果没有出现错误,两个线程在读取修改gCount变量时都保证了线程互斥。
2.2 lock_guard():
lock_guard()
lock_guard():
其原理是:声明一个局部的lock_guard对象,在其构造函数中进行加锁,在其析构函数中进行解锁。最终的结果就是:创建即加锁,作用域结束自动解锁。从而使用lock_guard()就可以替代lock()与unlock()。
void thread_count() {
int i = 0;
int tmp = 0;
while (i < 5000)
{
lock_guard g1(m);//用此语句替换了m.lock();lock_guard传入一个参数时,该参数为互斥量,此时调用了lock_guard的构造函数,申请锁定m
tmp = gCount;
i++;
printf("1:thread id:%u, count:%d n",std::this_thread::get_id(), gCount);
gCount = tmp + 1;
printf("2:thread id:%u, count:%d n", std::this_thread::get_id(), gCount);
}
//printf("error");
}
运行结果:
2.3 unique_lock
unique_lock类似于lock_guard,只是unique_lock用法更加丰富,同时支持lock_guard()的原有功能。
使用lock_guard后不能手动lock()与手动unlock();使用unique_lock后可以手动lock()与手动unlock();
unique_lock的第二个参数,除了可以是adopt_lock,还可以是try_to_lock与defer_lock;
try_to_lock:尝试去锁定,得保证锁处于unlock的状态,然后尝试现在能不能获得锁;尝试用mutx的lock()去锁定这个mutex,但如果没有锁定成功,会立即返回,不会阻塞在那里。
defer_lock: 始化了一个没有加锁的mutex;
void thread_count() {
int i = 0;
int tmp = 0;
while (i < 5000)
{
unique_lock g1(m, defer_lock);//始化了一个没有加锁的mutex
g1.lock();
tmp = gCount;
i++;
printf("1:thread id:%u, count:%d n",std::this_thread::get_id(), gCount);
gCount = tmp + 1;
printf("2:thread id:%u, count:%d n", std::this_thread::get_id(), gCount);
g1.unlock();
}
//printf("error");
}
运行结果:
C++并发编程之线程异步 std::future知识点总结
包含头文件#include
async是一个函数模板,用来启动一个异步任务,它返回一个future类模板对象,future对象起到了占位的作用,刚实例化的future是没有储存值的,但在调用future对象的get()成员函数时,主线程会被阻塞直到异步线程执行结束,并把返回结果传递给future,即通过FutureObject.get()获取函数返回值。
#include#include #include #include #include #include using namespace std; double t1(const double a, const double b) { double c = a + b; Sleep(5000);//假设t1函数是个复杂的计算过程,需要消耗5秒 return c; } int main() { double a = 2.3; double b = 6.7; future fu = async(t1, a, b);//创建异步线程线程,并将线程的执行结果用fu占位; cout << "正在进行计算" << endl; cout << "计算结果马上就准备好,请您耐心等待" << endl; //用一个 do while 循环捕获fu的状态 ready状态说明可以获取异步线程运行结果 std::future_status status; do { status = fu.wait_for(std::chrono::seconds(1)); switch (status) { case std::future_status::deferred: std::cout << "std::future_status::deferred" << std::endl; break; case std::future_status::timeout: std::cout << "std::future_status::timeout" << std::endl; break; case std::future_status::ready: std::cout << "std::future_status::ready" << std::endl; break; default: std::cout << "none status" << std::endl; break; } } while (status != std::future_status::ready); cout << "计算结果:" << fu.get() << endl;//阻塞主线程,直至异步线程return //cout << "计算结果:" << fu.get() << endl;//取消该语句注释后运行会报错,因为future对象的get()方法只能调用一次。 return 0; }
总体来说异步线程时比较灵活的,个人认为相较于同步多线程来说使用更加方便高效,在工程中可以较多使用。
遇到的问题:
在类中实现异步线程时:
error:invalid use of non-static member function
解决方法:invalid use of non-static member function
遇到的问题记录: 搭建开发环境https://zhuanlan.zhihu.com/p/122956974
一个类的对象不能访问该类的私有函数和成员使用类的代码不可以访问带有 private 标号的成员。private 封装了类型的实现细节。只能在内部使用,规则的设立是为了防止数据泄漏,你想象要是这样你能访问不就是说在外面就能引用乃至修改了,这是不允许的。private在定义类的时候,其类型只能由类的成员函数使用,这样如果类中没有相应的函数,在外面是获取不到该值的。
要想访问private成员,需要在类中定义public成员函数,在该函数里访问private成员。
(通过基类指针只能访问派生类的成员变量,但是不能访问派生类的成员函数)
例:
#includeusing namespace std; class Student{ private: char *m_name; int m_age; float m_score; public: //声明构造函数 Student(char *name, int age, float score); //声明普通成员函数 void show(); }; //定义构造函数 Student::Student(char *name, int age, float score){ m_name = name; m_age = age; m_score = score; } //定义普通成员函数 void Student::show(){ cout< //创建对象时向构造函数传参 Student stu("小明", 15, 92.5f); stu.show(); //创建对象时向构造函数传参 Student *pstu = new Student("李华", 16, 96); pstu -> show(); return 0; }
m_name、m_age、m_score都是pravite成员,定义一个public成员函数show()即可访问pravite成员。
变量 “using” 不是类型名1.可能原因:
文件名后缀不正确,在test.c文件中使用using namespace std;即报错
将后缀改为.cpp文件错误消除
在一个工程(或功能包)文件中,引用头文件需要配置c_cpp_properties.json文件(除非将cpp文件和h文件都放在一个src下)。
在"includePath":[]中加入:
"${workspaceFolder}/xxx/include/**"
{workspaceFolder}为工作空间索引,xxx为工程文件(功能包名称),include为头文件存放文件夹,**为包含所有。
C++ 头文件理解个人简单理解其作用为
如:头文件 h1.h有声明函数fun1()。在src1.cpp中包含了头文件h1.h, 并且定义了两个函数fun1()、fun2(),那么src1.cpp中的fun1()就是对h1.h中的fun1()进行了定义。src2.cpp包含h1.h, 则其可直接使用fun1()函数,而函数fun2()对于src2.cpp而言是未定义的。
代码例程:
head01.h
#pragma once //保证头文件只被编译一次。 #include#include class student { public: std::string name; void ShowInfo() { std::cout << "The student's name is " << name << std::endl; } }; void PrintStudent(student s); //若不在头文件中声明PrintHello(),则在src02.cpp中会报错 void PrintHello();
src01.cpp
#include "head01.h"
void PrintStudent(student s){
s.ShowInfo();
}
void PrintHello() {
printf("hello_world");
}
src02.cpp
#include"head01.h"
int main()
{
student s1;
s1.name = "xiaoming";
PrintStudent(s1);
s1.ShowInfo();
PrintHello();
}
更多参考:https://zhuanlan.zhihu.com/p/387773355
随机数生成#include静态成员函数、成员初始化#include std::random_device rd; std::mt19937 gen(rd()); void main() { std::uniform_real_distribution<> x(-1, 1); std::uniform_real_distribution<> y(-1, 1); std::cout << x(gen) << " ," << y(gen); }
#includeusing namespace std; class A { public: static void f(A a); private: int x; }; void A::f(A a) { //静态成员函数只能引用属于该类的静态数据成员或静态成员函数。 // cout< A a;//对于函数体内的内置类型的对象,若没有初始化,其值是未定义的,即a的成员未定义; b.f(b);//正确 //a.f(a);//错误,a未初始化 a.f(A());//等价于 a.f(A::A()); 默认构造函数初始化 system("pause"); return 0; }
参考:https://www.cnblogs.com/coding-wtf/p/5786854.html
变量、结构体等初始化问题结构体:C++中,没有检测结构体中成员变量是否已经赋值的较好办法,所以如要保证安全性,可以在结构体加入默认构造函数避免结构体初始化但成员未赋值导致程序报错。或者将结构体在全局定义中初始化,结构体的成员自动初始化为0、空和false。
变量:对于变量,若定义为局部变量,初始化时未赋值,则其将保持未赋值状态,使用时需注意可能需要手动赋值;若定义为全局变量,全局变量会默认初始化为0 空 和 false。
容器:容器不管是全局变量还是局部变量都会自动初始化为0、空和false。
参考:https://www.cnblogs.com/ericling/p/11897771.html
linux中使用g++编译出现问题 g++ xxx.h: 没有那个文件或目录一个可能出现的情况是,已经在vscode中的c_cpp_properties.json设置了包含头文件的路径,但这只是设置了vscode的自动补齐,也即在vscode中不会报告头文件错误,但使用g++编译头文件仍会报错,因为g++默认头文件的路径中不包含你的头文件,解决办法1是编译时设置头文件路径:
g++ code01.cpp -o code01 -I /home/xia/mydemo01_ws/src/cpp_learnning/include
其中/home/xia/mydemo01_ws/src/cpp_learnning/include为code01.cpp中包含的头文件的完整路径。
"xxx"找不到标识符 (xxx为自定义的函数)这种情况可能的一种原因是函数定义顺序的问题,如:
需要自定义两个函数A和B,其中B使用了函数A,则需要先定义A,再定义B,否则会报错;
先定义B,而B使用了函数A,但是此时A未定义,则会报错“找不到表示符”。
1.可能的原因为 函数使用正确,如:
//调用 fun1 错误,报错:没有与参数列表匹配的构造函数 int fun1(a); //调用 fun1 正确 int fa = fun1(a);



