面试题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.