重学C++Primer笔记11---类的四个合成和MyString类的实现

来源:互联网 发布:淘宝怎么接通人工客服 编辑:程序博客网 时间:2024/06/05 18:03

  合成即默认,由编译器自动生成并隐式调用。构造、复制构造、析构、赋值操作符分别对对象的初始化、复制、撤销、赋值进行操作。理解这些概念稍微要花点时间,但是理解了还要不断的实践,才能够得心应手,信手拈来。

1 合成构造函数

  构造函数的一般特性:

  • 构造函数的名字与类的名字相同,且不能指定返回类型
  • 构造函数可以重载,即形参表可以多样
  • 因为构造函数是用来初始化变量,所以const构造函数是不可行的
  • 构造函数的初始化列表只在构造函数的定义中而不是声明中指定
  • 某些数据成员必须使用构造函数初始化列表来初始化:任何的常量(const)成员、引用类型成员、没有默认构造函数的类类型成员。注意,非const引用只能绑定到与该引用同类型的对象,const引用则可以绑定到不同但相关的类型的对象或绑定到右值
  • 数据成员在构造函数的初始化列表中的初始化次序是按照成员被声明的次序是相同的,因此,初始化类表中也应按照这个次序来初始化,防止一个成员是根据其他成员而初始化的,但是次序不跟声明的次序相同的这种情况发生
  • 通常,除非有明显的理由想要定义隐式转化,否则,单形参构造函数应该为explicit修饰,这样,用户需通过显示的构造对象才能通过,注意,explicit关键字只能用于类内部的构造函数声明上

  合成构造函数:

  • 只有当一个类没有定义构造函数(包括复制构造函数)时,编译器才会自动生成一个默认的构造函数
  • 如果定义了其他的构造函数,那么显示定义一个无参构造函数(默认构造函数)总是安全的

2 合成的复制构造函数

  复制构造函数的一般特性:

  • 当对另一个同类型的对象显式或隐式初始化,会调用复制构造函数
  • 将一个对象作为实参传给一个函数(非引用类型或非指针类型),会调用复制构造函数
  • 函数返回一个对象时,会调用复制构造函数
  • 初始化顺序容器中的元素时,会调用复制构造函数
  • 根据元素初始化式类表初始化数组元素时,会调用复制构造函数
  • IO类型的对象不能复制,因此对此类对象需要防止复制初始化,如私有化复制构造函数
  • 通过声明(但不定义)private复制构造函数,可以禁止任何复制类类型对象的尝试,包括友元中的函数和成员函数

  合成的复制构造函数:

  • 合成复制构造函数的行为是,执行逐个成员初始化(非static成员),即将新对象初始化为原对象的副本,当成员为类类型的时候,则使用该类的复制构造函数进行复制。特殊情况就是数组成员也能够复制。
  • 当成员包含指针类型的成员或者有成员表示在构造函数中分配的其他资源,都需要重新显示定义复制构造函数。原因是初始化只是复制原对象的副本,所以指针也只是复制,即原对象和新对象的指针成员共同指向同一块内存,当一个对象释放内存后,再释放另一个对象,那么就会造成重释放(double free)错误
  • 没有定义复制构造函数时,编译器就会为我们合成一个,即使定义了其他的构造函数(非复制构造函数),也会合成复制构造函数

3 合成析构函数

  • 不带任何参数,且和构造函数一样没有返回值,因此析构函数是不能重载的
  • 即使自定义了析构函数,编译器也总是会为我们合成一个析构函数,并且如果自定义了析构函数,编译器在执行时会先调用自定义的析构函数(手动释放堆上内存)再调用合成的析构函数(不会释放堆上的内存)
  • 析构函数按对象创建时的逆序撤销每个非static成员(栈上面的成员)
  • 析构函数并不删除指针成员所指向的对象,因此需要手动去释放内存

4 合成赋值操作符

  赋值操作符的一般特性:

  • 如果类没有定义自己的赋值操作符,同样的,编译器也会合成一个
  • 显示定义赋值操作符,则是重载赋值操作符
  • 大多数操作符可以定义为成员函数或非成员函数,当操作符为成员函数时,它的第一个操作数隐式绑定到了this指针,赋值操作符也是如此,但赋值操作符必须定义自己的类的成员,因为赋值必须是类的成员,如定义自己的String类,

5 相关概念理解

explicit

  C++中, 只有一个参数的构造函数(或者除了第一个参数外其余参数都有默认值的多参构造函数), 承担了两个角色。 1 是个构造器 ,2 是个默认且隐含的类型转换操作符。
  所以, 有时候在我们写下如 MyClass object = type, 这样的代码, 且恰好type的类型正好是MyClass单参数构造器的参数类型, 这时候编译器就自动调用这个构造器, 创建一个MyClass类型的对象。

heap

  堆区(heap),一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。

stack

  栈区(stack),由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。

6 实例—MyString类的实现

  下面的MyString类的实现很好的对上面的概念进行了实践,通过实验,可以比较好的理解上面的理论知识。

