C++primer知识点(三)

二十二:

1:拷贝控制操作

拷贝构造函数,拷贝赋值运算符,移动构造函数,移动赋值运算符,析构函数。

这些,在类的数据成员都能默认构造,拷贝,复制,销毁时,编译器默认都会有合成的版本。

(1)   拷贝构造函数:

Foo(const Foo&);

第一个参数是自身类类型的引用,额外的参数都有默认值。

几种情况下会被隐式使用,所以,不能是explicit

默认拷贝构造函数,又叫合成拷贝构造函数,也会逐元素的拷贝数组的成员。

拷贝初始化是依靠拷贝构造函数和移动构造函数来完成的。

调用的情况:

0)初始化时,用=或者I()直接初始化时用:对象赋值/拷贝对象时,=非初始化会调用拷贝赋值运算符。

1)函数传递非引用参数的对象

2)函数返回非引用的对象

3)花括号列表初始化数组或者聚合类的成员时。

4)标准库容器初始化或调用insert/push成员时。而emplace是进行直接初始化。

其中第1)条解释了,拷贝构造函数的参数为什么是引用了,要拷贝实参,要调用拷贝构造函数,又要拷贝实参,无限循环。

拷贝初始化中,编译器可以跳过拷贝/移动构造函数,直接创建对象,即,允许:

stringstr =
“123”; //拷贝构造函数

改写成:

stringstr(“123”);//略过了拷贝构造函数

即使略过了,拷贝/移动构造函数必须是存在且可访问的。

(2)拷贝复制运算符

名为operator=的函数。必须定义为成员函数。返回指向其左侧运算对象的引用。

(3)析构函数

首先指向函数体,然后销毁成员,成员按初始化顺序逆序销毁。(析构部分是隐式的),隐式销毁内置指针类型成员,并不会delete掉所指向的对象,而智能指针是类类型,所以只能指针在析构阶段会被自动销毁。

如果一个类需要析构函数,几乎可以肯定需要“拷贝构造函数”和“拷贝赋值运算符”。(这三个函数是联系很紧密的,涉及到资源的申请释放),拷贝赋值进行的操作相当于析构和拷贝构造的组合。

(4)

=default ,(1)类内是内联,类外不是内联(2)只能用于编译器可以合成的函数。

=delete (1)必须用于函数第一次声明时。(2)可以用于任何函数。

析构函数或者类成员的析构函数定义为删除的,那么不能定义该类的变量或临时对象,因为对象无法销毁。(虽然可以动态分配这种类型的对象,但是不能释放)

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

虽然我们可以给引用赋值,但是改变的不是引用本身,而是他原来引用的对象。所以:对于有引用成员的类,合成拷贝赋值运算符被定义为删除的。

(5)move 定义在utility头文件中。使用时要直接调用std::move,防止类定义自己的move,即使我们在调用move前声明usingstd::move,但是实参的命名空间要优先于本作用域内的搜索。(可以将左值转化为右值,返回一个右值);

(6)移动,会大幅度提高性能。且io类和unique_ptr(包含不能被拷贝的资源:指针或IO缓冲)不能拷贝,但可以移动。

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

左值引用代表对象身份。右值引用代表对象的值。

int i = 42;

int &r1 = i*32;//错误

const int &r1 = i*32;//正确,可以将const引用绑定到右值上

int && r2 = i*32;//正确,右值引用

前置递增/递减 返回左值。

后置递增/递减 返回右值。

int ia = 10;

//ia++ = 5;//错误

++ia = 6;//正确

不能将右值引用绑定到右值引用类型的变量上。

int &&r1 = 42;

int &&r2 = r1;//错误

我们可以销毁一个移后原对象,也可以对他赋值,但是不能使用它,即希望他会有什么值。

(7)移动构造函数

第一个参数是右值引用,其他参数必须有默认实参。

不分配任何内存,但记得要把原对象做好处理:移后原对象可以被安全的销毁或者赋值。

如果,确认不会抛出异常,可以在参数列表的小括号后加上 noexcept(声明和定义中都要有)告诉编译器,否则编译器要做一些额外的工作。

必须定义拷贝控制成员的类,是有些类成员必须经过拷贝成员的操作。通常移动操作比拷贝要节省效率。

移动容器元素可以使用移动迭代器,解引用生成右值引用。

拷贝成员通常参数是const T&的,移动成员通常不是const,即T&&,逻辑上,一个不改变,一个改变。

(8)

有时右值的使用方式令人惊讶:

s1,s2为string:

s1+s2 = “wow”;//对右值赋值,这里是允许的。。

