《C++Primer》12、13章

第12章 动态内存

12.1 智能指针

shared_ptr<T>

make_shared<T>(args)

直接初始化 new int(10);

默认初始化 new int;

值初始化 new int();

由内置指针(而不是智能指针)管理的动态内存在被显示释放前一直都会存在。

最好坚持只使用智能指针;

delete之后重置指针值为nullptr;

unique_ptr

u = nullptr 释放u指向的对象,将u置为空

u.release()  u放弃对指针的控制权,返回指针,将u置为空

u.reset() 释放u指向的对象

u.reset(q) 释放u指向的对象,如果q为内置指针,将u指向这个对象

u.reset(nullptr) 释放u指向的对象,将u置为空

例子:

unique_ptr<string>p1(new string(“sta”));

unique_ptr<string>p3(new string(“test”));

release返回的指针通常被用来初始化另一个智能指针或给另一个智能指针赋值。

p2.reset(p3.release());

或者unique_ptr<string> p2(p1.release());

12.2 动态数组

int *pia = new int[10](); //10个值初始化为0的int

delete [] pia;

智能指针和动态数组

unique_ptr<int []> up(new int[10]); //up指向一个包含10个未初始化int的数组

up.release(); //自动用delete[]销毁其指针

allocator 类

allocator<T> a

a.allocate(n)

a.deallocate(p,n)

a.construct(p,args)

a.destroy(p)

第13章 拷贝控制

13.1 拷贝、赋值与销毁

在定义任何C++的类时,拷贝控制操作都是必要部分。

拷贝构造和移动构造定义了当用同类型的另一个对象初始化本对象时做什么。

拷贝赋值和移动赋值定义了将一个对象赋予另一个对象时做什么。


string sBook = “C++primer”;   // 拷贝初始化

string b;    //默认初始化

b = sBook;  //拷贝赋值

13.1.1 拷贝构造函数

拷贝构造函数:如果一个构造函数的第一个参数是自身类类型的引用,且任何额外的参数都有默认值,则此构造函数是拷贝构造函数。

合成拷贝构造函数:如果没有定义拷贝构造函数,编译器会为我们定义一个。

一般情况,合成的拷贝构造函数会将起参数的成员逐个拷贝到正在创建的对象中。

每个成员的类型决定它如何拷贝。数值成员逐个拷贝。

拷贝初始化 VS 直接初始化

参考:https://www.cnblogs.com/cposture/p/4925736.html

直接初始化:要求编译器使用普通的函数匹配来选择最匹配的构造函数。

拷贝初始化:要求编译器将右侧运算对象拷贝到正在创建的对象中,如果需要还要经行类型转换。

 

拷贝初始化何时发生?

1. 用=定义变量时   *********实验,临时变量

2. 将一个对象作为实参传递给一个非引用类型的形参

3. 从一个返回类型为非引用类型的函数返回的对象  ******实验,编译器优化

4. 用花括号列表初始化一个数组中的元素或一个聚合类中的成员

5. 标准容器库调用insert或push成员  *********实验,emplace比push高效


ClassTest ct2 = "ab";   //复制初始化  实际。。。编译器的思想是能不用临时变量就不用临时变量


ClassTest ct6 = getOne();//复制初始化  实际。。。直接将ct6的地址带入函数经行了初始化


#include <map>

#include <iostream>

#include <unordered_map>

#include <vector>

#include <cstring>

using std::cout;

using std::endl;

struct T_TEST

{

unsigned int mme = 13;

int test = 5;

T_TEST() = default;

T_TEST(unsigned int _mme, int t)

{

mme = _mme;

test = t;

};

};

typedef struct viterbiNode

{

int   attr = 2;

}T_VITERBINODE;

unsigned char IP[] = "111.111.111.111";

struct T_CONSTRUCT

{

int a = 1;

int b = 2;

T_CONSTRUCT()

{

std::cout<<"default construct"<<std::endl;

};

T_CONSTRUCT(int _a, int _b)

{

a = _a;

b = _b;

std::cout<<"two param construct"<<std::endl;

};

T_CONSTRUCT(const T_CONSTRUCT& other)

{

a = other.a;

b = other.b;

std::cout<<"copy construct"<<std::endl;

};

};

