C++ Primer学习二

C++ Primer学习二

拷贝控制

rule of three:定义了其中一个,剩下的几个都要定义

  1. copy constructor
  2. copy assign operator
  3. destructor

rule of five:定义了其中一个,剩下的几个都要定义

  1. copy constructor
  2. copy assign operator
  3. destructor
  4. move constructor
  5. move assign operator

深拷贝与浅拷贝
a. 浅拷贝:简单的赋值拷贝操作。浅拷贝带来的问题就是堆区的内存重复释放。要利用深拷贝解决。
b. 深拷贝:在堆区重新申请空间,进行拷贝操作。
在这里插入图片描述
编译器会默认生成拷贝构造函数,但是如果类里面有动态申请的内存空间,那么一定要自定义拷贝构造函数,用深拷贝去解决浅拷贝带来的问题,并且拷贝构造函数/类中重载赋值运算符一定要使用索引,因为不用索引的话,去调用对象的时候会首先调用一次拷贝构造,造成死循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
类似实现一种资源管理的方式,应该从哪里考虑:
构造函数初始化,拷贝构造++,析构函数--,如果减到0,直接delete掉。
class Foo{
public:
int* resource_;
size_t* count_;

Foo(int resource):resource_(new int (resource)){
cout<<"in constructor"<<endl;
this->count_ = new size_t(1);
}
Foo(const Foo& foo){
this->resource_ = foo.resource_;
this->count_ = foo.count_;
*(this->count_)+=1;
cout<<"update this->count " <<*(this->count_)<<endl;
}
~Foo(){
*(this->count_) -=1;
if(*(this->count_) ==0){
cout<<"do destructor"<<endl;
delete this->count_;
delete this->resource_;
}
}
};

int main(){
Foo foo1(1);
Foo foo2(foo1);
Foo foo3(foo2);
Foo foo4(foo3);
cout<<"how many count " <<*(foo2.count_)<<endl;
return 0;
}

输出结果
in constructor
update this->count 2
update this->count 3
update this->count 4
how many count 4
do destructor

移动构造

  1. 右值:右值可以偷过来
    a. 容易消失,没有名字,不可修改。
    b. 没有其他的人在使用。
  2. 如果有移动构造,移动来源的资源一定要释放掉。

运算符重载
a. + - / * 运算符重载:定义新的运算规则
b. 左移运算符重载:定义输出方式,一定要定义成友元函数,没法用隐式转换
c. 递增运算符重载:实现自己的数据类型。
d. 赋值运算符重载:进行赋值,注意深浅拷贝。重载赋值符号,也是放置浅拷贝的重要一项。
e. 关系运算符重载:让两个自定义对象进行对比操作。
f. 函数调用重载:仿函数,定义类似函数的行为,比较自由。

拷贝构造函数和拷贝赋值运算符的调用时机:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include<iostream>
namespace _nmp2_2{
class A{
public:
A():m_caa(0),m_cab(0){}; //构造函数
A(const A& tmp){ // 拷贝构造
this->m_caa = tmp.m_caa;
this->m_cab = tmp.m_cab;
}
A& operator= (const A& tmp){ //拷贝赋值运算符
m_caa = tmp.m_caa;
m_cab = tmp.m_cab;
return *this;
}
public:
int m_caa;
int m_cab;
};
};

int main(){
_nmp2_2::A oba;
oba.m_caa = 10;
oba.m_cab = 20;
_nmp2_2::A obb = oba;// 拷贝构造
obb = oba; // 拷贝赋值运算符
return 0;
}
  • 类和类之间的关系一般就是继承关系,或者是组合关系。
  • 委托关系:一个类中包含指向另一个类的指针。

面向对象

  1. 运行时多态:虚函数+动态绑定:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Book{
public:
string ISBN;
virtual void func(){
cout<<"Book"<<endl;
}
};

class ComicBook : public Book{
public:
void func(){
cout<<"Comic Book"<<endl;
}
};

class ActionBook : public Book{
public:
void func(){
cout<<"Action Book"<<endl;
}
};

int main(){
ComicBook cb;
ActionBook ac;
Book* b1 = (Book*) &cb;
Book& b2 = ac;
b1->func(); // cout Comic Book
b2.func(); // cout Action Book
return 0;
}
  1. 派生类构造的时候,要先构造基类的东西。

  2. 派生类析构的时候,先析构自己的东西,再析构基类的东西。

  3. vtable:找到子类函数的关键。

    静态成员:静态变量和静态成员函数,加static关键字:
    a. 静态成员变量:
    i. 所有对象共享同一份数据
    ii. 在编译阶段分配内存,不和类对象在同一个存储空间上,只有非静态成员变量才属于类的对象上
    iii. 类内声明,类外初始化。
    b. 静态函数:
    i. 所有对象共享同一个函数。
    ii. 静态成员函数只能访问静态成员变量。

  4. 纯虚函数不能实例化对象。

  5. 如何让抽象类不能生成对象?

  • 构造函数和拷贝构造函数都用protected修饰。
  1. protect和private的区别:子类中不可访问父类中的private的属性和函数,但可以访问protect的。
  2. C++中struct和类的唯一区别就是权限不同,struct默认public,public继承,class 默认private,private继承。
  3. 基类的析构函数,一般加上virtual,让派生类释放自己的,基类释放自己的,每个类做好自己的事情,派生类不要管基类的释放。
  4. 容器中存放类对象:如果vector< Base > vc 中push_back一个派生类,那么派生类相对于基类多出来的部分,会被砍掉。但是如果说把类型换换,vector< Base* > vc2, vc2中插入一个派生类指针,根据多态性,可以访问到多出来的那部分。
  • 如果类中包含静态成员变量,无论这个静态成员变量是否使用,都会给这个静态成员变量分配内存。
  • 全局对象的初始化顺序是不固定的
  • 做父类应该有个虚析构函数。
  • 对于不允许进行拷贝构造或者拷贝赋值运算符的函数:用=delete或者定义为private函数。

