第6章 C++面向对象3

面试题25 复制构造函数与赋值函数有什么区别

【解析】
有3个方面的区别:
(1)复制构造是一个对象来初始化一块内存区域,这块内存就是新对象的内存区。例如
class A;
A a;
A b = a;//复制构造函数调用
A b(a); //复制构造函数调用

而赋值函数是对于一个已经被初始化的对象来进行operator=操作。例如
class A;
A a;
A b;
b = a; //赋值函数调用

(2)一般来说是在数据成员包含指针对象的时候,应付两种不同的处理需求:一种是复制指针对象,
一种是引用指针对象。复制构造函数在大多数情况下是复制,赋值函数则是引用对象。
(3)实现不一样。复制构造函数首先是一个构造函数表,它调用的时候是通过参数传进来的那个对象来初始化产生一个对象。
赋值函数则是把一个对象赋值给一个原有的对象,所以,如果原来的对象中有内存分配,要先把内存释放掉,而且还要检查
一下两个对象是不是同一个对象,如果是的话,就不做任何操作。


面试题26 编写类String的构造函数、析构函数和赋值函数

#include <iostream>
using namespace std;

class String
{
public:
    String(const char *str = nullptr);              //普通构造函数
    String(const String &other);                    //复制构造函数
    ~String();                                      //析构函数
    String &operator=(const String &other);         //赋值函数

private:
    char *m_string;                                 //私有成员,保存字符串
};
//普通构造函数
String::String(const char *str)
{
    cout << "Construcing=============" << endl;
    if(nullptr == str)                              //如果str为nullptr,存空字符串
    {
        m_string = new char[1];                     //分配一个字节
        *m_string = '\0';                           //将值赋值为字符串结束符
    }
    else
    {
        m_string = new char[strlen(str) +1];        //分配空间容纳str内容
        strcpy(m_string, str);                      //复制str到私有成员
    }
}
//复制构造函数
String::String(const String &other)
{
    cout << "Constructing Copy=======" << endl;
    m_string = new char[strlen(other.m_string) + 1];//分配空间容纳str内容
    strcpy(m_string, other.m_string);               //复制str到私有成员
}
//析构函数
String::~String()
{
    cout << "Destructing=============" << endl;
    if(m_string != nullptr)                         //如果m_string不为nullptr,释放堆内存
    {
        delete []m_string;
        m_string = nullptr;                         //释放后置为nullptr
    }
}
//赋值函数
String& String::operator=(const String &other)
{
    cout << "Operate = Function======" << endl;
    if(this == &other)                              //如果对象与other是同一个对象
    {
        return *this;                               //直接返回本身
    }

    delete []m_string;                              //释放堆内存
    m_string = new char[strlen(other.m_string) + 1];
    strcpy(m_string, other.m_string);

    return *this;
}

int main()
{
    String a("hello");                              //调用普通构造函数
    String b("world");                              //调用普通构造函数
    String c(a);                                    //调用复制构造函数
    c = b;                                          //调用赋值函数

    return 0;
}

【解析】
(1)普通构造函数:这里判断了传入的参数是否为NULL。如果是NULL,初始化一个字节的空字符串(包括结束符'\0');
如果不是,分配足够大小长度的堆内存来保存字符串。
(2)复制构造函数:只是分配足够小长度的堆内存来保存字符串。
(3)析构函数:如果类私有成员m_String不为NULL,释放m_String指向的堆内存,并且为了避免产生野指针,将m_String赋为NULL。
(4)赋值函数:首先判断当前对象与引用传递对象是否是同一个对象,如果是,不做操作,直接返回;否则,先释放当前对象的堆内存,然后分配足够大小长度的埣内存复制字符串。
程序的执行结果如下。

这里代码第65~68行会发生构造函数以及赋值函数的调用,而析构函数的调用发生在main()函数退出时。


面试题27 了解C++类各成员函数的关系

写出下面代码的输出结果:

#include <iostream>
using namespace std;

class A
{
private:
    int num;
public:
    A()
    {
        cout << "Default constructor======" << endl;
    }
    ~A()
    {
        cout << "Desconstructor=====num===" << num << endl;
    }
    A(const A &a)
    {
        cout << "Copy constructor=========" << endl;
    }
    void operator=(const A &a)
    {
        cout << "Overload operator==========" << endl;
    }
    void setNum(int n)
    {
        num = n;
    }
};