class ClassTest

{

public:

ClassTest()

{

c[0] = ‘\0‘;

cout<<"ClassTest()"<<endl;

}

ClassTest& operator=(const ClassTest &ct)

{

strcpy(c, ct.c);

cout<<"ClassTest& operator=(const ClassTest &ct)"<<endl;

return *this;

}

ClassTest(const char *pc)

{

strcpy(c, pc);

cout<<"ClassTest (const char *pc)"<<endl;

}

// private:

ClassTest(const ClassTest& ct)

{

strcpy(c, ct.c);

cout<<"ClassTest(const ClassTest& ct)"<<endl;

}

private:

char c[256];

};

ClassTest getOne()

{

cout<<"getOne begine"<<endl;

ClassTest a("a");

cout<<"getOne end"<<endl;

return a;

};

int main()

{

std::vector<T_CONSTRUCT> v1;

std::vector<T_CONSTRUCT> v2;

std::cout<<"push:"<<std::endl;

v1.push_back(T_CONSTRUCT(3,4));

std::cout<<"emplace:"<<std::endl;

       v2.emplace_back(5,6);

cout<<"ct1: ";

ClassTest ct1("ab");//直接初始化

cout<<"ct2: ";

ClassTest ct2 = "ab";//复制初始化

cout<<"ct3: ";

ClassTest ct3 = ct1;//复制初始化

cout<<"ct4: ";

ClassTest ct4(ct1);//直接初始化

cout<<"ct5: ";

ClassTest ct5 = ClassTest();//复制初始化

cout<<"ct6: ";

ClassTest ct6 = getOne();//复制初始化

}

延伸:聚合类


满足条件:

所有成员都是public

没有定义任何构造函数

没有类内初始值,如果有初始值,不能用{}进行赋值了,否则编译不过

没有基类,也没有virtual函数

 

不足:

所有成员public

将正确初始化的工作交给用户

增加或者删除一个成员后,所有的初始化语句都需要更新 

 

初始化:

初始化列表,如果个数少于成员数量,靠后的成员被值初始化

默认初始化:

如果内置类型的变量未被显示初始化,它的值由定义的位置决定。

定义在任何函数体之外的变量被初始化为0

定义在函数内部的类型变量将不被初始化。

 

为什么有这种区别?

1. 变量存储的位置;

2. C++为了兼容C。

 


POD类: 聚合类的一种

参考:https://www.cnblogs.com/DswCnblog/p/6371071.html

POD的定义:极简的、属于标准布局的

KW检查:非POD类,不能用memset等内存式的拷贝

memset(pthandleInfo, 0, sizeof(T_HANDLEINFO));

 

为什么需要POD类型?

可以使用字节赋值,比如memset,memcpy操作

对C内存布局兼容。

保证了静态初始化的安全有效。

是否是POD类型?

c++11 用std::is_pod<T>::value 判断

 

13.1.2 拷贝赋值运算符

Foo& operator=(const Foo&);

理解:拷贝初始化、赋值运算符的区别

string s = string(“2017”);  // s拷贝初始化

string j,k;

j = k;   // 使用string的拷贝赋值运算符

实验:修改拷贝赋值运算符,共几次拷贝赋值或构造?


cout<<"***********A=getOther(c)"<<endl;

d = getOther(c);

cout<<"***********getOther(c)"<<endl;

getOther(c);

cout<<"*********T_CONSTRUCT e = getOther(c)"<<endl;

const T_CONSTRUCT e = getOther(c);

13.1.3 析构函数

智能指针是类类型,具有析构函数。智能指针成员在析构阶段会自动销毁。隐式销毁一个内置的指针类型成员不会delete它所指向的对象。

什么时候调用析构函数?

1. 变量在离开作用域是

2. 当一个对象被销毁时

3. 容器被销毁时,其元素被销毁

4. 动态对象应用delete时

5. 临时对象,当创建它的完整表达式结束时

析构函数体自身并不直接销毁成员。成员是在析构函数体之后隐含的析构阶段中被销毁的。

实验:


cout<<"***********getOther(c)"<<endl;

getOther(c);

copy construct

