C++primer知识点(三)

来源:互联网 发布:黑页源码 编辑:程序博客网 时间:2024/05/20 09:08

 

二十二:

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,s2string:

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

1AB--->A的转换构造函数

2BB--->A的类型转换函数

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

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

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

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

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

MyClassmy;

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

 

 

 

 

1 0
原创粉丝点击