int main()
{
    A a1;
    A a2(a1);
    A a3 = a1;
    A &a4 = a1;
    a1.setNum(1);
    a2.setNum(2);
    a3.setNum(3);
    a4.setNum(4);
    return 0;
}

【解析】
代码第33行,定义了一个对象a1,调用的是默认的构造函数。
代码第34行,用a1初始化一个对象a2,调用的是复制构造函数。
代码第35行,同上。注意,这里不是调用赋值函数,这里属于对象a3的初始化,而不是赋值,若要调用赋值,必须为如下形式。
A a3;
a3 = a1;
代码第36行,定义a4为a1的一个引用,不调用构造函数或赋值函数。
代码第37~40行,调用各个对象的SetNum()成员函数为私有成员num赋值。这里注意,
由于a4为a1的引用,因此a4.SetNum()实际上和a1.SetNum()等同。
当main()函数退出时,对象析构顺序与调用构造函数顺序相反,依次为a3, a2, a1。
【答案】
程序执行结果:


面试题28 C++类的临时对象

已知class B以及Play()函数定义如下:

class B
{
public:
    B()
    {
        cout << "default constructor=======" << endl;
    }
    ~B()
    {
        cout << "destructed=====this=" << this << endl;
    }
    B(int i): data(i)//初始化私有成员
    {
        cout << "constructed by parameter=i=" << i << "  this==" << this << endl;
    }

private:
    int data;
};

B Play(B b)//复制一个b临时对象,return时会析构掉这个临时对象
{
    cout << "Play============&b==" << &b << endl;
    return b;
}

分析下面两个main()函数的输出。
第1个main()函数:

int main(int argc, char *argv[])
{
    B t1 = Play(5);//t1=调用默认的复制构造函数
    cout << "&t1=================" << &t1 << endl << endl;
    B t2 = Play(t1);
    cout << "&t2=================" << &t2 << endl << endl;
    return 0;
}


第2个main()函数:

int main(int argc, char *argv[])
{
    B t1 = Play(5);//t1=调用默认的复制构造函数
    cout << "&t1=================" << &t1 << endl << endl;
    B t2 = Play(10);//t2=调用默认的复制构造函数
    cout << "&t2=================" << &t2 << endl << endl;
    return 0;
}

【解析】
这里调用Play()函数时,有两种参数类型的传递方式。
如果是传递的是整形数,那么其在函数栈中首先会调用带参数的构造函数,产生一个临时对象,然后返回前(在return代码执行时)调用类的复制构造函数,生成临时对象(这样函数返回后主函数中的对象就被初始化了),最后这个临时对象会在函数返回时(在return代码执行后)析构。如果传递的是参数B类的对象,那么只有第一步与上面的不同,就是其函数栈中会首先调用复制构造函数产生一个临时对象,其余步骤完全相同。
可以看出,两种情况的区别是采用不同的方法生成临时对象(一个是调用带参数的构造函数,另一个是调用复制构造函数)。
在第一个main()函数中,对象t1使用了传入整形数的方式调用Play()函数,而对象t2使用了传入B的对象的方式调用Play()函数,
在第二个main()函数中,对象t1和t2都使用了传整形数的方式调用Play()函数,第一个main()函数下的执行结果为:


第二个main()函数下的执行结果为:

为了更加详细地说明结果,在B类中加入一个自定义的复制构造函数:

    B(const B &b)
    {
        cout << "copy constructor===this==" << this << " &b=" << &b << " b.data=" << b.data << endl;
        data = b.data;
    }


第一个main()函数下的执行结果为:


第二个main()函数下的执行结果为:

此时,两个main()函数的输出结果只有第6行不一样,也就是由于传入不同类型参数(整形与对象类型),这个由采取了不同的方式生成临时对象而导致的。

面试题29 复制构造函数和析构函数

#include <iostream>
using namespace std;

class A
{
public:
    A()
    {
        cout << "This is A Construction====this=" << this << endl;
    }
    virtual ~A()
    {
        cout << "This is A destruction======this=" << this << endl;
    }
};

A fun()
{
    A a;
    cout << "fun===============&a====" << &a << endl;
    return a;
}

int main()
{
    {
        A a;
        cout << "==&a=======1=========" << &a << endl;
        a = fun();
        cout << "==&a=======2=========" << &a << endl;
    }
    return 0;
}