【MyString.h】
#pragma once#define _CRT_SECURE_NO_WARNINGS#include <iostream>using namespace std;class MyString{    friend ostream& operator<<(ostream& os,const MyString &);    friend MyString operator+(const char*, const MyString&);public:    MyString(void);    MyString(const char *);    MyString(const char *, int);    MyString(const MyString&);    ~MyString(void);public:    MyString& operator=(const MyString &);    MyString& operator=(const char *);    char& operator[](const int);    const char& operator[](const int)const;    MyString operator+(const MyString&);    MyString operator+(const MyString&) const;    MyString operator+(const char*);public:    int size();private:    int m_len;    char * m_str;};
【MyString.cpp】
#include "MyString.h"#include <string.h>//--------------------------------------------------------------//MyString初始化、复制、赋值函数//--------------------------------------------------------------//显式定义默认构造函数MyString::MyString(void){    //默认构造函数将长度置为0    m_len = 0;    //可以直接将m_str初始化为0,即分配了一个字符的堆(heap)内存空间,释放内存时,同样的用数组的释放    //方式(加了方括号对),也能够释放,但是对于大小大于1的数组,缺少方括号对,则会少释放内存空间    //m_str = new char();                    //动态长度为0的数组,new返回有效的非零指针,该指针不能够解引用操作    m_str = new char[0];                    cout << "MyString(void)" << endl;}//参数为const char* str构造函数MyString::MyString(const char * str){    m_len = strlen(str);    m_str = new char[m_len+1];    memset(m_str,0,m_len+1);    strncpy(m_str,str,m_len);    cout << "MyString(const char * str)" << endl;}//显式定义复制构造函数MyString::MyString(const MyString& mystring){    m_len = mystring.m_len;    m_str = new char[m_len+1];    memset(m_str,0,m_len+1);    strncpy(m_str,mystring.m_str,mystring.m_len);    cout << "MyString(const MyString& mystring)" << endl;}//字符串常量 len=sizeof("xxx");//字符指针或数组 len = strlen(xxx)+1;MyString::MyString(const char * str,int len){    m_len = len;    m_str = (char *)malloc(m_len+1);    memset(m_str,0,m_len+1);    strncpy(m_str,str,m_len);    cout << "MyString(const char * str,int len)" <<endl;}//析构函数MyString::~MyString(void){    //两种释放内存的方式均可?    delete [] m_str;    //delete m_str;    cout << "~MyString(void)" <<endl;}//--------------------------------------------------------------//友元函数//--------------------------------------------------------------//输出操作符重载ostream& operator<<(ostream& os,const MyString &mystring){    os << mystring.m_str;    return os;}MyString operator+(const char* cstr,  const MyString& str) {    MyString ms1 = cstr;    MyString ms2 = str + ms1;    return ms2;}//--------------------------------------------------------------//操作符重载//--------------------------------------------------------------//赋值操作符重载:包含指针,跟复制构造函数一样,对指针只是按成员复制,所以需重载MyString& MyString::operator=(const MyString & mystring){    if (&mystring == this)    {        cout << "不能够复制自己" << endl;        return *this;    }    delete [] m_str;    m_len = mystring.m_len;    m_str = new char[m_len+1];    memset(m_str,0,m_len+1);    strncpy(m_str,mystring.m_str,mystring.m_len);    cout << "operator=(const MyString & mystring)" <<endl;    return *this;}MyString& MyString::operator=(const char *str){    delete [] m_str;    m_len = strlen(str);    m_str = new char[m_len+1];    memset(m_str,0,m_len+1);    strncpy(m_str,str,m_len);    cout << "operator=(const char *str)" <<endl;    return *this;}char& MyString::operator[](const int index){    if(index >= m_len && index < 0){        cout << "下标操作溢出" << endl;        exit(-1);    }    return m_str[index];}//const char& MyString::operator[](const int index) const;//char& MyString::operator[](const int index) const;const char& MyString::operator[](const int index) const{    if(index >= m_len && index < 0){        cout << "下标操作溢出" << endl;        exit(-1);    }    return m_str[index];}//+操作符重载MyString MyString::operator+(const char* cstr) {    char* new_str;    int new_size = strlen(cstr) + m_len + 1;    new_str = new char[new_size];    memset(new_str,0,new_size);    strcpy(new_str, m_str);    strcat(new_str, cstr);    MyString ms = new_str;    delete new_str;    return ms;   }MyString MyString::operator+(const MyString& str) const {    char* temp_str;    int temp_size = strlen(str.m_str) + m_len + 1;    temp_str = new char[temp_size];    memset(temp_str,0,temp_size);    strcpy(temp_str, m_str);    strcat(temp_str, str.m_str);    MyString ms = temp_str;    delete temp_str;    return ms; }MyString MyString::operator+(const MyString& str) {    return (*this + str.m_str);}//--------------------------------------------------------------//MyString功能函数,可扩展//--------------------------------------------------------------//得到mystring的大小int MyString::size(){    return m_len;    }
【main.cpp】
#include "MyString.h"int main(int argc,char *argv[]){    /*默认构造函数测试*/    MyString str0;    /*测试形参为const char* 构造函数*/    char * cstr0 = "hello world";    char cstr1[] = "hello world";    MyString str1 = "hello world";    //C++ Primer 第四版书上说会调用复制构造函数(错误)    MyString str2("hello world");    MyString str3(cstr0);    MyString str4(cstr1);    MyString str5 = cstr0;    MyString str6 = cstr1;    /*先初始化,再调用形参为const char *赋值操作符重载函数,如果屏蔽形参为const char*函数,则先会隐式构造一个值为"world"MyString类    再调用const MyString &赋值操作符重载函数    */    MyString str7 = "hello";    str7 = "world";    /*先初始化,再调用形参为const MyString &赋值操作符重载函数*/    MyString str8 = "hello";    MyString str9 = "world";    str8 = str9;    /*如果重载下标操作符为char& MyString::operator[](const int index)const;,则还可以通过下标操作来修改const MyString但是对    于const修饰的MyString是不能修改的,所以需要将下标操作符重载函数改为const char& MyString::operator[](const int index)const;*/    const MyString str10 = "hello";    //去掉const char& MyString::operator[](const int index)const;前面的const修饰,则可以修改const 修饰的str10,不合理    //str10[0] = 'W';        //ch = 'e';    char ch = str10[1];    //+和<<号操作符重载    MyString str11 = "hello ";    str11 = str11 + "world";    cout << str11 << endl;    return 0;}
1 0
原创粉丝点击