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

YiYA-blog 2019-04-30 21:32 原文

目录

复习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是为了给空字符'\0'留出位置,new返回的是一个地址,所以str也是一个指针
15     std::strcpy(str, s);  //将指针字符串s复制给指针字符串str
16     //str=s; 这样做是不可以的,因为这样做只是复制了地址,而没有创建字符串副本
17     num_strings++;  //统计新建的对象的个数
18     std::cout << num_strings << ": \"" << str << "\"object created\n";//显式多少个对象已创建
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\n";//显式多少个对象已创建
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\n";
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 = '\0';    //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;\n";
  cout<<num_string<<" was left\n";
}


//在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\n"将会出现乱码。(因为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\n";
  }
  //此时再执行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 = '\0';
 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() != '\n') //过滤掉输入流中的换行符
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() != '\n')  //过滤掉最后输入的换行符
26             continue;
27         if (!cin || temp[0] == '\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:\n" << sayings[shortest] << endl;
53         cout << "First alphabetically:\n" << sayings[first] << endl;
54         cout << "The program used " << String::HowMany() << " String objects. Bye.\n";
55     }
56     else
57         cout << "No input! bye.\n";
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 = '\0';
 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\n";
23     }
24     /* 析构函数定义 */
25     ~JustTesting()
26     {
27         cout << words << " was destroyed\n";
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:\n" << "buffer: " << (void *)buffer << "  heap: " << pc2 << endl;
47     cout << "Memory contents:\n";
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:\n";
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\n";
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\n";
23     }
24     /* 析构函数定义 */
25     ~JustTesting()
26     {
27         cout << words << " was destroyed\n";
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:\n" << "buffer: " << (void *)buffer << "  heap: " << pc2 << endl;
47     cout << "Memory contents:\n";
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:\n";
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\n";
69 
70     system("pause");
71     return 0;
72 
73 }
正确的版本

执行结果:

推荐阅读