不是说构造函数和析构函数成对的吗?为什么少了一个构造函数呢?
【解析】
构造函数和析构函数确实是成对的,构造函数除了普通构造函数之外,还包括复制构造函数。上面的程序中一共构造了3个对象,分别是main()函数中的a、fun()函数中的a以及fun返回时生成的临时对象。前两个对象都是普通构造函数构造的,而由fun返回时生成的临时对象是由复制构造函数生成的。上面的程序中只是在普通构造函数中打印了信息。加入自定义复制构造函数和赋值函数,如下所示。

    A(A &a)
    {
        cout << "This is A Copy Construction==&a=" << &a << endl;
    }
    A& operator =(const A &a)
    {
        cout << "This is an assignment function==&a=" << &a << " this=" << this << endl;
        return *this;
    }

程序结果执行如下:

可以看出,此时的构造函数和析构函数都被执行了3次。另外,在main()函数中把fun()返回的临时对象赋给了对象a,此时会调用赋值函数。

【答案】
构造函数和析构函数确实是成对的,原程序中的fun返回时生成的临时对象是由复制构造函数生成的。这里没有在复制构造函数中输出信息(编译器生成默认复制构造函数),所以看上去构造函数比析构函数少了一个。


面试题30 看代码写结果——C++静态成员和临时对象

#include <iostream>
using namespace std;

class human
{
public:
    human()
    {
        human_num++;
    }
    static int human_num;
    ~human()
    {
        human_num--;
        print();
    }
    void print()
    {
        cout << "human num is: " << human_num << endl;
    }
};

int human::human_num = 0;

human f1(human x)//这里会调用一次复制构造函数,
{
    x.print();
    return x;//这里理论上要生成一个临时地象,
}

int main()
{
    human h1;
    h1.print();
    human h2 = f1(h1);//h2是复制构造函数构造的对象吗
    h2.print();
    return 0;
}

【解析】
这个程序的human类有一个静态成员human_num,每执行一次,普通构造函数human_num加1,每执行一次,析构函数human_num减1.注意,在f1()函数中会使用默认的复制构造函数,而默认的复制构造函数没有对human_num处理。
代码第34行,只构造了对象h1(调用普通构造函数),因此打印1.
代码第35行使用值传递参数的方式调用了f1()函数,这里分为3步:
(1)在f1()函数内首先会调用复制构造函数生成一个临时对象,因此代码第27行打印1.
(2)f1()函数内调用复制构造函数,给main的对象h2初始化(复制临时对象)。
(3)f1()函数返回后,临时对象发生析构,此时human的静态成员human_num为0,打印出0.
代码第36行打印的还是0.
main()函数结束时有h1和h2两个对象要发生析构,所以分别打印出-1和-2。
程序的意图其实很明显,就是静态成员用human_num记录类human的实例数。然而,由于默认的复制构造没有对静态成员操作,导致了执行结果的不正确。这里可以通过添加一个自定义的复制构造函数解决。
human(human &h)
{
    human_num++;
}
此时human_num就能起到应有的作用了。
【答案】
程序执行结果


面试题31 什么是临时对象?临时对象在什么情况下产生

【解析】
当程序员之间进行交谈时,经常把仅仅需要一小段时间的变量称为临时变量。例如在下面的swap()函数里:
void swap(int &a, int &b)
{
    int temp = a;
    a = b;
    b = temp;
}
通常称temp为临时变量。但是在C++里,temp根本不是临时变量。实际上,它只是一个函数的局部变量。
真正的临时对象是看不见的,它不会出现在程序代码中。大多数情况下,它会影响程序执行的效率,所以有时想避免临时对象的产生。它通常在以下两种情况下产生。
(1)参数按值传递。
(2)返回值按值传递。
参考下面的代码。

#include <iostream>
using namespace std;

class Test
{
public:
    Test(): num(0){} //默认构造函数,
    Test(int number):num(number) {}//带参数的构造函数,
    void print()
    {
        cout << "num = " << num << endl;
    }
    ~Test() //析构函数,打印this指针和私有成员num
    {
        cout << "destructor: this = " << this << ", num = " << num << endl;
    }
private:
    int num;
};

void fun1(Test test) //参数按值传递,
{
    test.print();
}

Test fun2()
{
    Test t(3);
    return t;           //返回值按值传递,
}

int main(int argc, char *argv[])
{
    Test t1(1);
    
    fun1(t1);   //对象传入,
    fun1(2);    //整型数2传入,
    t1 = fun2();

    return 0;
}


程序中的fun1()函数的参数是按值传递的,fun2()函数的返回值是按值传递的。在Test类的析构函数中打印了this指针的值,下面是程序执行结果。

