auto 及 初始化
//以下初始化没有区别
auto i=10;
auto j(10);
auto l{10};
//以下编译器不会提示类型丢失
int i=10.0;
int j(10.0);
// 编译器或会提示精度丢失,是一种更好的初始化方式
❌ int l{10.0};
//c++ 11 的列表初始化
auto l={10,20,30}
initializer_list<int> m={10,20,30};
c++ 引用类型 和 c# 不同
类的栈空间分配
#include <iostream>
using namespace std;
class Ca{
public:
Ca(int a): a(a){
}
int a;
};
void f1(Ca ca ){
ca.a+=1;
cout <<"函数内," << ca.a << endl;
}
int main() {
/* 和 c# 不同的是 Ca(100) 是分配在函数栈空间的 */
Ca ca = Ca(100);
//函数 f1 传递的是值拷贝,和 c# 是不相同的概念
f1(a); //函数内,101
cout <<ca.a << endl; //100
}
类的栈空间分配,及性能优化引用类型
#include <iostream>
using namespace std;
class Ca{
public:
Ca(int a): a(a){
}
int a;
};
void f1(Ca &ca ){
ca.a+=1;
cout <<"函数内," << ca.a << endl;
}
int main() {
/* 和 c# 不同的是 Ca(100) 是分配在函数栈空间的 ,如果构造函数不需要彻底参数或有默认值
都不需要显示调用构造函数 */
Ca ca = Ca(100);
//函数 f1 传递的引用类型,类似 c# 的 ref
f1(ca); //函数内,101
//因为函数内对 ca 进行了引用,在函数外可以反应修改的值
cout <<ca.a << endl; //101
}
类的堆空间分配
#include <iostream>
using namespace std;
class Ca{
public:
Ca(int a): a(a){
}
int a;
};
void f1(Ca* ca ){
// 👍 ca->a 等同于 (*ca).a
ca->a+=1;
cout <<"函数内," << ca->a << endl;
}
int main() {
/* 通过指针, 指向堆空间,堆空间通过 new 分配 */
Ca* ca =new Ca(100);
//函数 f1 传递的指针类型,和c# 的 引用类型一致
f1(ca); //函数内,101
//通过指针访问的是和函数内相同的堆空间
cout << ca->a << endl; //101
//堆空间,通过 new 分配记得要释放
delete ca;
}
malloc、free
- new / new[]:完成两件事,先底层调用 malloc 分配了内存,然后调用构造函数(创建对象)。
- delete/delete[]:也完成两件事,先调用析构函数(清理资源),然后底层调用 free 释放空间。
- new 在申请内存时会自动计算所需字节数,而 malloc 则需我们自己输入申请内存空间的字节数。
//申请的时候必须要指定申请的的大小
char *str = (char*) malloc(100);
free(str);
str = nullptr;
引用类型丢失
class Ca{
public:
Ca(string &message): msg(message){
}
//引用外部的变量
string &msg;
void print(){
cout << ((msg != "") ? "输出了":"没有值") << endl;
}
};
/* 此函数执行结束 堆栈丢失 msg 变量不存在了 ,ca 对象引用了不存在的 对象 msg */
Ca* createCa(){
string msg="msg";
return new Ca(msg);
}
int main() {
auto ca= createCa();
ca->print(); // ❌ 没有值
delete ca;
return 0;
}
对象切割及静态联编导致的多态失效
class Ca {
public:
virtual void f() {
cout << "Ca::f" << endl;
}
};
//在内存上 cb 拷贝了 ca 的部分
class Cb : public Ca {
public:
void f() override {
cout << "Cb::f" << endl;
}
};
int main() {
Ca a=Cb(); a 只能找到 自己类型的部分,因为不是指针,即使使用 virtual 标注方法
a.f(); // ❌ "Ca::f" 对象切割导致的多态丢失
return 0;
}
静态联编
class Ca {
public:
void f() {
cout << "Ca::f" << endl;
}
};
class Cb : public Ca {
public:
void f() {
cout << "Cb::f" << endl;
}
};
int main() {
//因为没有虚函数,在编译的时候使用了静态联编 ,a 指针指向的内容在编译的时候就确定了是 Ca 类型的
//而不是 Cb 类型的,所以调用的是 Ca 类型的 f 函数,编译器提前就确定了调用的是 Ca 类型的 f 函数
//而不管实际运行的是 Cb 类型的 f 函数
Ca* a = new Cb();
a->f(); //❌ "Ca::f" 对象切割导致的多态丢失
delete a;
return 0;
}
动态联编
动态联编,c++是编译成机器吗的,为什么能做到动态的确定虚函数指向动态对象呢,通过虚函数表实现的
class Ca {
public:
virtual void f() {
cout << "Ca::f" << endl;
}
};
class Cb : public Ca {
public:
void f() override {
cout << "Cb::f" << endl;
}
};
int main() {
//因为 a 的 f 方法是虚函数使用动态联编,在运行的时候才确定类型
Ca* a = new Cb();
a->f(); 👌 "Cb::f" 对象切割导致的多态丢失
delete a;
return 0;
}
智能指针
class Ca {
public:
Ca() {
cout << "Ca constructor" << endl;
}
~Ca() {
cout << "Ca destructor" << endl;
}
};
共享指针 shared_ptr
标准库为我们提供的共享指针 shared_ptr 也可以自动释放关联的堆空间。共享指针可以被多个使用者 持有,共享指针内部会记录持有者的个数(被引用的次数),随着应用程序的运行,持有者会一个个诞 生,一个个消亡,当共享指针不再存在持有者时(引用计数为 0 时),共享指针所关联的堆内存被释 放
#include <memory>;
int main() {
{
//shared_ptr make_shared 共享指针
shared_ptr<Ca> myClass1 = make_shared<Ca>();
cout << myClass1.use_count()<< endl; // 1
// myClass1 原来关联的共享指针内部会记录持有者的个数为零,进行堆内存释放
// myClass1.reset(); 也可以手动是否关联的内存
myClass1 = make_shared<Ca>();
cout << myClass1.use_count()<< endl;
}
return 0;
}
Ca constructor
1
Ca constructor
Ca destructor
1
Ca destructor
使用指针的话需要手动的是否内存
Ca* ca{ new Ca() };
delete ca;
ca = new Ca();
delete ca;
独占指针 unique_ptr
int main() {
{
//定义独占指针
unique_ptr<Ca> myClass1 = make_unique<Ca>();
//不允许来个独占指针指向相同的内存空间
//unique_ptr<Ca> myClass2 = myClass1;
//当独占指针指向别的内存空间,原来的内存空间会释放
myClass1= make_unique<Ca>();
}
return 0;
}
模板
函数模板
template <typename T>
inline T const& Max (T const& a, T const& b)
{
return a < b ? b : a;
}
类模板
template <class T>
class Stack {
private:
vector<T> elems; // 向量,用来保存元素
public:
void push(T const&); // 入栈
void pop(); // 出栈
T top() const; // 返回栈顶元素
bool empty() const{ // 如果为空则返回真
return elems.empty();
}
};
指针
int main() {
int* a= new int(10);
int *b = new int(20);
// 等同于 int* c = nullptr; 或 int* c = 0; 不指向任何内存地址
int* c = NULL ;
//野指针,指向的是随机的内存地址
int* d;
cout<<"a=" << *a << endl; // a=10
cout<<"b=" << *b << endl; // b=20
//不能直接访问 空指针,会引发错误
if( c!= nullptr){
cout<<"c=" << *c << endl;
}
cout<<"d=" << *d << endl; //输出随机的值
return 0;
}
指针在不同的操作系统,指针本身的字节长度也不一样,32 操作系统是 4 字节 64 位操作系统是 8 字 节
int main() {
int* a= new int(10);
double* b= new double (10);
cout << "a: " << sizeof(a) << endl;
cout << "b: " << sizeof(b) << endl;
}
常量指针 和指针常量
* 后面的 const 是用来修饰指针的, * 前面的 const 是用来修饰变量的
int main() {
//常量指针
const int* const p1=new int(10);
// 不可以修改指针的指向
//p1 = new int(20);
// 指针指向常量,不可以继续修改
const int* p2=new int(10);
//*p2 =30;
}
数组
int main() {
//定义一个 int 数组指向数组
int* a= new int[3]{10,20,30};
//这里如果进行越界范围,不会报错
for (int i = 0; i < 3; ++i) {
cout << *a << endl;
a++;
}
//删除数组堆空间
delete [] a;
return 0;
}
函数
#include <functional>
void f(int a) {
std::cout << " f action:" << a << std::endl;
}
// 等同于 void (*callback)(int a) 或 std::function<void(int)> callback
// std::function<void(int)> 这种方式更好
void f1( void callback(int a) ) {
callback(1);
}
int main() {
//等同于 f1(&f)
f1(f);
return 0;
}
lambda
#include <functional>
#include <iostream>
void f2(std::function<int(int)> callback) {
auto item= callback(1);
std::cout << " f2 action:" << item << std::endl;
}
int main() {
//等同于 f1(&f)
auto item=100;
//lambda 表达式 通过 [&item] 捕获外部的变量
// 参数类型如果能进行自动转换的,就可以匹配
auto tmp=[&item](int a)->bool {
std::cout << " f action:" << a <<" capture:"<< item << std::endl;
return false;
};
f2(tmp);
return 0;
}
union
union 存在内存对齐。在 64 位处理器中,内存对齐的单位一般是 8 字节 union 用成员的最大地址空 间表示 union 的 内存空间大小,但是一个 union 对象只能是唯一的类型
//通过 union 做到一块内存空间,既可以代码 ip4 也可以表示 ip6 的内存空间
union MyIp{
char ipv4[4];
char ipv6[16];
};
int main() {
cout << sizeof(MyIp) << endl; //16
MyIp ip={.ipv4={'1','2','3','4'}};
cout << ip.ipv6 << endl; //1234
cout << ip.ipv4 << endl; //1234
return 0;
}
静态变量
类中的静态变量
class Ca{
public:
//类中的静态变量不能在构造函数里进行初始话,也不能在定义的时候进行初始化
static int A;
Ca(){
}
};
//类中的静态变量只能这样进行初始化,因为静态变量有静态存储区进行保存
int Ca::A = 100;
int main() {
auto ca =new Ca();
cout << ca->A << endl; //100
ca->A= 200;
cout << ca->A << endl; //200
Ca::A = 300;
cout << Ca::A << endl; //300
cout << ca->A << endl; //300
delete ca;
cout << "---------------------" << endl;
// delete 删除了 ca 对象的内存,但是静态存储区是不受影响的,静态存储区的生命周期是程序
cout << Ca::A << endl; //300
//这里的 ca 指针本身还在,只是指针指向的内存区域不存在的,但是静态变量存在
cout << ca->A << endl; //300
return 0;
}
静态变量不受 {} 作用域影响,生命周期直到程序运行结束
class Ca{
public:
Ca() {
cout << "Ca" << endl;
}
~Ca() {
cout << "~Ca" << endl;
}
};
int main() {
{
static Ca ca;
}
cout << "main" << endl;
return 0;
}
Ca
main
~Ca
友元
如下代码通过 public 函数 f 获取 private 的变量 a 的值
友元函数
class Ca{
public:
// f 函数的 const 表示此函数内不能修改此类内的任何成员值
int f() const{
return a;
};
private:
int a=200;
};
int main() {
auto ca=Ca();
int i= ca.f();
cout << i << endl;
return 0;
}
通过友元函数实现访问 友元函数只是一个普通函数,并不是该类的类成员函数,它可以在任何地方调 用, 通过对象不能使用友元函数
class Ca{
public:
private:
int a=200;
//定义友元函数
friend int f(Ca& ca);
};
//友元函数的实现
int f(Ca& ca){
//可以访问任何对象的成员,不受访问权限限制
return ca.a;
}
int main() {
auto ca=Ca();
int i= f(ca);
cout << i << endl;
return 0;
}
友元类
- 友元关系没有继承性 假如类 B 是类 A 的友元,类 C 继承于类 A,那么友元类 B 是没办法直接访 问类 C 的私有或保护成员。
- 友元关系没有传递性 假如类 B 是类 A 的友元,类 C 是类 B 的友元,那么友元类 C 是没办法直 接访问类 A 的私有或保护成员,也就是不存在“友元的友元”这种关系。
class Ca{
public:
private:
int a=200;
//定义可以把保护成员开发给的类
friend class Cb ;
};
class Cb{
public:
//可以访问受保护的成员
int getA(Ca &ca){
return ca.a;
}
};
int main() {
auto cb=Cb();
auto ca=Ca();
int i=cb.getA(ca);
cout << i << endl;
return 0;
}
decltype
decltype (expression)
这里的括号是必不可少的,decltype 的作用是“查询表达式的类型”,因此,上面语句的效果是,返回 expression 表达式的类型。注意,decltype 仅仅“查询”表达式的类型,并不会对表达式进行“求值”。
int main() {
auto a=100;
auto b=100.1;
// decltype 推动出类型是 float
decltype(a+b) c=200.1;
cout << c << endl;
return 0;
}
与 using/typedef 合用,用于定义类型
int main() {
auto a=100;
auto b=100.1;
//定义类型
using f =decltype(a+b);
//定义类型
typedef decltype(a+b) nf;
f d=100.1;
nf e=100.1;
cout << d << endl;
cout << e << endl;
return 0;
}
模板里定义返回的类型
template <typename T>
auto multiply(T x, T y)->decltype(x*y)
{
return x*y;
}
线程
五种创建线程的方式
- 函数指针
- Lambda 函数吧
- Functor(仿函数)
- 非静态成员函数
- 静态成员函数
#include <thread>
/* 函数指针 */
void fun(int x) {
while (x-- > 0) {
cout << x << endl;
}
};
int main() {
/* Lambda */
auto fun= [](int x){
while (x-- > 0) {
cout <<"lambda "<< x << endl;
}
};
std::thread t1(fun,10);
t1.join();
return 0;
}
防函数操作符重载
#include <thread>
class Ca {
public:
Ca(){
std::cout << "Ca constructor" << std::endl;
}
void operator()(int x) {
std::cout << "operator action" << std::endl;
while (x-- > 0) {
cout << "lambda " << x << endl;
}
}
};
int main() {
auto ca= Ca();
std::thread t1(ca, 10);
t1.join();
return 0;
}
非静态成员
#include <thread>
class Ca {
public:
Ca(){
std::cout << "Ca constructor" << std::endl;
}
void fun(int x) {
while (x-- > 0) {
cout << x << endl;
}
}
};
int main() {
auto ca= Ca();
std::thread t1(&Ca::fun, ca, 10);
t1.join();
return 0;
}
枚举
//限定作用域的枚举类型
enum class open_modes { input, output, append };
open_modes i=open_modes::input
//不限定作用域的枚举类型
enum color { red, yellow, green };
color j=red
stl
Alexander Stepanov(后被誉为 STL 标准模板库之父,后简称 Stepanov),1950 年出生与前苏联的 莫斯科,他曾在莫斯科大学研究数学,此后一直致力于计算机语言和泛型库研究。
STL 是 “Standard Template Library” 的缩写,中文译为 “标准模板库”。STL 是一些容器的集合,例 如 list、vector、set、map 等。同时,STL 也是算法和其它一些组件的集合。STL 的目的是标准化组 件,这样就可以使用现成的组件,不用重新开发。 STL 就是借助模板把常用的数据结构及其算法都实 现了一遍,并且做到了数据结构和算法的分离。例如,vector 的底层为顺序表(数组),list 的底层 为双向链表,deque 的底层为循环队列,set 的底层为红黑树, hash_set 的底层为哈希表。
在掌握了基础语法后,工作中要要使用的是这些开发库 C++ STL 构成 C++ STL 包含三大部分:容器 (Containers)、算法(Algorithms)、迭代器(iterators)。
- 容器是用来管理某一类对象的集合。C++ 提供了各种不同类型的容器,比如 deque、list、vector、map 等。| 2.算法 作用于容器,它们提供了执行各种操作的方式,包括对 容器内容执行初始化、排序、搜索和转换等操作。
- 迭代器用于遍历对象集合的元素。这些集合可能是容器,也可能是容器的子集 在 C++ 标准中,STL 被组织为下面的 13 个头文件
<algorithm>
<deque>
<functional>
<iterator>
<vector>
<list>
<map>
<memory>
<numeric>
<queue>
<set>
<stack>
<utility>