新标准库允许向右值赋值。我们可以阻止这种操作。即强制左侧运算对象(即,this指向的对象)是一个左值。

我们指出this左值/右值属性的方式与定义const成员函数相同。在参数列表后放置一个引用限定符。(声明与定义都要有),引用限定符必须跟在const之后

Foo &operator=(const Foo&)&;//只能向可修改的左值赋值。

Foo sorted() &&;//可用于可改变的右值

Foo sorted() const &;//可用于任何类型的Foo

定义const成员函数,可以一个有,一个没有。

但是对于引用限定的函数:如果函数名,参数都相同,必须“都加引用限定符或者都不加”

例子1:

#include<iostream>

#include<string>

#include<vector>

#include<numeric>

#include<algorithm>

using namespace std;

class X

{

public:

X():data{1,4,3}

{

}

vector<int>data;

Xsorted() const &

{

cout<<"const&"<<endl;

Xret(*this);                          //1/2

sort(ret.data.begin(),ret.data.end()); //1

returnret;                             //1

//returnret.sorted();                  //2无限递归

//returnX(*this).sorted();             //3右值sorted

}

Xsorted() &&

{

cout<<"&&&&&&&&"<<endl;

sort(data.begin(),data.end());

return*this;

}

};

int main()

{

Xx;

Xx2 = x.sorted();

for(auto e : x2.data)

{

cout<< e << " ";

}

cout<< endl;

getchar();

return0;

}

例子2:

#include<iostream>

#include<string>

#include<vector>

#include<list>

#include<array>

#include<numeric>

#include<map>

#include<unordered_map>

#include<memory>

#include<new>

#include<algorithm>

usingnamespacestd;

classMyClass

{

public:

MyClass() :str("default")

{

cout
<<"合成构造函数" <<endl;

}

MyClass(stringstr1)
:str(str1)

{

cout
<<"string参数构造函数" <<endl;

}

MyClass(constMyClass&you)

{

str
=you.str;

cout
<<"const拷贝构造" <<endl;

}

MyClass(MyClass
&you)

{

str
=you.str;

cout
<<"非const拷贝构造"<<endl;

}

MyClass& operator=(MyClass
&you)

{

cout
<<"拷贝赋值"<<endl;

this->str
= you.str;

return
*this;

}

MyClass(MyClass&&you
)

{

str
=you.str;

cout
<<"非const移动构造"<<endl;

}

MyClass(constMyClass
&&you)

{

str
=you.str;

cout
<<"const移动构造" <<endl;

}

MyClass& operator=(MyClass&&you)

{

cout
<<"移动赋值"<<endl;

str
=you.str;

return
*this;

}

voidstrOut()

{

cout
<<str<<"-----------------------"
<<endl<<endl;;

}

private:

stringstr;

};

intmain()

{

conststringss
="const str";

constMyClassm0;//默认构造

cout
<<"const MyClass m0;" <<endl<<endl;

MyClassm1;//默认构造

cout
<<"MyClass m1;" <<endl<<endl;

MyClassm2
="m2";//string参数构造函数

cout
<<"MyClass m2 =\"m2\";" <<endl
<<endl;

MyClassm3("m3");//string参数构造

cout
<<"MyClassm3(\"m3\");" <<endl
<<endl;

MyClassm4(ss);//string参数构造

cout
<<"MyClass m4(ss);" <<endl<<endl;

MyClassm5(std::move(ss));//string参数构造===

cout
<<"MyClass m5(std::move(ss));=====" <<endl<<endl;

MyClassm6
=m2;//拷贝构造非const

cout
<<"MyClass m6 = m2;" <<endl<<endl;

MyClassm6_1
=m0;//const拷贝构造

cout
<<"MyClass m6_1 = m1;" <<endl<<endl;

MyClassm7(m2);//拷贝构造非const

cout
<<"MyClass m7(m2);" <<endl<<endl;

MyClassm8(std::move(m3));//移动构造非const

cout
<<"MyClassm8(std::move(m3));" <<endl
<<endl;

m8 =std::move(m3);//移动赋值

cout
<<"m8 = std::move(m3);" <<endl<<endl;

m1 =m2;//拷贝赋值

cout
<<"m1 = m2;" <<endl<<endl;

m1 =std::move(ss);//string参数构造+移动赋值===========

cout
<<"m1 =std::move(ss);========" <<endl
<<endl;

getchar();

return 0;

}

二十三:重载与类型转换

(1)除了重载函数调用运算符operator()之外,其他重载运算符不能含有默认实参。

不能重载 ::  .* .  ?: (四个)