before return

copy construct

delete

delete

13.1.4 三五法则

1 需要析构函数的类也需要拷贝和赋值操作

 

2 需要拷贝操作的类也需要赋值操作,反之亦然

 

13.1.5 使用=default

显示的要求编译器生成合成的版本。

 

13.1.6 阻止拷贝

通过=delete

本质上,当不能拷贝、赋值或销毁类的成员时,类的合成拷贝控制成员就是被定义为删除的。

合成的拷贝控制成员可能是删除的:

如果一个类有数据成员不能默认构造、拷贝、复制或销毁,则对应的成员函数将被定义为删除的。

通过private拷贝控制(新标准之前,现在不建议用)

拷贝控制成员声明为pirvate,但不定义它们。

13.2 拷贝控制和资源管理

两种策略:

1. 类的行为像值,通过构造函数和赋值函数控制;

 

类值拷贝赋值运算符:赋值运算符通常组合了析构函数构造函数的操作。

类似析构函数,赋值操作会销毁左侧对象的资源;

类似拷贝构造函数,赋值操作会从右侧运算对象拷贝数据;

 

2. 类的行为像指针,通过shared_ptr的方案解决或者引用计算的控制;

 

引用计数的工作方式:

构造函数还需要创建一个引用计数,用来记录有多少对象与正在创建的对象共享状态,当我们创建一个对象时,只有一个对象共享状态,计数器初始化1

拷贝构造函数不分配新的计数器,而是拷贝给定对象的数据成员,包括计数器。拷贝构造函数递增计数器,指出给定对象的状态又被一个新用户。

析构函数递减计数器,如果计数器变为0,则析构函数释放状态。

拷贝赋值运算符递增右侧运算对象的计数器,然后递减左侧运算对象的计数器。如果左侧运算对象的计数器变为0,则销毁状态。

 

 

赋值运算符:

当将一个对象赋值给它自身时,赋值运算必须正确;

一个好的方法是在销毁左侧运算对象资源之前拷贝右侧运算对象。

 

13.3 交换操作swap

对于分配了资源的类,定义swap可能是一种很重要的优化手段。(交换的可能只是某个成员的指针)

class HasPtr{

friend void swap(HasPtr&, HasPtr&);

};

inline

void swap(HasPtr &lhs, HasPtr &rhs)

{

using std::swap;

swap(lhs.ps, rhs.ps);

}

13.4 拷贝控制示例

13.5 动态内存管理类

很好的代码示例

实验:vecoter容量扩容及拷贝P317vector对象是如何增长的?


std::vector<T_CONSTRUCT> v1;

std::vector<T_CONSTRUCT> v2;

std::cout<<"push 1:"<<std::endl;

v1.push_back(T_CONSTRUCT(3,4));

std::cout<<"push 2:"<<std::endl;

v1.push_back(T_CONSTRUCT(2,3));

std::cout<<"emplace:"<<std::endl;

v2.emplace_back(5,6);

push 1:

two param construct

copy construct

delete

push 2:

two param construct

copy construct

copy construct

delete

delete

emplace:

two param construct

如何提高性能?


1、 直接push_back()改成用emplace_back();

2、 reserve(n) 预定n个空间,当然后续push_back()会增加,其中的值不确定;

3、 resize(n, x)  申请n个空间初始化为x。

reserve只是保持一个最小的空间大小,而resize则是对缓冲区进行重新分配,

里面涉及到的判断和内存处理比较多所以比reserve慢一些。

对于数据数目可以确定的时候,先预设空间大小是很有必要的。直接push_back数据频繁移动很是耗时

C++11在时空性能方面的改进:https://www.cnblogs.com/me115/p/4788322.html#h26


大小固定容器 array

前向列表 forward_list

哈希表[无序容器] unordered containers   

常量表达式 constexpr

静态断言 static_assert

move语义和右值引用

原地安置操作 Emplace operations  

13.6 对象移动

移动而非拷贝对象会大幅度提升性能。

标准库容器、string和shared_ptr类既支持移动也支持拷贝。IO类和unique_ptr类可以移动但不能拷贝。

13.6.1 右值引用

右值引用一个重要的性质:只能绑定到一个将要销毁的对象。