模板与泛型编程

  1. C++除了面向对象编程思想之外,还有泛型编程思想,主要利用的技术就是模板。
  2. 面向对象编程和泛型编程都能处理在编写程序时不知道类型的情况,不同之处在于,面向对象编程能够处理在程序运行之前都都未知的情况,而泛型编程,在编译时就能获知类型
  3. 模板的声明和定义要都放在.h中:编译器看到模板不会生成代码,一定要实例化一个模板的一个特定版本的时候,编译器才会生成代码,为了生成一个实例化版本,模板的头文件中要包含模板的定义和声明。
  4. 函数模板:建立一个通用函数,其函数返回值类型和形参类型可以不具体指定,用一个虚拟的类型来代表。
    在这里插入图片描述
  5. 类模板:建立一个通用类,类中的成员数据类型可以不具体制定,用一个虚拟的类型来代表。类模板在初始化的时候一定要显式的表明是什么类型的。
    在这里插入图片描述
    在类内,声明T之后,就不用再用T再次声明,但是在类外,但凡要用到A类,就要表明T的类型。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
template<typename T>
class A{
T a;

A& func(){
cout<<"hello world"<<endl;
}

A& func2();
};

template<typename T>
A<T>& A<T>::func2(){
cout<<"hello world 2"<<endl;
return *this;
}

对于类模板中的静态数据,每个类型共享一个,而不是所有的共享一个。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
template<typename T>
class A{
public:
static int count;
};

template<typename T>
int A<T>::count = 0;

int main(){
A<int> a1;
a1.count+=1;
A<long> a2;
a2.count+=10;

cout<<A<int>::count<<endl; // 1
cout<<A<long>::count<<endl; // 10

return 0;
}

普通类中包含模板函数 …

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class DebugDelete{
public:
template <typename T>
void operator() (T*p){
cout<<"delete it"<<endl;
delete p;
}
};

int main(){
double* p = new double(10);
DebugDelete d;
d(p);

return 0;
}
  1. 一个类型推断的小例子
1
2
3
4
template<typename It>
auto func(It begin, It end) -> decltype(*begin){
return *begin;
}
  1. 虽然不能直接将一个右值引用绑定到到一个左值上,但可以用move获得一个绑定到左值上的右值引用。std::move不会创建新的对象,仅仅是类型转换,但是std::move会影响到编译器函数重载的匹配。 std::move(a) == static_cast< A&&>(a);
  2. 左值常引用相当于万能型:可以用左值或者右值进行初始化。
  3. 改成const &,可以省去参数的拷贝,一定要用const &,多用,好用。
  4. 完美转发 = 引用折叠 + 万能引用 + std::forward。
  5. 想保留左值右值属性的时候,用std::forward。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
template <typename FUNC, typename T1, typename T2>
void flip(FUNC func, T1&& t1, T2&& t2){
func(t2,t1); // t2 t1 都是左值,并且不能将左值调用到右值的函数里面,所以要用完美转发
func(std::forward<T2>(t2),std::forward<T1>(t1));
}
void f(int v1,int& v2){
cout<<v1 <<" " <<++v2<<endl;
}

int main(int argc, char * argv[])
{
int i = 10;

// FUNC->f
// t1->int
// t2->int
// 左值传到右值里面相当于一个引用
flip(f,i,42);
cout<<i<<endl; //11
}
  1. 模板重载的时候,会按照匹配度去调用。 非模板匹配的好的,就会调用非函数模板。
  2. 可变参数模板:当参数个数未知,类型未知,一定要用模板,有点类似递归,要有一个最终的出口。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//出口
template <typename T>
ostream& print(ostream& os, const T& t){
os<<t<<endl;
return os;
}

template <typename T, typename... Args>
ostream& print(ostream& os, const T& t, Args... args){
os<<t<<" ";
print(os,args...);
return os;
}


int main(int argc, char * argv[])
{
print(cout,1,"string", 0.0, 3L);
}

  1. 命名空间:减少命名冲突。
  2. 头文件当中禁止使用using,cpp文件中放到匿名命名空间中。

C++ Primer学习二
https://cauccliu.github.io/2024/03/26/C++ Primer学习二/
Author
Liuchang
Posted on
March 26, 2024
Licensed under