逻辑运算符,求值顺序规则和短路问题无法保留,所以,不建议重载。

逗号运算符,取地址运算符,已有内置的含义,不建议重载。

(2)赋值(=)/下标([])/调用() /成员访问箭头(->)
必须是成员函数。

算术/相等/关系可以换位置的,一般应是 友元函数。

(3)输入输出运算符必须是非成员函数。因为输入输出必须是i/o stream成员,我们无法给标准库类添加成员。所以只能当参数传递i/o stream。

istream&operator>>(istream &is,MyClass &it);

输入运算符必须处理输入可能失败的情况,而输出运算符不需要。

(4)   vector还定义了第三中赋值运算符,接受花括号的元素列表作为参数。

MyClass&operator=(initializer_list<string>);//必须是成员函数

(5)复合赋值 += *=等,为与内置类型保持一致,要返回左侧运算对象的引用。

(6)区分前置和后置运算符:(后置版本接受一个额外的(不被使用)int类型参数。

(7)箭头运算符,不能随便定义,获取成员这一事实永远不变。(编译器怎么限定的????我随便返回一个“你好”,也没报错。)

(8)如果类定义了函数调用运算符,则类的对象称作“函数对象”

lambda是函数对象。(不是对象调用,所以,我们只需对于sort等,传递对象就行,而不是对象后加括号)

当lambda通过引用捕获变量时,编译器可以直接使用该引用,而不必在lambda产生的类中将其存储为数据成员。

当lambda通过值捕获的变量时,lambda产生的类必须为每个值捕获的变量建立对应的数据成员。

因为需要带参数的构造函数,参数列表来初始化成员,所以lambda产生的类不包含默认构造函数/赋值运算符及默认析构函数,而默认拷贝/移动构造函数要视捕获的数据成员类型而定。(成员是否可以拷贝/移动)

(9)标准库函数对象。functional头文件

算术:plus<T>  minus<T> multiplies<T>divides<T> modulus<T> negate<T>取反

关系:equal_to<T>not_equal_to<T> greater<T> greater_equal<T> less<T>less_equal<T>

逻辑:logical_and<T>logical_or<T> logical_not<T>

例:sort<svec.begin(),svec.end(),greater<string>());

(10)可调用对象:函数,函数指针,lambda表达式,bind创建的对象,重载了函数调用运算符的类

标准库function类型,function<T>是一个模板,能够表示的对象的调用形式。

function<int(int,int)>可以囊括上面所有的形式。

对于同函数名,不同参数类型的,为解决function存储的二义性:1)函数指针 2)使用lambda表达式(函数体为 return
add(a,b))

(11)类类型转换:转换构造函数,类型转换运算符

operatortype() const;

可以面向任何可以作为函数返回的类型。(不能是数组和函数,可以是指针和引用),没有返回类型,没有形参,必须是成员函数,一般是const

尽管编译器一次只能执行一个用户定义的类型转换,但是可以置于内置类型转换之前或之后。(连同内置转换,可以连续转)

类型转换时隐式的,所以没有参数,虽然没有返回值,但是都会返回一个对应类型的值。

(12)类型转换可能产生意想不到的后果。

int i = 42;

cin << i; //cin转换为bool,提升成int,然后左移。

C++11引入显示的类型转换,来防止。

explicit operator int() const { return val;}

MyClass si = 3;

si+3;//错误

static_cast<int>(si)+3;//正确

有个例外:如果表达式被用作条件,编译器将显式的类型转换自动应用于他。

while(cin>>value) //对结果转换,而不是一开始就把cin转换

对bool类型的转换一般用在条件部分,所以,operator bool一般定义成explicit

(13)避免二义性类型转换

1)

1:A有B--->A的转换构造函数

2:B有B--->A的类型转换函数

当需要B--->A时,会有歧义。当然显示的调用可以避免。

2)内置算术类型有自动转换,所以对于设计内置类型的转换,只定义一个就可。

3)提供了构造函数含一个算术类型参数(算术到类转换)

转换目标是算术类型的类型转换(类到算术转换)

重载了运算符(参数是类类型)

MyClassmy;

inti = my + 0;//有歧义。

时间: 2024-08-23 04:47:01

C++primer知识点(三)的相关文章

ExtJS4.2 Grid知识点三:改变表格Grid单元格背景颜色