所以:所引用的对象将要被销毁;

该对象没有其他用户。

有哪些是生成右值?

返回非引用类型的函数

算数、关系、位、后置递增递减运算符

std::move 标准库move函数:显示地将一个左值转换为对应的右值引用类型。

注意: 除了对源对象赋值或销毁之外,我们将不再使用它。

调用move之后,我们不能对移后源对象的值做任何假设。

13.6.2 移动构造函数和移动赋值运算符

为了让我们自己的类型支持移动操作,需要为其定义移动构造函数和移动赋值运算符。

移动构造函数

StrVector::StrVec(StrVec &&s) noexcept    // 必须是noexcept

:elements(s.elements)

{

s.elements = nullptr;

}

移动后,源对象要保证安全。销毁是无害的。

移动赋值函数

StrVec  &StrVec::operator=(StrVec &&rhs) noexcept

{

if (this != &rhs)

{

free();  //释放已有元素

elements = rhs.elements;

rhs.elements = nullptr;  //源对象可析构

}

return *this;

}

在移动操作之后,移后源对象必须保持有效的、可析构的状态,但用户不能对其值进行任何假设。

 

为什么是noexcept

vector异常处理保护。

合成的移动操作:只有当一个类没有定义任何自己版本的拷贝控制成员,且它的所有数据成员都能够移动构造或移动赋值时。

移动右值,拷贝左值

StrVec v1, v2;

v1 = v2;     // v2是左值,使用拷贝赋值

StrVec getVec(istream &) //getVec 返回一个右值

v2 = getVec(cin);  // getVec(cin)是右值,使用移动赋值

左值、右值:

在C++11中可以取地址的、有名字的就是左值,反之,不能取地址的、没有名字的就是右值(将亡值或纯右值)。举个例子,int a = b+c, a 就是左值,其有变量名为a,通过&a可以获取该变量的地址;表达式b+c、函数int func()的返回值是右值,在其被赋值给某一变量前,我们不能通过变量名找到它。

如果没有移动构造函数,右值也被拷贝。(没有定义移动构造函数,使用move操作实际是用拷贝构造函数实现

定义了移动构造函数或移动赋值运算符的类必须也定义自己的拷贝操作,否则这些成员默认地被定义为删除的。


建议:不要随意使用移动操作

只有在确信算法在为一个元素赋值或者将其传递给一个用户定义的函数后不再访问它

小心地使用move,可以大幅度提升性能。随意使用很可能造成错误。

13.6.3 右值引用和成员函数

区分移动和拷贝的重载函数通常一个版本接受一个const X&,另一个版本接受一个T&&

原文地址:https://www.cnblogs.com/sunnypoem/p/9537540.html

时间: 2024-10-31 06:46:31

《C++Primer》12、13章的相关文章

C++ Primer Plus 13章的疑问

1 void show(const Brass &rba) 2 { 3 rba.ViewAcct(); 4 cout<<endl; 5 } 6 7 void inadequate(Brass ba) 8 { 9 ba.ViewAcct(); 10 cout<<endl; 11 } 12 13 14 BrassPlus buzz("Buzz Parsec", 00001111, 4300); 15 16 show(buzz); 17 inadequate(

【C++ Primer 第13章】2. 拷贝控制和资源管理

拷贝控制和资源管理 • 类的行为像一个值.意味着它应该有自己的状态,当我们拷贝一个像值得对象时,副本和原对象是完全独立的,改变副本不会对原对象有任何影响. • 行为像指针的类则共享状态.当我们拷贝一个这种类的对象时,副本和原对象使用相同的底层数据,改变副本也会改变原对象. 13.2节练习 1 #include<iostream> 2 #include<string> 3 using namespace std; 4 5 class HasPtr { 6 public: 7 HasP

PMP 第12~13章错题总结

1.合同解释应该遵循几个主要原则: 1)主导语言原则 2)适用法律原则 3)整体解释原则 4)公平诚信原则2.合同收尾包括的工作: 1)产品核实 2)可交付成果验收 3)财务结算 4)退还保证金或担保函 5)总结合同实施情况,进行采购审计,从独立.公正的第三方角度来总结采购工作的经验教训 6)更新合同记录,收集资料,整理合同档案,更新组织过程资产3.常见的谈判策略 1)最后期限 2)自己的权利有限,有决策权的人又不在场 3)拖延 4)撤退 5)出人意料 6)公平合理 7)既成事实 8)好人坏人4