这里代码第36行和第37行使用了两种类型的参数传入fun1()函数。它们都会生成临时变量,不同点是要采用不同的方式:
第36行的调用使用了复制构造函数创建临时变量,而第37行调用使用的则是带参数的构造函数创建临时变量。
如何避免临时变量的产生呢?可以使用按引用传递代替按值传递。例如,把上面的fun1()函数改变成如下形式。
void fun1(Test &test)
{
    test.print();
}
这样,fun1()函数的参娄就是一个已经存在的对象引用,此时整形值是不能传进来的。执行下面的主程序。
int main(1
{
    Test t1();
    fun1(t1);//对象引用传入
    return 0;
}
程序的执行结果如下:


可以看到,此时就不会产生临时对象了。
注意:引用必须有一个实在的、可引用的对象,否则引用是错误的。因此,在没有实在的、可引用的对象的时候,只有依赖于临时对象。


面试题32 为什么C语言不支持函数重载而C++能支持

什么是函数重载?为什么C语言不支持函数重载,而C++能支持函数重载?
【解析】
函数重载是用来描述同名函数具有相同或者相似的功能,但数据类型或者是参数不同的函数管理操作。例如,要进行两种不同数据类型的和的操作,在C语言里需要写两个不同名称的函数来进行区分。
int add1(int a, int b)
{
    return a+b;
}
float add2(float a, float b)
{
    return a + b;
}
上面的代码写得不太好,这两个具备相似操作的函数,却给它们取了两个不同的名字,这样做不便于管理。因此,C++为了方便程序员编写,引入了函数重载的概念。例如下面的代码。

#include <iostream>
using namespace std;

class Test
{
public:
    int add(int x, int y) //相加,传入参数以及返回值都是int
    {
        return x + y;
    }
    float add(float x, float y) //相加,传入参数以及返回值都是float
    {
        return x + y;
    }
};

int add(int x, int y)//相加,传入参数以及返回值都是int
{
    return x + y;
}

float add(float x, float y) //相加,传入参数以及返回值都是float
{
    return x + y;
}

int main()
{
    int i = add(1, 2);
    float f = add(1.1f, 2.2f);
    Test test;
    int i1 = test.add(3, 4);
    float f1 = test.add(3.3f, 4.4f);

    cout << "i = " << i << endl;
    cout << "f = " << f << endl;
    cout << "i1 = " << i1 << endl;
    cout << "f1 = " << f1 << endl;

    return 0;
}


上面的程序中使用了全局函数和类成员函数的重载,代码第29~38行是对它们的调用与测试。可以看到,在C++中可以根据传入参数类型和返回类型来区分不同的重载函数。
C语言不支持函数重,C++却支持,为什么呢?这是因为C++的重载函数经过编译器处理之后,两个函数的符号是不相同的。例如代码第17行的add函数,经过 处理后变成了_int_add_int_int之类,而后都变成了_float_add_float_float之类。这样的名字包含了函数名、函数参数数量及返回类型信息,C++就是靠这种机制来实现函数重载的。
【答案】
函数重载是用来描述同名函数具有相同或者相似的功能,但数据类型或者是参数不同的函数管理操作。
函数名经过C++编译器处理后包含了原函数名、函数参数数量及返回类型信息,而C语言不会对函数名进行处理。


面试题33 判断题——函数重载的正确声明

【解析】
A 错误。第二个函数被视为重复声明,第二个声明中的const修饰词会被忽略。
B 错误。第二个声明是错误的,因为单就函数的返回值 而言,不足以区分两个函数的重载。
C 正确。这是合法的声明,reset()函数被重载。
D 错误,第二个函数声明是错误的,因为在一组重载函数中,只能有一个函数被指定为extern "C".
【答案】
C 正确。


面试题34 重载和覆写有什么区别

【解析】
重载(overriding)是指子类改写了父类的方法,覆写(overloading)是指同一个函数的不同版本之间参数不同。
重载是编写一个与已有函数同名但是参数表不同(参数数量或参数类型不同)的方法,它具有如下所示的特征。
(1)方法名必须相同。
(2)参数列表必须不相同,与参数列表的顺序无关。
(3)返回值类型可以不相同。
覆写是派生类重写基类的虚函数,它具有如下所示的特征。
(1)只有虚方法和抽象方法才能够被覆写。
(2)相同的函数名。
(3)相同的参数列表。
(4)相同的返回值类型。
重载是一种语法规则,由编译器在编译阶段完成,不属于面向对象的编程;而覆写是由运行阶段决定的,是面向对象编程的重要特征。


面试题35 编程题——MyString类的编写

对于下面的类MyString,要求重载一些运算符后可以计算表达式a=b+c;
其中,a,b,c都是类MyString的对象,请重载相应的运算符并编写程序测试。


【解析】
为了实现a=b+c;这个表达式,需要重载两个运行会,一个是‘+’运算符,用于b+c,另一个是‘=’运行符,用于对象a的赋值。程序代码如下。

#include <iostream>
using namespace std;

class MyString
{
public:
    MyString(const char *s)   //参数为字符指针的构造函数,
    {
        str = new char[strlen(s) + 1];
        strcpy(str, s);
    }

    ~MyString()         //析构函数释放str堆内存,
    {
        delete []str;
    }

    MyString& operator=(MyString &string)   //赋值函数,重载=,
    {
        if(this == &string)
        {
            return *this;
        }
        if(str != nullptr)  //释放内存,
        {
            delete []str;
        }
        str = new char[strlen(string.str) + 1];//申请内存,
        strcpy(str, string.str);                 //复制字符串内容,
        return *this;
    }


    MyString& operator+(MyString &string)       //重载+(改变被加对象),
    {
        char *temp = str;
        str = new char[strlen(temp) + strlen(string.str) + 1];
        strcpy(str, temp);  //复制第一个字符串   str成员改变,
        delete []temp;
        strcat(str, string.str);    //连接第二个字符串,
        return *this;
    }

    /*
    MyString& operator+(MyString &string)   //重载+(不改变被加对象),
    {
        MyString *pString = new MyString("");//堆内存中构造对象,
        pString->str = new char[strlen(str) + strlen(string.str) + 1];
        strcpy(pString->str, str);          //复制第一个字符串  str没有改变,
        strcat(pString->str, string.str);   //连接第二个字符串,
        return *pString;                    //返回堆中的对象,
    }
    */

    void print()
    {
        cout << str << endl;
    }
private:
    char *str;
};

/*
//MyString类的友员,要求str成员是public访问权限
MyString& operator +(MyString &left, MyString &right) //重载+(不改变被加对象)
{
    MyString *pString = new MyString("");
    pString->str = new char[strlen(left.str) + strlen(right.str) + 1];
    strcpy(pString->str, left.str);
    strcat(pString->str, right.str);
    return *pString;
}
*/
int main(int argc, char *argv[])
{
    MyString a("hello ");
    MyString b("world");

    MyString c("");
    c = c + a;  //先做加法,再赋值,
    c.print();
    c = c + b;  //先做加法,再赋值,
    c.print();

    c = a + b;
    a.print();
    c.print();

    return 0;
}

这里有3个版本的‘+’操作符重载函数,它们都是调用strcpy复制第一个字符串,然后调用strcat连接第二个字符串。
第1个版本返回*this对象,它改变了被加对象的内容。使用第一个'+'操作符重载函数版本的执行结果:

第2个版本和第3个版本都是返回堆中构造的对象,它们没有改变被加对象内容。它们的区别如下。
(1)第2个版本属于类的成员函数,而第3个版本是类的友员函数。
(2)第2个版本的参数为1个,而第3个版本的参数为2个,因为友员函数不含有this指针。
(3)由于类的友员函数不能使用私有成员,因此在这里使用第3个版本时需要把str成员的访问权限改为public。
使用这两个‘+’操作符重载函数版本的执行结果:


面试题36 编程题——各类运算符重载函数的编写

(用C++实现一个String类,它具有比较,连接,输入,输出功能。并且请提供一些测试用例说明如何使用这个类。不能用MFC,STL以及其它库。)
【解析】
要实现本题要求的功能,需要重载下面的运行符。
(1)<, >, ==和!=比较运行符。
(2)+=连接运算符以及赋值 运行符
(3)<<输出运算符以及>>输入运算符。
根据分析,可得到如下String类(String.h文件)的定义。

#ifndef STRING06_36_H
#define STRING06_36_H

#include <iostream>
using namespace std;

class String
{
public:
    String();                                                           //默认构造函数,
    String(int n, char c);                                              //普通构造函数,
    String(const char *source);                                         //普通构造函数,
    String(const String& s);                                            //复制构造函数,
    String& operator =(char* s);                                        //重载=,实现字符串赋值,
    String& operator =(const String& s);                                //重载=,实现对象赋值,
    ~String();                                                          //析构函数,
    char& operator[](int i);                                            //重载[],实现数组运算,
    const char& operator[](int i) const;                                //重载[],实现数组运算(对象为常量),
    String& operator +=(const String& s);                               //重载+=,实现与字符串相加,
    String& operator +=(const char *s);                                 //重载+=,实现与对象相加,
    friend ostream& operator <<(ostream &out, String& s);               //重载<<,实现输出流,
    friend istream& operator >>(istream& in, String& s);                //重载>>,实现输入流,
    friend bool operator < (const String& left, const String& right);   //重载<,
    friend bool operator > (const String& left, const String& right);   //重载>,
    friend bool operator == (const String& left, const String& right);  //重载==,
    friend bool operator != (const String& left, const String& right);  //重载!=,
    char* getData();                                                    //获取data指针,
    size_t length();                                                    //获取字符串长度,

private:
    size_t size;                                                        //data表示的字符串长度,
    char *data;                                                         //指向字符串数据,
};


#endif // STRING06_36_H

为了实现与对象操作和与字会串操作,=和+=运算符的重载函数都有两个,它们的参数分别为String对象引用和字符指针,String.h文件的21~26行声明运行符重载函数都是友员,并且这些函数所重载的运算符都是双目运算符,因此参数是两个。也可以把这些友员函数改为成员函数,此时参数是一个。还有一点需要注意:输入输出流操作符的重载最好是声明为友员函数。
String类声明的所有函数实现在String.cpp文件中,下面是String.cpp的清单。

#include "string06_36.h"
#include <string.h>

//默认构造函数,
String::String()
{
    data = new char[1];
    *data = '\0';           //空字符串只含有'\0'一个元素,
    size = 0;
}
//普通构造函数,
String::String(int n, char c)//含有n个相同字符的字符串,
{
    data = new char[n + 1];
    size = n;
    char *temp = data;//保存data,
    while(n--)//做n次赋值,
    {
        *temp++ = c;
    }
    *temp = '\0';
}
//普通构造函数,
String::String(const char *source)//字符串内容与source相同,
{
    if(nullptr == source)//source为nullptr,
    {
        data = new char[1];
        *data = '\0';//将data赋为空字符串,
        size = 0;
    }
    else
    {
        size = strlen(source);//source不为nullptr,
        data = new char[size + 1];
        strcpy(data, source);//复制source字符串,
    }
}
//复制构造函数,
String::String(const String& s)//字符串内容与对象s的相同,
{
    data = new char[s.size + 1];
    strcpy(data, s.data);
    size = s.size;
}
//重载=,实现字符串赋值,
String& String::operator =(char* s)//目标为字符串,
{
    if(data != nullptr)
    {
        delete []data;
    }
    size = strlen(s);
    data = new char[size + 1];
    strcpy(data, s);//复制目标字符串,
    return *this;
}
//重载=,实现对象赋值,
String& String::operator =(const String& s)//目标为String对象,
{
    if(this == &s)//如果对象s就是自己,直接返回*this,
    {
        return *this;
    }
    if(data != nullptr)//释放data堆内存,
    {
        delete []data;
    }
    size = strlen(s.data);
    data = new char[size + 1];//分配内存,
    strcpy(data, s.data);//复制对象s的字符串成员,
    return *this;
}
//析构函数,
String::~String()
{
    if(data != nullptr) //data不为nullptr,释放堆内存,
    {
        delete []data;
        data = nullptr;
        size = 0;
    }
}
//重载[],实现数组运算,
char& String::operator[](int i)//[]重载,
{
    return data[i];//取数组下标为i的字符元素,
}
//重载[],实现数组运算(对象为常量),
const char& String::operator[](int i) const
{
    return data[i];
}
//重载+=,实现与字符串相加,
String& String::operator +=(const String& s)//连接对象s的字符串成员,
{
    size_t len = size + s.size + 1;
    char *temp = data;
    data = new char[len];//申请足够的堆内存来存放连接后的字符串,
    size = len - 1;
    strcpy(data, temp);//复制原来的字符串,
    strcat(data, s.data);//连接目标对象内的字符串成员,
    delete []temp;
    return *this;
}
//重载+=,实现与对象相加,
String& String::operator +=(const char *s)//连接s字符串,
{
    if(s == nullptr)
    {
        return *this;
    }
    size_t len = size + strlen(s) + 1;
    char *temp = data;
    data = new char[len];//申请足够的堆内存来存放连接后的字符串,
    size = len - 1;
    strcpy(data, temp);//复制原来的字符串,
    strcat(data, s);//连接目标字符串,
    delete []temp;
    return *this;
}
size_t String::length()
{
    return size;
}
//重载<<,实现输出流,
ostream& operator <<(ostream &out, String& s)//打印对象s内字符串成员的所有字符元素,
{
    for(int i = 0; i < s.length(); i++)
    {
        out << s[i] << " ";//输出字符串中每一个字符元素,
    }
    return out;
}
//重载>>,实现输入流,
istream& operator >>(istream& in, String& s)
{
    char p[50];
    in.getline(p, 50);//从输入流接收最多50个字符,
    s = p;//调用赋值函数,
    return in;
}
//重载<,
bool operator < (const String& left, const String& right)
{
    int i = 0;
    while(left[i] == right[i] && left[i] != 0 && right[i] != 0)
    {
        i++;
    }
    return (left[i] - right[i]) < 0 ? true : false;
}
//重载>,
bool operator > (const String& left, const String& right)
{
    int i = 0;
    while(left[i] == right[i] && left[i] != 0 && right[i] != 0)
    {
        i++;
    }
    return (left[i] - right[i]) > 0 ? true : false;
}
//重载==,
bool operator == (const String& left, const String& right)
{
    int i = 0;
    while(left[i] == right[i] && left[i] != 0 && right[i] != 0)
    {
        i++;
    }
    return (left[i] - right[i]) == 0 ? true : false;
}
//重载!=,
bool operator != (const String& left, const String& right)
{
    int i = 0;
    while(left[i] == right[i] && left[i] != 0 && right[i] != 0)
    {
        i++;
    }
    return (left[i] - right[i]) != 0 ? true : false;
}
//获取data指针,
char* String::getData()
{
    return data;
}

由于以上的代码清单中有详细的注释,因此这里就不再赘述了。另外还有两点需要说明:
友员函数不能访问String类的私有成员,但由于重载了[]操作符,所以采取使用对象索引(left[i]和right[i])的方式访问。
不能使用字符串复制(私有成员data不能访问),而是调用赋值 函数给对象s赋字符串的内容
测试代码如下:

int main()
{
    String str(3, 'a');
    String str1(str);//这种调用有什么问题,
    String str2("scott");
    String str3;
    cout << "str:=" << str << endl;
    cout << "str1:=" << str1 << endl;
    cout << "str2:=" << str2 << endl;
    cout << "str3:=" << str3 << endl;

    str3 = str2;
    cout << "str3:=" << str3 << endl;
    str3 = "12ab";
    cout << "str3:=" << str3 << endl;

    cout << "str3[2]:=" << str3[2] << endl;

    str3 += "chan";
    cout << "str3:=" << str3 << endl;
    str3 += str1;
    cout << "str3:=" << str3 << endl;

    cin >> str1;
    cout << "str1:=" << str1 << endl;

    String t1 = "1234";
    String t2 = "1234";

    String t3 = "12345";
    String t4 = "12335";

    cout << "t1 == t2 ? " << (t1 == t2) << endl;
    cout << "t1 < t3 ? " << (t1 < t3) << endl;
    cout << "t1 > t4 ? " << (t1 > t4) << endl;
    cout << "t1 != t4 ? " << (t1 != t4) << endl;

    return 0;
}


测试程序执行结果:


面试题37 看代码写输出——new操作符重载的使用

下面程序中主函数的new是类中new操作符重载。但是new后面只有一个参数0xa5,而类中函数的声明有两个参数。怎么会调用这个类的呢?

#include <memory.h>
#include <stdlib.h>
#include <iostream>

class Blanks
{
public:
    void *operator new(size_t stAllocateBlock, char chInit);
};
void *Blanks::operator new(size_t stAllocateBlock, char chInit)
{
    void *pvTemp = malloc(stAllocateBlock);
    if(pvTemp != nullptr)
        memset(pvTemp, chInit, stAllocateBlock);
    return pvTemp;
}

int main()
{
    Blanks *a5 = new(0xa5) Blanks;
    std::cout << "a5======" << a5 << std::endl;
    return a5 != nullptr;
}


【解析】
这里有以下几点需要说明。
重载new操作符第一个参数必须是size_t类型的,并且传入的值就是类的大小。本题中类的大小为1.如果类中含有一个int类型成员(int占4个字节),
那么参数stAllocateBlock的值为4.
代码第20行中的0xa5表示第二个参数的大小,也就是chInit为0xa5。
代码第14行,用chInit初始化分配的那块内存。
当执行代码第20行时,首先调用Blanks重载的new操作符函数,然后使用默认的构造函数初始化对象,最后用这个Blanks对象地址初始化a5.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 《C面向对象程序设计第三版答案》是由谭浩强编写的一本与C语言相关的教材辅导答案。C面向对象程序设计是计算机科学中的一门重要课程,谭浩强作为资深教授和编程专家,他撰写的书籍在编程领域拥有很高的权威性。 这本教材答案为学习者提供了对应教材《C面向对象程序设计第三版》的习题答案和思考指导。习题是帮助学生巩固所学知识和提升编程能力的重要方式,通过对答案的学习,学生可以更好地理解并运用相关知识。学习者可以通过对比答案,分析解题思路、吸收优秀的编程风格和技巧,从而提高编程水平。 《C面向对象程序设计第三版答案》按照教材的节顺序,详细解答了各个节的习题,包括程序设计题、思考题、应用题等,涵盖了从基础的语法使用到复杂的程序设计技巧,旨在帮助学生全面理解并掌握C语言的面向对象编程思想和方法。 除了提供答案,这本教材还包括了一些习题的思考指导,指导学生如何分析问题、拆解问题、确定解决步骤等。这些思考指导帮助学生培养编程思维和解决问题的能力,使他们能够独立思考和解决实际编程任务。 总之,《C面向对象程序设计第三版答案》是一本为学习C语言面向对象程序设计的学生提供的辅助资料,它不仅提供了习题答案,还包括了思考指导,帮助学生提高编程水平和解决实际问题的能力。 ### 回答2: 《C++面向对象程序设计(第3版)》是计算机科学与技术专业学生的主要教材之一,由谭浩强编写。这本书全面介绍了C++编程语言的面向对象编程思想和相关的概念、原则与技巧。 该教材内容分为15,首先从C++的基本概念和语法入手,然后逐渐介绍了面向对象编程的思想和实现。每的结尾都提供了习题和答案,帮助学生巩固所学知识。 《C++面向对象程序设计(第3版)》的答案是谭浩强根据书中习题所提供的参考答案。这些答案精确明确,清晰易懂,配有详细的解释和示范代码。通过阅读和理解这些答案,学生可以加深对所学知识的理解,提高自己的编程技能。 同时,这本书还提供了大量的示例代码和实践案例,帮助学生将理论知识应用于实际的编程项目中。通过实践,学生可以更好地理解面向对象编程的思想和方法,并培养自己的分析和解决问题的能力。 总之,《C++面向对象程序设计(第3版)》是一本权威性、系统性和实用性并存的教材。通过学习这本书,学生可以全面掌握C++编程语言和面向对象编程的相关知识,提高自己的编程能力,并为将来的实际工作打下坚实的基础。 ### 回答3: 《C++面向对象程序设计》(第三版)是谭浩强所著的一本教材,该教材主要介绍了C++面向对象程序设计的基本概念、语法和技巧。全书共分为10个节,涵盖了面向对象程序设计的各个方面。 第一介绍了C++的发展历程以及面向对象程序设计的基本概念和特点。第二详细讲解了C++的基本语法和常用数据型。第三重点介绍了C++中的和对象的概念,并通过具体的示例演示了如何定义和使用。 第四讲解了C++的继承和派生,介绍了单继承和多继承的概念以及如何定义和使用派生。第五介绍了C++中的多态性,包括静态多态和动态多态的概念以及如何使用虚函数实现动态绑定。 第六讲解了C++中的运算符重载型转换,通过实例说明了如何重载运算符型转换函数。第七介绍了C++中的异常处理机制,讲解了异常的概念和处理方法。 第八讲解了C++中的文件操作,包括输入输出流、文件读写以及文件指针的相关知识。第九介绍了C++的模板和泛型编程,包括函数模板和模板的定义和使用。 第十介绍了C++中的标准模板库(STL),包括容器、迭代器、算法和函数对象等的使用方法。 《C++面向对象程序设计》(第三版)通过简明扼要的语言和生动具体的示例,全面而深入地介绍了C++面向对象程序设计的基本概念和技巧,适合初学者学习和参考。同时,该教材也提供了丰富的练习题和案例,供读者巩固和应用所学知识。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值