在ExtJS4.2 Grid知识点一:改变表格Grid单元格文字颜色一文中讲解了如何改变单元格中文字颜色,接下来在本章学习如何改变Grid中单元格的背景颜色,显示结果如图片: 在线演示  /  示例代码 实现方式同样是为Grid中该列自定义renderer函数,查询ExtJS 4.2 API得知,Ext.grid.column.Column的renderer属性可以是一个函数也可以是字符串,这个知识点是通过函数来实现的.函数参数列表如下: value : 当前待渲染的单元格值,即表格中某行某列的

Python数据分析--Pandas知识点(三)

本文主要是总结学习pandas过程中用到的函数和方法, 在此记录, 防止遗忘. Python数据分析--Pandas知识点(一) Python数据分析--Pandas知识点(二) 下面将是在知识点一, 二的基础上继续总结. 前面所介绍的都是以表格的形式中展现数据, 下面将介绍Pandas与Matplotlib配合绘制出折线图, 散点图, 饼图, 柱形图, 直方图等五大基本图形. Matplotlib是python中的一个2D图形库, 它能以各种硬拷贝的格式和跨平台的交互式环境生成高质量的图形,

ES6之主要知识点(三)字符串

引自:http://es6.ruanyifeng.com/#docs/string#codePointAt codePointAt() String.fromCodePoint() at() includes(),startsWith(),endsWith() repeat() padStart(),padEnd() 模板字符串 模板编译 1.codePointAt() codePointAt方法的结果与charCodeAt方法相同. 总之,codePointAt方法会正确返回32位的UTF-1

C++之易混淆知识点三

最近复习算法,感到有一丝丝忘记的困惑,赶紧记下来... 一.分治法 分治法的思想就是"分而治之",很明显就是将规模比较庞大.复杂的问题进行分治,然后得到多个小模块,最好这些小模块之间是独立的,如果这些小模块之间耦合性比较大的话,需要多次计算重复的问题,从而出现了冗余,这种情况下,可以利用动态规划法,保存这些小模块问题的解,这样就避免了多次重复计算相同问题的解了.分治法的一般解题步骤包括: 根据分治法的解题思想,我们可以看到这其中需要用到递归.以斐波那契函数为例: 现在要求计算,则使用分

C++Primer 第三章

//1.位于头文件中的代码一般不应该使用using声明.这是因为头文件的内容会拷贝到所有引用它的文件中,可能会产生始料未及的命名空间冲突. // 三种使用命名空间中的名字的方法 using namespace std; //最好不要在头文件中使用,容易造成命名空间名字污染,导致命名冲突 using std::string; std::string; //2.头文件cctype中定义了一组对字符进行操作的函数 // isalnum(), isalpha(), iscntrl(), isdigit(

C++primer知识点(一)

一: g++c1.cpp -o c1.exe -std=c++11 c++11,加入了int a = {10},的初始化形式,如果有精度损失,那么会有警告. 二: 对:const int引用=  int //const 只是针对自己来说的 错:int 引用 = constint //不符合逻辑与语法,引用不是常量,说明可以改,但引用的却是常量,矛盾了. 三: 引用的初始化(绑定)必须是类型一致:除了 1:常量引用可以是,可转换成引用的类型的. 因为是常量引用,既然不能改,编译器的实现方式是借助一

java知识点三:异常

异常 一.概念 异常时程序中的一些错误,但并不是所以的错误都是异常,并且错误有时候是可以避免的. 异常体: Throwable:所以异常类的超类 Error:表示不希望被程序捕获,或者是程序无法处理的错误 Exception:表示用户程序可能捕捉的异常情况或者说可以处理的异常 其中异常类Exception又分为运行时异常(RuntimeException)和非运行时异常. Java异常又可以分为不受检查异常(Unchecked Exception)和检查异常(Checked Exception)

面试知识点三:Java多线程

35.并行和并发有什么区别? 36.线程和进程的区别? 37.守护线程是什么? 38.创建线程有哪几种方式? 39.说一下 runnable 和 callable 有什么区别? 40.线程有哪些状态? 41.sleep() 和 wait() 有什么区别? 42.notify()和 notifyAll()有什么区别? 43.线程的 run()和 start()有什么区别? 44.创建线程池有哪几种方式? 45.线程池都有哪些状态? 46.线程池中 submit()和 execute()方法有什么区

知识点(三)

一.css垂直居中 单行文本的居中 1.文字水平居中 2.文本垂直水平居中 多行文本的垂直居中 1.使用display:flex实现 2.使用display:-webkit-box实现 3.使用绝对定位和负边距 4.使用transform:translate定位 5.绝对定位和0 6.通过display:table-cell 二.js常用数据类型 字符串值,数值,布尔值,数组,对象. 三.async和await 在async/await之前,我们有三种方式写异步代码 1嵌套回调 2以Promis