函数指针(——C primer 第13章)

函数指针: int  f(int);//声明一个函数 int  (*pf)(int)=&f;//创建一个函数指针,并将函数f的函数地址对它进行初始化. 其实函数名在被使用时,总是由编译器将它转化为指针.上面的那个&那个并非必要,只是显示 的说明编译器将隐式执行的任务. int ans; ans=f(25);//使用名字调用函数f,其实在执行时,函数名f被转化为函数指针,指向函数在内存中的地址. ans=(*pf)(25);//将函数指针转化为函数名,其实在执行时又转化回去了 ans=pf(

高级声明(——C primer第13章)

高级声明: (1) int *f,g: 这个只声明了一个f指针.*是都是紧跟在其后的. (2) int *f(); f是一个函数,它的返回值是一个指向整型的指针. (3) int  (*f)(); f是一个函数指针,这个函数的返回值是一个int类型. (4) int *(*f)(); f是一个函数指针,这个函数的返回值是一个int*类型 (5) int f[]; f是一个整型数组,数组的长度暂时省略 (6) int *f[] f是一个数组,数组的元素是指向整型的指针 (7)  int f()[]

C++ primer plus读书笔记——第13章 类继承

第13章 类继承 1. 如果购买厂商的C库,除非厂商提供库函数的源代码,否则您将无法根据自己的需求,对函数进行扩展或修改.但如果是类库,只要其提供了类方法的头文件和编译后的代码,仍可以使用库中的类派生出新的类.而且可以在不公开实现的情况下将自己的类分发给其他人,同时允许他们在类中添加新特性. 2. 派生类构造函数首先创建基类对象,如果不调用基类构造函数,程序将使用默认的基类构造函数. 3. 创建派生类对象时,程序首先调用基类构造函数,然后再调用派生类构造函数.派生类对象过期时,程序将首先调用派生

3.28日第七次作业12章沟通管理13章合同管理

3.28日第七次作业12章沟通管理13章合同管理   第12章.项目沟通管理   1.项目沟通管理包括哪些过程?(记)P349 答:1).沟通计划编制 2).信息分发 3).绩效报告 4).项目干系人管理 2.阻碍有效沟通的因素有哪些?P351-352 答:1).沟通双方的物理距离 2).沟通的环境因素 3).缺乏清晰的沟通渠道 4).复杂的组织结构 5).复杂的技术术语 6).有害的态度 3.沟通计划编制的第一步是什么?目的是什么?P353 答:沟通计划编制的第一步是干系人分析.其目的是得出项

第12 13 14 15章总结与感悟

第12章 用户体验 12.1用户体验的要素 1.1用户的第一印象 1.2从用户的角度考虑问题,这需要“同理心”(理解别人的处境,心理,动机和能力) 用户需要帮助,但是用户没那么笨 光吃狗食也不够 1.3软件服务始终记住用户的选择 1.4短期刺激和长期影响 1.5不让用户犯简单的错误 1.6用户体验和质量 1.7情感设计 12.2用户体验设计的步骤和目标 12.3评价标准 1.尽快提供可感触的反馈 2.系统界面符合用户的现实管理 3.用户有控制权 4.一致性和标准化 5.适合各种类型的用户 6.帮

C++ Primer学习总结 第13章 拷贝控制

第13章 拷贝控制 1.    什么是拷贝构造函数? P440 如果一个类的构造函数的第一个参数是自己类类型的引用, 且所有其他参数都有默认值, 那么这就是一个拷贝构造函数. 2.    拷贝初始化和直接初始化.  P441 如果初始化的时候使用等号"="来初始化一个对象, 那么就是拷贝初始化. 相反, 如果初始化时, 没有用等号"=", 那么就是直接初始化. 如果是拷贝初始化, 那么用的一定是拷贝构造函数(也有可能用移动构造函数). 如果是直接初始化, 那么就用参