• C++第十二章_关于复制构造函数(解释了StringBad sailor = sports;会出现的问题以及解决方法)__复习new和delete以及学习静态类成员变量__关于赋值运算符(重构)__进一步重载赋值运算符(解析了name=temp两个对象的具体执行步骤)__比较重载运算符(使用友元函数重载)__静态成员函数__无缺陷的String类方法总结__在构造函数中使用new时应该注意的问题(


    目录

    复习new和delete以及学习静态类成员变量

    01)char* str = "Hello world";    //注意str是一个字符串指针哟
       int num_strings = strlen(str); //这些是正确的,num_strings=11,strlen()并不计算字符串中的空字符
       num_strings = 11
    02)对于静态类成员变量:
       假如有StringBad类对象s1,s2,s3。则s1,s2,s3都有自己的str和len私有成员变量
       但是s1,s2,s3共用静态类成员变量num_strings *****
       需要注意的是:
      A 静态类成员变量可以在h文件中(类中)声明,也可以在头文件中声明,但是如果多个cpp文件都声明了该h文件,
          那么该静态类成员变量就会被初始化好多次。
      B 静态类成员变量可以在cpp文件中定义具体的数值
    03)子函数的形参如果是一个类对象的话,最好是将该形参设置为引用,一旦设置成一般的类对象,会出现一些问题
     比如user_main.cpp中callme2()子函数中的问题
    04)StringBad sports("Spinish Leaves Bowl fot Dollars");
       StringBad sailor = sports;
       //上面的这一句实际等价于StringBad sailor = (StringBad)sports;
       //调用的构造函数是StringBad(const StringBad &)
       //由于原函数中没有该构造函数,所以编译器会自动创建该构造函数,而编译器自动创建的构造函数是没有将
       //num_strings自动加1的,所以就导致了构造函数和析构函数中的num_strings++和num_strings--不一致,导致
       //程序运行的结果中,最后析构函数删除的对象的个数出现不对的现象。

     1 //stringBad.h
     2 //设计一个stringBad类,类似于C++库中的string类
     3 #include <iostream>
     4 #ifndef STRINGBAD_H_
     5 #define STRINGBAD_H_
     6 
     7 class StringBad
     8 {
     9 private:
    10     char* str;
    11     int len;
    12     static int num_strings;  //新建一个静态类成员变量
    13 public:
    14     StringBad(const char* s);  //声明构造函数
    15     StringBad();  //声明默认构造函数
    16     ~StringBad();  //声明析构函数
    17     friend std::ostream & operator<<(std::ostream & os, const StringBad & st);  //声明友元函数
    18 };
    19 
    20 #endif
    21 
    22 /* 复习new和delete以及学习静态类成员变量 */
    23 /*
    24 01)char* str = "Hello world";
    25    int num_strings = strlen(str);  //这些是正确的,num_strings=11,strlen()并不计算字符串中的空字符
    26    num_strings = 11
    27 02)对于静态类成员变量:
    28    假如有StringBad类对象s1,s2,s3。则s1,s2,s3都有自己的str和len私有成员变量
    29    但是s1,s2,s3共用静态类成员变量num_strings
    30    需要注意的是:
    31    A 静态类成员变量可以在h文件中(类中)声明,也可以在头文件中声明,但是如果多个cpp文件都声明了该h文件,
    32      那么该静态类成员变量就会被初始化好多次。
    33    B 静态类成员变量可以在cpp文件中定义具体的数值
    34 03)子函数的形参如果是一个类对象的话,最好是将该形参设置为引用,一旦设置成一般的类对象,会出现一些问题
    35    比如user_main.cpp中callme2()子函数中的问题
    36 04)StringBad sports("Spinish Leaves Bowl fot Dollars");
    37    StringBad sailor = sports;
    38    //上面的这一句实际等价于StringBad sailor = (StringBad)sports;
    39   //调用的构造函数是StringBad(const StringBad &)
    40   //由于原函数中没有该构造函数,所以编译器会自动创建该构造函数,而编译器自动创建的构造函数是没有将
    41   //num_strings自动加1的,所以就导致了构造函数和析构函数中的num_strings++和num_strings--不一致,导致
    42  //程序运行的结果中,最后析构函数删除的对象的个数出现不对的现象。
    43 */
    StringBad.h
     1 //stringbad.cpp
     2 #include <cstring>
     3 #include "stringbad.h"
     4 
     5 //静态类成员变量的定义,注意要使用类成员限定符StringBad::,但是没有使用关键字static
     6 int StringBad::num_strings = 0;  
     7 
     8 //定义一个参数的构造函数
     9 //主要注意的是,对于StringBad boston("Boston");boston对象中并没有保存字符串"Boston",而是仅仅保存了该字符串的地址信息
    10 StringBad::StringBad(const char* s)  //创建对象时,可以这样创建StringBad boston("Boston");
    11 {
    12     len = std::strlen(s);  //这一句很明显是在说strlen()在名称空间std中,
    13     //同时也说明了strlen()可以接受一个字符串指针作为参数
    14     str = new char[len + 1];  //加1是为了给空字符''留出位置,new返回的是一个地址,所以str也是一个指针
    15     std::strcpy(str, s);  //将指针字符串s复制给指针字符串str
    16     //str=s; 这样做是不可以的,因为这样做只是复制了地址,而没有创建字符串副本
    17     num_strings++;  //统计新建的对象的个数
    18     std::cout << num_strings << ": "" << str << ""object created
    ";//显式多少个对象已创建
    19 }
    20 
    21 //默认构造函数的定义
    22 StringBad::StringBad()
    23 {
    24     len = 4;
    25     str = new char[len + 1];
    26     std::strcpy(str, "C++");
    27     num_strings++;//统计新建的对象的个数
    28     std::cout << num_strings << ": "" << str << ""object created
    ";//显式多少个对象已创建
    29 }
    30 
    31 //析构函数的定义
    32 //当StrngBad对象过期时,str指针也将过期,但str指向的内存仍然被分配,除非是有delete将其释放
    33 //析构函数执行时,先删除最后创建的对象,后删除最先创建的对象
    34 StringBad::~StringBad()
    35 {
    36     std::cout << """ << str << "" object deleted, ";
    37     --num_strings;  //对象的数据减1
    38     std::cout << num_strings << " left
    ";
    39     delete[] str;   //释放由new创建的内存
    40 }
    41 
    42 //友元函数的定义
    43 std::ostream & operator<<(std::ostream & os, const StringBad & st)
    44 {
    45     os << st.str;
    46     return os;
    47 }
    StringBad.cpp
     1 //user_main.cpp
     2 #include <iostream>
     3 #include "stringbad.h"
     4 
     5 using std::cout;
     6 using std::endl;
     7 
     8 void callme1(StringBad &rsb);
     9 void callme2(StringBad sb);
    10 
    11 int main()
    12 {
    13     {
    14         cout << "开始创建内部函数快" << endl;
    15         StringBad headline1("Celery Stalks at midnight");
    16         StringBad headline2("Lettuce Preey");
    17         StringBad sports("Spinish Leaves Bowl fot Dollars");
    18         cout << "headline1: " << headline1 << endl;
    19         cout << "headline2: " << headline1 << endl;
    20         cout << "sports: " << sports << endl;
    21 
    22         callme1(headline1);
    23         //callme2(headline2);  //callme2()的形参为非引用,会出问题
    24         /*
    25         callme2()会出错误的原因:
    26         01)headline2作为参数传递给cellme2(),在callme2()函数执行完毕后,会调用析构函数
    27         02)虽然函数按值传递可以防止原始参数被修改,但实际上函数已使原始字符串无法识别,导致显示一些非标准字符
    28         */
    29 
    30         StringBad sailor = sports;
    31         cout << "sailor: " << sailor << endl;
    32         //上面的这一句实际等价于StringBad sailor = (StringBad)sports;
    33         //调用的构造函数是StringBad(const StringBad &)
    34         //由于原函数中没有该构造函数,所以编译器会自动创建该构造函数,而编译器自动创建的构造函数是没有将
    35         //num_strings自动加1的,所以就导致了构造函数和析构函数中的num_strings++和num_strings--不一致,导致
    36         //程序运行的结果中,最后析构函数删除的对象的个数出现不对的现象。
    37     }
    38 
    39     system("pause");
    40     return 0;
    41 }
    42 
    43 void callme1(StringBad & rsb)
    44 {
    45     cout << "String passed by reference: " << endl;
    46     cout << """ << rsb << """ << endl;
    47 }
    48 void callme2(StringBad sb)
    49 {
    50     cout << "String passed by value: " << endl;
    51     cout << """ << sb << """ << endl;
    52 }
    user_main.cpp

    执行结果为:

    关于复制构造函数(解释了StringBad sailor = sports;会出现的问题以及解决方法)

    //StringBad.h文件
    calss StringBad
    {
    private:
      char* str;
      int len;
      static num_string;
      public:
      StringBad();//默认构造函数
      StringBad(char* s); //构造函数
      ~StringBad(); //析构函数
    };
    //StringBad.cpp文件
    StringBad::StringBad()
    {
      str = new char[1]; //与new char; 是等价的,只不过这里要和析构函数中的delete [] str;对应起来
      str = '';    //C++11中添加了nullptr来表示空指针,所以上面两句可以用str=nullptr来代替
      len = 0;
    }
    StringBad::StringBad(char* s)
    {
      len = std::strlen(s);
      str = new char[en+1]; //刚刚自己写成这样了: str = new char(len+1); 导致在析构函数中使用delete的时候不会用
      std::strcpy(str,s);
      num_string++; //已创建的对象数目加1
    }
    StringBad::~StringBad();
    {
      num_string--; //已创建的对象数目减1
      delete [] str;
      cout<<str<<" was deleted; ";
      cout<<num_string<<" was left ";
    }


    //在main函数中执行的操作
    StringBad::num_string=0; //对象数目初始化为0
    int main()
    {
      StringBad sports("Hello world!"); //创建对象sports,并将对象中的数据(str)初始化为Hello world!
      StringBad sailor = sports; //这一句将会调用默认的复制构造函数,因为自己没有定义复制构造函数
    }
    //StringBad sailor = sports;一句会出现很大的问题
    /*
    01)由于是调用的默认复制构造函数,在默认的复制构造函数中并没有num_string++; 所以会导致在执行析构函数时候剩余的对象                 数目出错
    02)  StringBad sailor = sports;等价于下面三句(无法通过编译,因为对象无法访问私有数据,这里只是说明一下)
      StringBad sailor;
      sailor.str = sports.str; //等价的这一句会出现致命的错误,即最后看到的乱码现象
      sailor.len = sports.len;
      对于sailor.str = sports.str;该句执行的结果是sailor对象中的str指针和sports对象中的str指针,都指向同一块内存,
      最后程序执行完毕,在执行析构函数时候,由于析构函数是先删除后创建的对象,也就是先删除sailor对象,同时也释放了sailor对             象中str所指向的内存,且sports对象中的str和sailor对象中的str是指向的同一块内存,则在删除sprots对象时,同时执行               cout<<str<<" was deleted "将会出现乱码。(因为sports.str指向的内存已经被sailor.str释放)
    03)对于上述问题的解决方法:自己编写一个编写复制构造函数
      StringBad::StringBad(const & st)
      {
        /* 解决问题01 */
        num_string++;
        /* 解决问题02 */
        len = st.len;
        str = new char[len+1];
        std::strcpy(str,st.str);
        cout<<num_string<<": "<<str<<" objects were created ";
      }
      //此时再执行StringBad sailor = sports;则sailor中的str和sports中的str将不是同一个地址
      //释放内存时候,就不会互相干扰
    */

    /* 什么时候自己定义复制构造函数 */
    //当类成员中有new初始化的、指向数据的指针,此时应该自己去定义复制构造函数,以复制指向的数据,而不是指针,
    //这被称为深度复制

    关于赋值运算符(重构)

    //对于StringBad sailor = sports;的执行过程分两种可能
    /*
    01)第一种可能是:直接使用复制构造函数,并且将对象sports中的数据复制给对象sailor
    02)第二种可能是:首先使用复制构造函数创建临时对象,然后使用赋值运算符(就是等号=)将临时对象赋值给sailor
                那么要使程序完美,那么就需要自己定义一个赋值运算符的重构函数
    */
    //赋值运算符(即等号)的重构函数的定义
    StringBad & StringBad::operator=(StringBad & st)
    {
      /*首先判断赋值运算符左边的对象地址(this)和右边的对象地址(&st)是不是相同*/
      if(this == &st) //this是调用该重构函数的对象的指针,该句就是在判断 a=b 中a和b是不是同一个值
        return *this; //如果是,那么程序结束,返回任意一个对象均可(*this或st)
      delete [] str; //由于a=b等价于a.operator(b),那么a就是被赋值的对象,所以要首先删除a对象中的成员str原来指向的内存
      /* 接下来进行深度复制 */
      len = st.len;
      str = new char[len+1];
      std::strcpy(str,st.str);
      return *this;
    }

    注意:不要将赋值和初始化混淆了

       Star sirius; //创建类对象sirius
          Star alpha = sirius; //初始化,调用复制构造函数
       Star dogstar;
       dogstar = sirius; //赋值,调用赋值构造函数

    进一步重载赋值运算符(解析了name=temp两个对象的具体执行步骤)

    //对于如下语句:
    String name;
    char temp[40];
    temp = getline(temp,40);
    name = temp;
    /*
    对于最后一句name = temp;执行步骤如下:
    01)先使用构造函数StringBad(const char* ps)来创建临时StringBad对象
    02)使用赋值运算符重构函数StringBad & StringBad::operator=(const StringBad & st)将临时对象中的数据复制到name中去
    03)使用析构函数将创建的临时对象删除掉。
    */
    //为提高效率,最简单的方法就是直接使用赋值运算符重构函数,使之能够直接使用常规字符串,这样就不用创建和删除临时对象了
    方法如下:
    StringBad & StringBad::operator=(StringBad & st)
    {
      delete [] str; //由于a=b等价于a.operator(b),那么a就是被赋值的对象,所以要首先删除a对象中的成员str原来指向的内存
      /* 接下来进行深度复制 */
      len = st.len;
      str = new char[len+1];
      std::strcpy(str,st.str);
      return *this;
    }

    比较重载运算符(使用友元函数重载)

    //StringBad.h文件
    friend bool operator<(const StringBad & st1, const StringBad & st2)
    friend bool operator>(const StringBad & st1, const StringBad & st2)
    friend bool operator==(const StringBad & st1, const StringBad & st2)

    //StringBad.cpp
    /*比较重载运算符(使用友元函数重载)*/
    bool StringBad::operator<(const StringBad & st1, const StringBad & st2)
    {
      if (std::strcmp(st1.str,st2.str)<0)
        return true;
      else
        return false;
    }
    //strcmp(a,b); 如果a参数位于第二个参数b之前,则返回一个负值
    //如果第一个参数位于第二个参数之后,则返回一个正值
    //如果两个参数相等,则返回0
    //以上函数可以简化为(友元函数定义):

    bool operator<(const StringBad & st1, const StringBad & st2)
    {
      return(std::strcmp(st1.str,st2.str)<0);
    }
    bool operator>(const StringBad & st1, const StringBad & st2)
    {
      return st1<st2; //调用上面写的对<重载的友元函数
    }
    bool operator==(const StringBad & st1, const StringBad & st2)
    {
      return(std::strcmp(st1.str,st2.str)==0);
    }

    对[ ]运算符的重载

    01)问题的提出:
       对于char city[40]="Armsterdan";
       那么有city[0]='A',如果city是一个类对象呢?那么就需要对[]进行重载
    02)对[ ]的重载实现方法:
      char & StringBad::operator[](int i)
      {
        return str[i]; //由于str是类中的私有数据,是一只存在的,所以该函数的返回类型可以是引用
      }
    03)调用方法:
       StringBad opera("The magic flute");
       那么语句cout<<opera[4];就是合法的opera[4]='m'
       或者opera[0]='M';也是合法的,将opera中str的第一个字符替换为M
       对于opera[4]将被转换为opera.operator[](4)
       对于opera[0]='M'将被替换为opera.operator[](0) = 'M';
    04)对于const类型的对象是不可以修改的,比如
       const StringBad opera("Hello World");
       opera[0] = "M";  //不合法,因为对象是const类型的,其值不可修改
    05)也可以提供一个仅供const StringBad 对象使用的operator[]()版本:
      const char & StringBad[](int i)
      {
        return str[i];
      }

    静态成员函数  

    01)可以将类函数声明为静态的(在声明和定义前加static),需要注意的是:
      A 不能通过对象调用静态成员函数,甚至不可以使用this指针;
      B 如果静态成员函数是在公有部分中声明的,那么可以使用类作用域解析符来使用(如StringBad::);
      C 静态成员函数与对象无关,因此只可以使用静态数据变量,在本例中HowMany()无法使用str和len,
       HowMany()只能访问静态变量num_string.
      D 如果声明和定义分开的话,那么在声明中要使用static关键字,在定义的时候要把关键字static去掉。
    02)声明+定义方法(举例):
      static int HowMany() { return num_string; }
    03)调用方法(举例):
      int count = StringBad::HowMany();

    //注意:
    StringBad sayings[4]; //表示创建一个数组,数组内的元素为4个StringBad类对象

     无缺陷的String类方法总结 

    本例子涉及到的类方法有:
    01)复制重构函数的声明、定义和调用
    02)静态变量、静态类方法的声明、定义和调用方法
    03)对=号的重构函数
    04)对<、>、和==的重构函数
    05)对输入(>>)和输出(<<)的重构函数

     1 #ifndef STRING1_H_
     2 #define STRING_H_
     3 
     4 #include <iostream>
     5 using std::ostream;  //刚刚这里写成cout了,导致下面对<<友元重载出错
     6 using std::istream;
     7 
     8 class String
     9 {
    10 private:
    11     char* str;  //保存字符串的地址
    12     int len;    //保存字符串的长度
    13     static int num_strings;  //保存创建的对象的个数
    14     static const int CINLIM = 80;  //和对>>的重载有关的一个静态常量
    15 public:
    16     /* 构造函数和析构函数 */
    17     String(const char* s);  //声明构造函数
    18     String();  //声明默认构造函数
    19     String(const String & st);  //声明复制构造函数
    20     ~String();  //声明析构函数
    21     int length() const { return len; }  //声明并定义内联函数,对象因此可以使用私有数据len
    22 
    23     /* 重构函数 */
    24     String & operator=(const String & st);  //对等号(赋值运算符的重构)
    25     String & operator=(const char* pt);  //对等号(赋值运算符的重构) 上下参数不一样
    26     char & operator[](int i);  //对[]的重构,举例:String str("Hello"); 那么str[1]就等于e,注意此时str是一个对象
    27     const char & operator[](int i) const;  //上边的那个允许对对象的第二个字符进行修改,即str[1]=E; 但是这个版本不允许,因为使用了const常量关键字
    28     //上边最后的那个const表示不会修改调用该方法对象中的数据
    29 
    30     /*友元函数*/
    31     friend bool operator<(const String & st1, const String & st2);  //小于号运算符重载+友元函数
    32     friend bool operator>(const String & st1, const String & st2);
    33     friend bool operator==(const String & st1, const String & st2);
    34     friend ostream & operator<<(ostream & os, const String & st);  //输出运算符的重载+友元函数
    35     friend istream & operator>>(istream & is, String & st);
    36 
    37     /* 静态方法(对象是不能调用的,只能通过类解析运算符(String::)使用) */
    38     static int HowMany();  //声明要加上关键字static,定义时就不用加关键字static了
    39 };
    40 
    41 #endif
    42 
    43 /* 无缺陷的String类方法总结 */
    44 /*
    45 本例子涉及到的类方法有:
    46 01)复制重构函数的声明、定义和调用
    47 02)静态变量、静态类方法的声明、定义和调用方法
    48 03)对=号的重构函数
    49 04)对<、>、和==的重构函数
    50 05)对输入(>>)和输出(<<)的重构函数
    51 */
    String1.h
      1 //string1.cpp
      2 #include <cstring>  //for strlen()、strcmp()等
      3 #include "string1.h"
      4 
      5 using std::cout;
      6 using std::cin;
      7 
      8 //静态变量的定义
      9 int String::num_strings = 0;  //注意要加类解析运算符
     10 //静态函数的定义
     11 int String::HowMany()  //注意要加类解析运算符
     12 {
     13     return num_strings;
     14 }
     15 
     16 /* 含一个参数的构造函数的定义*/
     17 String::String(const char* s)
     18 {
     19     len = std::strlen(s);  //去掉字符串最后的空字符后,总的字符数
     20     str = new char[len + 1];  //len+1是加上最后的空字符
     21     std::strcpy(str, s);
     22     num_strings++;  //对象数目加1
     23 }
     24 
     25 //默认构造函数定义
     26 String::String()
     27 {
     28     len = 1;
     29     str = new char[1];
     30     str = '';
     31     num_strings++;  //对象数目加1
     32 }
     33 
     34 //复制构造函数定义 例如String name = sports; 
     35 //sports为一个String对象,在这个过程中会创建一个临时对象,复制构造函数就负责将该临时对象复制给name
     36 String::String(const String & st)
     37 {
     38     len = st.len;
     39     str = new char[len + 1];
     40     std::strcpy(str, st.str);
     41     num_strings++;  //对象数目加1
     42 }
     43 
     44 //析构函数定义
     45 String::~String()
     46 {
     47     num_strings--;
     48     delete[] str;  //释放内存
     49 }
     50 
     51 //赋值运算符重构函数定义
     52 //调用方法为:String name = sports;  (name和sports都是String类对象)
     53 //实际调用方法为:name.operator=(sports);
     54 String & String::operator=(const String & st)  //刚刚类解析运算符的位置放错了,放在最先前边导致出错
     55 {
     56     if (this == &st)  //首先判断一下name对象和sports是不是同一个对象,如果是,那么该方法结束
     57         return *this;
     58     delete[] str;  //要首先删除name.str原来指向的内存,防止内存浪费
     59     len = st.len;
     60     str = new char[len + 1];
     61     std::strcpy(str, st.str);
     62     num_strings++;  //对象数目加1
     63     return *this;  //返回调用该方法的指针
     64 }
     65 
     66 //赋值运算符重构函数定义
     67 //调用方法为String name = "Hello world!"
     68 String & String::operator=(const char* pt)
     69 {
     70     delete[] str;  //释放name.str原来就有的内存
     71     len = std::strlen(pt);
     72     str = new char[len + 1];
     73     std::strcpy(str, pt);
     74     num_strings++;  //对象数目加1
     75     return *this;  //返回调用该方法的指针
     76 }
     77 
     78 //对[]的重构函数定义
     79 //调用方法为: 假如有String name("Hello");
     80 //那么可以使用 name[1],name[1]就等价于字符串中的第二个元素
     81 char & String::operator[](int i)
     82 {
     83     return str[i];  //直接返回字符串指针中的第i+1个元素就好了
     84 }
     85 
     86 //用法和上边的是一样的,只不过该方法不允许修改值
     87 //比如修改name.str中第二个元素: name[1]='M'; 在此方法下是不合法的
     88 const char & String::operator[](int i) const
     89 {
     90     return str[i];  //直接返回字符串指针中的第i+1个元素就好了
     91 }
     92 
     93 /* 以下为友元函数定义 */
     94 //strcmp(a,b); 如果a参数位于第二个参数b之前,则返回一个负值
     95 //如果第一个参数位于第二个参数之后,则返回一个正值
     96 //如果两个参数相等,则返回0
     97 
     98 //对小于号的重载
     99 bool operator<(const String & st1, const String & st2)
    100 {
    101     return (std::strcmp(st1.str, st2.str) < 0);
    102     //如果std::strcmp(st1.str, st2.str) < 0 这个表达式成立则返回ture,否则返回false
    103 }
    104 
    105 //对大于号的重载
    106 bool operator>(const String & st1, const String & st2)
    107 {
    108     return st1 < st2;  //调用上面的operator<()函数
    109 }
    110 
    111 //对恒等于号的重载
    112 bool operator==(const String & st1, const String & st2)
    113 {
    114     return (std::strcmp(st1.str, st2.str) == 0);
    115 }
    116 
    117 //对输出运算符的重载函数的定义
    118 ostream & operator<<(ostream & os, const String & st)
    119 {
    120     os << st.str;
    121     return os;
    122 }
    123 
    124 //对输入运算符的重载函数的定义
    125 //调用方法为:String name;  cin>>name;
    126 istream & operator>>(istream & is, String & st)
    127 {
    128     char temp[String::CINLIM];
    129     is.get(temp, String::CINLIM);
    130     if (is)  //判断上一句是否输入成功
    131         st = temp;
    132     while (is && is.get() != '
    ') //过滤掉输入流中的换行符
    133         continue;
    134     return is;
    135 }
    String1.cpp
     1 //usret_main.cpp
     2 #include <iostream>
     3 #include "string1.h"
     4 
     5 const int Arsize = 10;
     6 const int MaxLen = 81;
     7 
     8 int main()
     9 {
    10     using std::cout;
    11     using std::cin;
    12     using std::endl;
    13 
    14     String name;  //使用默认构造函数创建一个对象name
    15     cout << "Hi,what's your name?" << endl;
    16     cin >> name;  //使用对>>的重构函数输入到对象name中的str中去
    17     cout << name << ", please enter up to " << Arsize << " short sayings <empty line to quit>" << endl;
    18     String sayings[Arsize];  //创建一个数组,该数组内包含了Arsize个String类对象
    19     char temp[MaxLen];  //新建一个字符串数组,用来存储从键盘输入的字符串
    20     int i;
    21     for (i = 0; i < Arsize; i++)
    22     {
    23         cout << i + 1 << " :";
    24         cin.get(temp, MaxLen);  //输入字符串到字符串数组temp中去,最多可输入MaxLen个字符
    25         while (cin && cin.get() != '
    ')  //过滤掉最后输入的换行符
    26             continue;
    27         if (!cin || temp[0] == '')  //结束最外层while循环的条件,输入为空,则temp的第一个字符为空字符,且cin输入失败,返回值为0
    28             break;
    29         else
    30             sayings[i] = temp;  //调用String & String::operator=(const char* pt)函数,调用方法为sayings[i].operator=(temp);
    31     }
    32     int total = i;  //保存输入的字符串的总个数
    33     if (total > 0)
    34     {
    35         cout << "Here are your sayings:" << endl;
    36         for (i = 0; i < total; i++)
    37             cout << sayings[i][0] << ": " << sayings[i] << endl;
    38         //sayings[i][0]表示调用char & String::operator[](int i)函数,调用方法为sayings[i].operator[](0),取出对象sayings[i].str中的第一个字符
    39         //cout<<sayings[i][0]首先调用对<<的重载函数,返回一个cout,之后再调用对[]的重载函数,
    40         //cout<<sayings[i]则直接调用对<<的重载函数了
    41 
    42         /* 接下来找到字符串最短的和字符串首字母拍在最前的字符串 */
    43         int shortest = 0;
    44         int first = 0;
    45         for (i = 0; i < total; i++)
    46         {
    47             if (sayings[i].length() < sayings[shortest].length())  //仅仅是比较对象中的字符串长度
    48                 shortest = i;
    49             if (sayings[i] < sayings[first])  //调用对<的重载函数
    50                 first = i;
    51         }
    52         cout << "Shortest saying:
    " << sayings[shortest] << endl;
    53         cout << "First alphabetically:
    " << sayings[first] << endl;
    54         cout << "The program used " << String::HowMany() << " String objects. Bye.
    ";
    55     }
    56     else
    57         cout << "No input! bye.
    ";
    58 
    59     system("pause");
    60     return 0;
    61 }
    user_main.cpp

    执行结果:

    在上面对输入运算符(>>)重载的函数中operator>>(istream & is, String st)使用了这个语句:st = temp; 其中st是一个String对象,temp是一个char型数组,所以该句会调用对等号的重载函数operator=(const char* pt)

    而temp是一个char型数组名,本身就是一个地址,所以直接赋值给char型指针是可以的,如下进行了验证:

     在构造函数中使用new时应该注意的问题(什么时候该编写复制构造函数和赋值重构函数) 

    01)如果在构造函数中使用new来初始化指针成员,则应该在析构函数中使用delete;
    02)new和delete应该互相兼容: new对应delete,new[]对应delete[];
    03)可以在一个构造函数中使用new初始化指针,而在另一个构造函数中将指针初始化为空(0或C++11中的nullptr)
         因为delete(无论是delete还是delete[])都可以用于空指针;
    04)如果在构造函数中使用new来初始化指针成员,则应该定义一个复制构造函数,通过深度复制将一个对象初始化为另一个对象
         ,具体的说,赋值构造函数一个分配足够的空间来存储复制的数据,并复制数据,而不是仅仅复制数据的地址,
         复制构造函数的结构与上述的复制构造函数结构类似;
    05)如果在构造函数中使用new来初始化指针成员,则应该定义一个赋值运算符重构函数,通过深度复制将一个对象复制给另一个对象
         具体的说,该方法完成以下操作:检查是否进行了自我赋值,释放成员指针以前指向的内存,复试数据而不是仅仅复制数据的地址
         返回一个指向调用对象的引用(提供this指针来完成),
         赋值运算符重构函数与上述的赋值运算符重构函数结构类似;

    以下列出了另个不正确的例子(构造函数)

     1 String::String()
     2 {
     3     str = "default string";  //错误,没有为str分配存储空间
     4     len = std::strlen(str);
     5 }
     6 String::String(const char* s)
     7 {
     8     len = std::strlen(s);
     9     str = new char;    //错误,没有使用[],分配的空间是不确定的
    10     std::strcpy(str, s);
    11 }

    对于第一个错误的实例,可以使用以下任意一种方法

     1 String::String()
     2 {
     3     len = 0;
     4     str = new char[1];
     5     str = '';
     6 }
     7 String::String()
     8 {
     9     len = 0;
    10     str = 0;  //直接给str赋值为空指针
    11 }
    12 String::String()
    13 {
    14     static const char* s = "C++";  //静态变量,只会执行一次
    15     len = std::strlen(s);
    16     str = new char[len + 1];
    17     std::strcpy(str, s);
    18 }

    包含类成员的类的逐成员复制 

    1 class Magazine
    2 {
    3 private:
    4     String title;  //使用自己定义的String类去定义对象
    5     string publisher;  //使用标准string类去定义对象
    6 };

    01)String类和string类都是要动态内存分配,这是否意味着也需要给Magazine类去编写复制构造函数和赋值运算符重构函数呢
    02)答案是不需要。
    03)如果您将一个Magazine对象复制或赋值给另一个Magazine对象,复制成员title时,将使用String类中的复制构造函数
         接下来将title赋值给另一个Magazine对象时,将使用String类的赋值重构函数;同理复制或赋值publisher将使用string类
         中的复制构造函数和赋值重构函数

     返回对象还是指向对象的引用?

    case1:返回类型为指向const对象的引用 

     1 Vector Max(const Vector v1, const Vector v2)  //返回类型为对象
     2 {
     3     if (v1.magval() > v2.magval())
     4         return v1;
     5     else
     6         return v2;
     7 }
     8 const Vector & Max(const Vector v1, const Vector v2)  //返回类型为指向对象的引用
     9 {
    10     if (v1.magval() > v2.magval())
    11         return v1;
    12     else
    13         return v2;
    14 }

    说明:

    01) 返回类型为对象则将调用复制构造函数,而返回指向对象的引用则不会;
    02) 引用指向的对象应该在调用执行函数时存在,即该类方法执行完毕后。返回的对象还存在;
    03) 由于v1和v2都是const常量,那么返回的类型也必须是const常量。

     case2:返回类型为指向非const对象的引用

    01)在等号运算符重构函数的声明中使用了指向非const对象的引用:String & operator=(const String & st)
       对于如下代码,解释其原因:
       String s1("Good Stuff");
       String s2,s3;
       s3 = s2 = s1;
       此时返回类型为String或者是String &均可,但是为了提高效率,使用String &,而返回类型不是const,是因为
       方法s2=s1中,具体的调用方法为s2.operator=(s1),operator=()返回一个指向s2的引用,可以对其进行修改
    02)在对<<运算符的重构函数声明中:friend ostream & operator<<(ostream & os, const String & st);
       使用了指向ostream对象的引用,是因为ostream类中不存在复制构造函数,所以必须使用引用。

    case3:返回类型为对象

    如果返回的对象时被调用函数中的一个局部变量,则不应该按引用方式返回它
    如果返回的对象时一个局部变量,那么返回类型只能是对象,如下代码:
    Vector force1(50,60);
    Vector force2(40,70);
    Vector net;
    net = force1+force2;   //将会调用复制构造函数来创建临时对象来表示返回值,后该临时对象被复制给net,这是无法避免的
    那么在编写对+运算符的重载函数时,返回值的类型只能是对象:
    Vector Vector::operator+(const Vector &b) const   //最后一个const表示不可修改调用该方法的对象中的数据
    {
    return Vector(x+b.x,y+b.y); //返回值为局部变量,所以返回值类型不能为引用
    }

    case4:返回类型为const对象

    将对+运算符的重载函数的返回值声明为const常量的好处为(const Vector operator+(const Vector &b) const):
    01) net = force1+force2; //合法
    02) force1+force2 = net; //非法

    指向对象的指针

    01)假如Class_name是类名,变量value的类型为Type_name,则指向对象的指针一般形式为:
    Class_name* pclass = new Class_name(value);
    将调用如下的构造函数:
    Class_name(Type_name);
    02)下面的初始化方式将调用默认构造函数:
    Class_name* ptr = new Class_name;
    03)指针和对象的小结:
      A 使用常规表示方法来声明指向对象的指针:
          String* glamour; //声明指向String类的指针
      B 可以将指针初始化为指向已有的对象:
          Stirng* first = &sayings[0];  //注:sayings[]是一个对象数组
      C 对类使用new将调用相应的构造函数来初始化新创建的对象:
       String* gleep = new String; //使用默认构造函数创建对象,并让指针gleep指向该对象
       String* glop = new String("my my my"); //使用String(const char*)构造函数来创建对象
       String* favorite new String(sayings[choice]); //使用String(const String &)构造函数来创建对象
      D 可以使用->运算符通过指针来访问类方法:
       String* ptr = &sayings[0]; //其中sayings[]是一个对象数组,ptr指向数组内的第一个对象
       ptr->length(); //使用ptr访问类方法length()
      E 可以对对象使用接触引用运算符(*)来获得对象:
       String* first = &sayings[0]; //first同样指向对象数组sayings[]内的第一个元素
       if (sayings[i] < *first) //通过*来获取first指向的对象,并调用对小于号的重载函数operator<()
       firts = &sanyings[i];

     在对象的基础上再谈new和delete 

    01)假如有如下cpp文件:
      class Act { ... };
      ...
      Act nice;   //在函数外定义的为静态变量,在整个程序执行期间都存在,对象nice即为静态对象
      //定义静态变量的方法:(1)在函数外定义 (2)在变量定义时使用关键字static
      ...
      int main()
      {
        Act* pt = new Act;   //创建指向Act的指针,并未pt分配内存,pt即为动态变量对象
        {
          Act up;     //up对象时自动变量,在该程序块执行完后消失
          ...
        }    //该程序块执行完后,将调用up对应的析构函数
        delete pt;     //对指针pt应用delete时,将调用*pt对应的析构函数
        ...
      }   //整个程序结束时,将调用静态对象nice对应的析构函数
    02)如果对象时由new创建的,则仅当显式使用delete删除对象时,其析构函数才会被调用

    2019-05-06 09:14 周一

    使用new定位运算符为指针对象分配内存空间,但是此版本有问题 m14

    第九章介绍了有关new定位运算符的相关知识

    问题一:

    JustTesting *pc1, *pc2;   //创建两个指向JustTesting对象的指针

    pc1 = new (buffer) JustTesting;   //使用定位new运算符返回buffer的(JustTesting对象的)地址给pc1,并使用默认构造函数的默认参数创建JustTesting对象
    pc2 = new JustTesting("Heap1", 20); //使用常规new运算符返回一个可以存储JustTesting对象中数据的首地址,并使用新的数据创建对象

    JustTesting *pc3, *pc4; //创建两个指向JustTesting对象的指针
    pc3 = new (buffer) JustTesting("Bad Idea",6); //再次使用new定位运算符,但是此时新对象中的数据会覆盖掉pc1指向的对象中的数据
    //解决方法:pc3 = new (buffer+sizeof(JustTesting)) JustTesting("Bad Idea",6);
    pc4 = new JustTesting("Heap2", 10); //再次使用new常规运算符为新的对象中的数据分配内存,此时不会覆盖掉任何数据

    问题二:

    //如果程序员使用定位new运算符为对象分配内存,那么一定要确保其析构函数被调用

    //释放内存,由于delete只能释放由常规new运算符创建的内存,由定位new运算符是不能直接使用delete释放内存的
    //即 delete pc1;和delete pc3;是会报错的,
    //解决方法是使用指向对象的指针显示的调用析构函数pc3->~JustTesting(); pc1->~JustTesting();

     1 //使用new定位运算符为指针对象分配内存空间,但是此版本有问题
     2 //第九章介绍了有关new定位运算符的相关知识
     3 #include <iostream>
     4 #include <string>
     5 #include <new>  //在#include <iostream>中就已经包含了new头文件,这里不加该句也可以
     6 
     7 using namespace std;
     8 
     9 const int BUF = 512;
    10 
    11 class JustTesting
    12 {
    13 private:
    14     string words;
    15     int number;
    16 public:
    17     /* 带默认参数构造函数的定义 */
    18     JustTesting(const string & s = "Just Testing", int n = 0)
    19     {
    20         words = s;
    21         number = n;
    22         cout << words << " was constructed
    ";
    23     }
    24     /* 析构函数定义 */
    25     ~JustTesting()
    26     {
    27         cout << words << " was destroyed
    ";
    28     }
    29     /* 普通类方法定义 */
    30     void Show() const   //最后的const表明调用该类方法的对象中的数据不可更改
    31     {
    32         cout << words << ", " << number << endl;
    33     }
    34 };
    35 
    36 int main()
    37 {
    38     char* buffer = new char[BUF];  //创建一个512字节的缓冲区,由于是用new创建的,所以是动态存储,该存储区位于堆中
    39                                    //一个char型变量占用一个字节
    40 
    41     JustTesting *pc1, *pc2;  //创建两个指向JustTesting对象的指针
    42 
    43     pc1 = new (buffer) JustTesting;  //使用定位new运算符返回buffer的(JustTesting对象的)地址给pc1,并使用默认构造函数的默认参数创建JustTesting对象
    44     pc2 = new JustTesting("Heap1", 20);  //使用常规new运算符返回一个可以存储JustTesting对象中数据的首地址,并使用新的数据创建对象
    45 
    46     cout << "Memory bloak addresses:
    " << "buffer: " << (void *)buffer << "  heap: " << pc2 << endl;
    47     cout << "Memory contents:
    ";
    48     cout << pc1 << ": ";  //此处打印(pc1的)地址
    49     pc1->Show();
    50     cout << pc2 << ": ";  //此处打印(pc2的)地址
    51     pc2->Show();
    52 
    53     JustTesting *pc3, *pc4;  //创建两个指向JustTesting对象的指针
    54     pc3 = new (buffer) JustTesting("Bad Idea",6);  //再次使用new定位运算符,但是此时新对象中的数据会覆盖掉pc1指向的对象中的数据
    55     //解决方法:pc3 = new (buffer+sizeof(JustTesting)) JustTesting("Bad Idea",6);
    56     pc4 = new JustTesting("Heap2", 10);  //再次使用new常规运算符为新的对象中的数据分配内存,此时不会覆盖掉任何数据
    57     cout << "Memory contents:
    ";
    58     cout << pc3 << ": ";  //此处打印(pc3的)地址
    59     pc3->Show();
    60     cout << pc4 << ": ";  //此处打印(pc2的)地址
    61     pc4->Show();
    62 
    63     //释放内存,由于delete只能释放由常规new运算符创建的内存,由定位new运算符是不能直接使用delete释放内存的
    64     //且 delete pc1;和delete pc3;是会报错的,
    65     //解决方法是显示的调用析构函数:pc3->~JustTesting();  pc1->~JustTesting(); 
    66     delete pc2;  //先删除后创建的指向对象的指针
    67     delete pc4;
    68     delete[] buffer;
    69 
    70     cout << "Done
    ";
    71 
    72     system("pause");
    73     return 0;
    74 
    75 }
    有问题的版本

    执行结果:这样执行的确是不会报错,但是该程序是有问题的,即没有释放(删除)pc3和pc1指向的对象

     1 //使用new定位运算符为指针对象分配内存空间,正确的版本
     2 //第九章介绍了有关new定位运算符的相关知识
     3 #include <iostream>
     4 #include <string>
     5 #include <new>  //在#include <iostream>中就已经包含了new头文件,这里不加该句也可以
     6 
     7 using namespace std;
     8 
     9 const int BUF = 512;
    10 
    11 class JustTesting
    12 {
    13 private:
    14     string words;
    15     int number;
    16 public:
    17     /* 带默认参数构造函数的定义 */
    18     JustTesting(const string & s = "Just Testing", int n = 0)
    19     {
    20         words = s;
    21         number = n;
    22         cout << words << " was constructed
    ";
    23     }
    24     /* 析构函数定义 */
    25     ~JustTesting()
    26     {
    27         cout << words << " was destroyed
    ";
    28     }
    29     /* 普通类方法定义 */
    30     void Show() const   //最后的const表明调用该类方法的对象中的数据不可更改
    31     {
    32         cout << words << ", " << number << endl;
    33     }
    34 };
    35 
    36 int main()
    37 {
    38     char* buffer = new char[BUF];  //创建一个512字节的缓冲区,由于是用new创建的,所以是动态存储,该存储区位于堆中
    39                                    //一个char型变量占用一个字节
    40 
    41     JustTesting *pc1, *pc2;  //创建两个指向JustTesting对象的指针
    42 
    43     pc1 = new (buffer) JustTesting;  //使用定位new运算符返回buffer的(JustTesting对象的)地址给pc1,并使用默认构造函数的默认参数创建JustTesting对象
    44     pc2 = new JustTesting("Heap1", 20);  //使用常规new运算符返回一个可以存储JustTesting对象中数据的首地址,并使用新的数据创建对象
    45 
    46     cout << "Memory bloak addresses:
    " << "buffer: " << (void *)buffer << "  heap: " << pc2 << endl;
    47     cout << "Memory contents:
    ";
    48     cout << pc1 << ": ";  //此处打印(pc1的)地址
    49     pc1->Show();
    50     cout << pc2 << ": ";  //此处打印(pc2的)地址
    51     pc2->Show();
    52 
    53     JustTesting *pc3, *pc4;  //创建两个指向JustTesting对象的指针
    54     pc3 = new (buffer + sizeof(JustTesting)) JustTesting("Bad Idea", 6); //再次使用new定位运算符这样就不会覆盖掉pc1指向的内存中的数据
    55     pc4 = new JustTesting("Heap2", 10);  //再次使用new常规运算符为新的对象中的数据分配内存,此时不会覆盖掉任何数据
    56     cout << "Memory contents:
    ";
    57     cout << pc3 << ": ";  //此处打印(pc3的)地址
    58     pc3->Show();
    59     cout << pc4 << ": ";  //此处打印(pc2的)地址
    60     pc4->Show();
    61 
    62     delete pc2;  //先删除后创建的指向对象的指针,释放在堆中创建的内存
    63     delete pc4;
    64     pc3->~JustTesting();  //显示的调用析构函数,以删除pc3指向的对象
    65     pc1->~JustTesting();  //显示的调用析构函数
    66     delete[] buffer;
    67 
    68     cout << "Done
    ";
    69 
    70     system("pause");
    71     return 0;
    72 
    73 }
    正确的版本

    执行结果:

  • 相关阅读:
    安装好php后找不到php.ini
    Nginx 和 PHP 的两种部署方式比较
    高性能Web服务之lnmp架构应用
    >/dev/null 2>&1的含义
    LC_ALL=C的含义
    深入理解PHP Opcode缓存原理
    iostat 监视I/O子系统
    sar 找出系统瓶颈的利器
    Linux常用命令汇总
    linux增加自定义path和manpath
  • 原文地址:https://www.cnblogs.com/YiYA-blog/p/10798159.html
Copyright © 2020-2023  润新知