【1】为什么空类可以创建对象呢?

示例代码如下:

  1. #include <iostream>
  2. using namespace std;
  3. class Empty
  4. {
  5. };
  6. void main()
  7. {
  8. Empty obj1;
  9. cout << sizeof(Empty) << endl; // 1
  10. }

让我们先看看这个例子。既然都没有构造函数,怎么实现对象obj1的构建呢?

哦,经过大脑的回旋式搜索,忆得有一本书上说过,当用户定义一个空类(如上)时,编译器就会为这个类默认生成六个方法。

既然是编译器默认完成的工作,那我们只要知道具体是那些方法,其余就学习编译器的原理了。

那么,编译器生成了那六个方法:示例代码如下:

  1. class Empty
  2. {
  3. public:
  4. Empty(); // 默认构造方法
  5. Empty(const Empty &); // 拷贝构造函数
  6. ~Empty(); // 析构函数
  7. Empty &operator=(const Empty &); // 赋值构造函数
  8. Empty *operator &(); // 取地址
  9. const Empty * operator&() const; // 常对象取地址
  10. };

OK,这就是默认生成的那六个方法。其中前四个是经常谈及到的,也是平常定义一个类时,尤其要慎重考虑的。

到这里,也许有人还看出了点问题。那就是为什么空类的大小是1呢??呵呵~~编译器搞的鬼?其实也不是随随便便搞的鬼,我们说:存在即是合理的!这样做肯定有它的道理。

大家试想一个极端问题,仍然是上面这个空类,我们定义一个语句:Empty ar[10]; // 一个包含10个Empty对象的数组。

好啦,如果sizeof(Empty) == 0,那么我们如何区分数组中的十个元素呢??你想想这是不是狭隘的表现吗?

所以说,为了更完善,更健壮,更伟大。编译器插入这个字节,是为了在使用这个类定义多个对象的时候能保证每个对象有自己的地址。

另外,大家看看这个例子:

  1. Empty *pa1, *pa2;
  2. pa1 = new Empty();
  3. pa2 = new Empty();
  4. // ..
  5. if (pa1 == pa2) // 如果不分配内存,这个比较就会失去意义
  6. {
  7. }

如果不分配内存,如上的代码会不成立!

【2】什么是构造函数?使用构造函数有哪些注意事项?

(1)构造函数

构造函数是一种特殊的方法,主要作用在创建对象时初始化对象,即为对象的属性(数据成员)赋初始值,一般与 new 运算符一起使用在创建对象的语句中。

在C++重载机制下,一个类可以有多个构造函数,可根据其参数个数的不同、或参数类型的不同、或参数顺序的不同来区分它们,即构造函数的重载。

(2)使用构造函数注意事项:

<1> 构造函数的函数名必须与类名相同,而且没有返回值,更不能用void来修饰。

<2> 当一个类没有定义构造函数时,编译器会为每个类添加一个默认的构造函数。默认构造函数访问权限是public的,且为inline函数(常识)。

<3> 构造函数允许重载。(各个不同的构造函数创建的对象不同,即所谓的个体存在先天性差异)

<4> 构造函数不能被直接调用,必须通过new运算符在创建对象时才会自动调用。

<5> 在一个类中,只要有用户自定义的一个构造函数,那么这个类的默认构造函数就不再存在。

那么,也就意味着,想再实现像默认构造函数那样创建对象的形式,必须另外添加一个默认构造函数,或用户定义一个复合默认构造函数。

为了准确理解,请看以下示例代码:

第一、默认构造函数创建对象

  1. /*
  2. * 默认构造函数创建对象
  3. */
  4. class Test
  5. {
  6. private:
  7. int a;
  8. };
  9. void main()
  10. {
  11. Test t1; // 默认构造函数创建对象
  12. }

第二、自定义构造函数创建对象

  1. /*
  2. * 自定义构造函数
  3. */
  4. class Test
  5. {
  6. private:
  7. int a;
  8. public:
  9. Test(int x) : a(x)
  10. {}
  11. };
  12. void main()
  13. {
  14. // Test t1; // error! 编译器提示:no appropriate default constructor available
  15. Test t2(100); // OK 因为有相应类型的构造函数(自定义构造函数)
  16. }

第三、复合默认构造函数创建对象

  1. /*
  2. * 复合默认构造函数
  3. */
  4. class Test
  5. {
  6. private:
  7. int a;
  8. public:
  9. // Test(){}; // error!! 编译时出错,因为下面的这个是复合默认构造函数,二者冲突
  10. Test(int x = 10) : a(x)
  11. {}
  12. };
  13. void main()
  14. {
  15. Test t1; // OK 因为构造函数有默认值
  16. Test t2(100); // OK 因为有相应类型的构造函数
  17. }

<6> 构造函数一般不建议定义为私有的,因为在外部无法实现对象的创建。但是,特殊需要的情况下也是可以的这样实现的。

<7> 构造函数有两种方式实现对数据成员的初始化。

1:使用初始化列表进行初始化(以上<5>条第二示例代码可见)

2:在函数体中进行初始化

<8> 在用默认构造函数创建对象时,如果创建的是全局对象或静态对象,则对象的位模式全为 0,否则,对象值是随机的。

<9> 基类构造函数负责构建基类的对象,派生类的构造函数负责构建派生类的对象。派生类对象创建是由基类开始进行的。

<10> 构造函数不可以声明为const,也是完全没有必要的。

<11> 注意:分清楚对象与函数声明问题。示例代码如下:

  1. class Test
  2. {
  3. private:
  4. int a;
  5. public:
  6. Test(){};
  7. };
  8. void main()
  9. {
  10. Test t1; //对象?
  11. Test t2(); //对象?函数?
  12. }

注意:第十一行:编译器是以函数声明进行编译的,不是一个对象!

【3】什么是析构函数?使用析构函数有哪些注意事项?

(1)析构函数

我们已经了解了C++的编程哲学观是面向对象的,那么程序在运行时,创建了很多的对象,而当初系统在创建这些对象时,是为各自都分配有一定的内存空间的。

现在,程序要结束了!我们就要释放建立这些对象所占用的内存资源。而析构函数就是完成这些善后工作的。

(2)析构函数注意事项:

<1> 构造函数的函数名必须与类名相同,与构造相反再加一个非(~)。而且无参数没有返回值,更不能用void来修饰。

<2> 当一个类没有定义析构函数时,编译器会为每个类添加一个默认的析构函数。默认访问权限是public的,并且inline函数(常识)。

<3> 析构函数不可以重载,一个类中只允许有一个析构函数。

<4> 建立对象时若用new开辟了一片内存空间,应在退出前在析构函数中用delete全部释放。

<5> 析构函数仅仅只是一个普通成员函数。

<6> new 与 delete操作符详解

请看下面的示例代码以及说明:

new操作符使用示例:

  1. Test* ptr = new Test();

这一条语句,new操作符完成了两件事。

1:分配足够的内存以便容纳所需类型的对象。

2:调用构造函数初始化内存中的对象。

而且,你始终记住,new操作符的使命,或者说赋予它的能力就是这么大,只完成这两件事情,不能以任何方式改变它的行为。

delete操作符使用示例:

  1. delete ptr;

这一条语句,delete操作符完成了两件事。

1:调用对象的析构函数,释放对象申请的堆内存。

2:释放对象本身的内存,彻底释放本次申请的资源。

另外,请看如下两段代码:

代码1:

  1. #include <iostream>
  2. using namespace std;
  3. class Test
  4. {
  5. public:
  6. Test()
  7. {
  8. p = new char[1024];
  9. }
  10. ~Test()
  11. {
  12. cout << "deconstructor" << endl;
  13. delete []p;
  14. p = NULL;
  15. }
  16. private:
  17. char *p;
  18. };
  19. void main()
  20. {
  21. Test* pa = new Test();
  22. pa->~Test(); // 调用析构函数
  23. }

代码2:

  1. #include<iostream>
  2. using namespace std;
  3. class Test
  4. {
  5. public:
  6. Test()
  7. {
  8. p = new char[1024];
  9. }
  10. ~Test()
  11. {
  12. cout << "deconstructor" << endl;
  13. delete []p;
  14. p = NULL;
  15. }
  16. private:
  17. char *p;
  18. };
  19. void main()
  20. {
  21. Test *pa = new Test();
  22. delete pa; // 既调用析构函数,又释放申请的内存资源
  23. }

分析:注意每个示例的注释行:

第一个:仅仅调用了析构函数,析构函数释放了构造函数中申请的堆内存。

第二个:delete不仅隐式的调用了析构函数,并且释放了pa所指向的对象本身内存资源。最后main函数退出前,摧毁掉了临时变量pa指针。

其实,这个道理很简单的。举个现实的例子:见过盖房子和拆房子吧?申请内存就像索取宅基地,构建对象就类似盖房子,析构就像拆房子,而delete不仅拆房子还归还了宅基地。

所以说这也就是问题的关键,我们要归还的不仅仅是你房子占的那么大面积,而是要全部释放掉你当初申请的那么大块的宅基地面积。

【4】拷贝构造函数

(1)为什么需要拷贝构造函数?

因为我们想实现用一个已知对象去创建一个与它完全相同的新对象。

(2)拷贝构造函数有什么特点?

<1> 函数名与类名相同,并且没有返回值类型。

<2> 有且仅有一个参数,并且是同类已知对象的常引用。

<3> 每个类必有一个拷贝构造函数,如果程序员没有定义,系统会添加默认拷贝构造函数。

(3)拷贝构造的参数为什么是对象引用?

如果不是的话,将导致无限递归…..

(4)拷贝构造函数一般在什么时候使用?

<1> 声明一个新的对象时,用已知对象初始化新对象。

示例代码如下:

  1. #include<iostream>
  2. using namespace std;
  3. class Test
  4. {
  5. private:
  6. int a;
  7. public:
  8. Test(int x = 0) : a(x)
  9. {
  10. cout << "construction" << endl;
  11. }
  12. Test(const Test & t)
  13. {
  14. cout << "Copy construction" << endl;
  15. a = t.a;
  16. }
  17. };
  18. void main()
  19. {
  20. Test t1; // 调用构造函数
  21. Test t2(t1); // 调用拷贝构造函数
  22. Test t3 = t2; // 调用拷贝构造函数
  23. system("pause");
  24. }
  25. /*
  26. construction
  27. Copy construction
  28. Copy construction
  29. */

<2> 当一个已知对象作为一个实参传递给一个函数的形参时,在用实参对象初始化形参对象时,需要调用拷贝构造函数。

<3> 当对象作为一个函数的返回值时,要创建临时对象,临时对象的初始化是调用拷贝构造函数进行的。

关于<2>, <3>点示例代码如下:

  1. #include<iostream>
  2. using namespace std;
  3. class Test
  4. {
  5. private:
  6. int a;
  7. public:
  8. Test(int x = 0) : a(x)
  9. {
  10. cout << "construction" << endl;
  11. }
  12. Test(const Test & obj)
  13. {
  14. cout << "Copy construction" << endl;
  15. a = obj.a;
  16. }
  17. };
  18. Test Fun(Test t)
  19. {
  20. return t;
  21. }
  22. void main()
  23. {
  24. Test t1; // 调用构造函数
  25. Test t2(t1); // 调用拷贝构造函数
  26. Fun(t2); // 两次调用拷贝构造函数
  27. system("pause");
  28. }
  29. //the result of this:
  30. /*
  31. construction
  32. Copy construction
  33. Copy construction
  34. Copy construction
  35. */

<4> 在以下两种方式下也是应用拷贝构造函数的

1: 在初始化顺序容器中的元素时。

2: 按照元素初始化列表初始化数组元素时。

示例代码如下:

  1. #include <iostream>
  2. #include <vector>
  3. using namespace std;
  4. class Test
  5. {
  6. private:
  7. int a;
  8. public:
  9. Test(int x = 0) : a(x)
  10. {
  11. cout << "construction" << endl;
  12. }
  13. Test(const Test & obj)
  14. {
  15. cout << "Copy construction" << endl;
  16. a = obj.a;
  17. }
  18. };
  19. void main()
  20. {
  21. Test temp;
  22. vector<Test> vTObjs;
  23. cout << "初始化顺序容器中的元素:" << endl;
  24. vTObjs.assign(5, temp);
  25. cout << "根据元素初始化列表初始化数组元素:" << endl;
  26. Test Array[5] = {temp, temp, temp, temp, temp};
  27. system("pause");
  28. }
  29. // run out:
  30. /*
  31. construction
  32. 初始化顺序容器中的元素:
  33. Copy construction
  34. Copy construction
  35. Copy construction
  36. Copy construction
  37. Copy construction
  38. Copy construction
  39. 根据元素初始化列表初始化数组元素:
  40. Copy construction
  41. Copy construction
  42. Copy construction
  43. Copy construction
  44. Copy construction
  45. 请按任意键继续. . .
  46. */

(5)什么是浅拷贝?什么是深拷贝?

其实,浅拷贝深拷贝是很简单的问题。这样说话有点欠拍砖!因为会的人什么都觉得简单。呵呵!请看下面的分析:

想象一下:假如一个类中的数据成员中有一个是指针类型的,那么,当用户创建一个对象,系统就要为这个对象的指针成员对应的分配一块内存。

而如果你还想利用这个创建的对象通过拷贝构造函数去创建一个与其一样的新对象,问题就出现了?什么问题呢?就是你问的问题(嘿嘿~~)。

如果你把新对象的指针成员直接赋值为已知对象的指针成员值,就意味着两个对象指向了同一块内存。后果很严重:

程序结束,在析构对象时,因为有一个先后顺序之分,就势必会对同一块内存释放两次,这将会导致系统崩溃!这就是所谓的浅拷贝隐患。

那深拷贝呢?也就是解决了如上的麻烦。

其解决方案就是重新为新对象的指针成员开辟空间,然后把已知对象的指针所指内容拷贝过去,实现内容的完全一致。并且保证各自独立。

浅拷贝示例代码如下:

  1. #include <iostream>
  2. #include <assert.h>
  3. using namespace std;
  4. char * strcpy2(char *strDest, const char *strSrc)
  5. {
  6. assert((strDest != NULL) && (strSrc != NULL));
  7. if (strDest == strSrc)
  8. {
  9. return strDest;
  10. }
  11. char *address = strDest;
  12. while (*strDest++ = *strSrc++);
  13. return address;
  14. }
  15. class String
  16. {
  17. public:
  18. String(const char *str = NULL);
  19. String(const String &other);
  20. ~String();
  21. private:
  22. char *m_data;
  23. };
  24. String::String(const char *str)
  25. {
  26. if (NULL == str)
  27. {
  28. m_data = new char[1];
  29. assert(m_data != NULL);
  30. *m_data = '\0';
  31. }
  32. else
  33. {
  34. int length = strlen(str);
  35. m_data = new char[length + 1];
  36. assert(m_data != NULL);
  37. strcpy2(m_data, str);
  38. }
  39. }
  40. String::String(const String &other)
  41. {
  42. m_data = other.m_data;
  43. }
  44. String::~String()
  45. {
  46. delete []m_data;
  47. }
  48. void main()
  49. {
  50. String str1("xian");
  51. String str2(str1);
  52. }

注意,浅拷贝也是系统默认拷贝构造函数的实现方式,因为存在这种缺陷,所以才有了深拷贝的进一步完善。

深拷贝示例代码如下:

  1. String::String(const String &other)
  2. {
  3. int length = strlen(other.m_data);
  4. m_data = new char[length + 1];
  5. assert(m_data != NULL);
  6. strcpy(m_data, other.m_data);
  7. }

(6)拷贝构造函数一般为共有的。

【5】赋值构造函数

(1)什么是赋值构造函数?

一个类中赋值运算符的重载方法即就是赋值构造函数。

(2)为什么需要赋值构造函数?

当用户使用内置数据类型时,可以进行顺利的赋值运算操作。

示例代码如下:

  1. 1 void main()
  2. 2 {
  3. 3 int a = 10, b = 20, sum;
  4. 4 sum = a + b;
  5. 5 cout << sum << endl;
  6. 6 }

而如果Programer自己定义新的类型时,由于系统没有实现赋值运算符重载函数,所以是无法进行相关的操作。

示例代码如下:

  1. #include <iostream>
  2. using namespace std;
  3. class Complex // 复数类
  4. {
  5. public:
  6. double real; // 实数
  7. double imag; // 虚数
  8. Complex (double real = 0, double imag = 0)
  9. {
  10. this->real = real;
  11. this->imag = imag;
  12. }
  13. };
  14. void main()
  15. {
  16. Complex com1(10, 20), com2(1, 2), sum;
  17. sum = com1 + com2; // error!!编译错误
  18. }

那么,为了处理这种问题,C++可以使用运算符重载机制,实现运算符重载函数。即就是赋值构造函数的由来。

(3)使用运算符重载函数注意事项?

<1> 运算符重载函数其函数名字规定为operator后紧跟重载运算符。比如:operator+(), operator*()等。

现在我们给上述程序声明一个加法运算符的重载函数用于完成复数的加法运算:

示例代码如下:

  1. #include <iostream>
  2. using namespace std;
  3. class Complex // 复数类
  4. {
  5. public:
  6. double real; // 实数
  7. double imag; // 虚数
  8. Complex (double real = 0, double imag = 0)
  9. {
  10. this->real = real;
  11. this->imag = imag;
  12. }
  13. };
  14. Complex operator+(Complex com1, Complex com2) // 实现运算符重载函数
  15. {
  16. return Complex(com1.real + com2.real, com1.imag + com2.imag);
  17. }
  18. void main()
  19. {
  20. Complex com1(10, 10), com2(20, 20), sum;
  21. sum = com1 + com2; // 或sum = operator+(com1, com2)
  22. cout << "sum的实数部分为" << sum.real << endl;
  23. cout << "sum的虚数部分为" << sum.imag << "i" << endl;
  24. }
  25. /*
  26. *sum的实数部分为30
  27. *sum的虚数部分为30i
  28. */

如果仔细观察的话,上面的示例是有很多问题的。

因为这个运算符重载函数是全局的,那也就意味着,如果类中的数据成员不是public,这个方法就无能为力!

为了解决这个问题:“解铃仍需系铃人”C++不是有友元函数吗?OK,看下面的处理方案。

友元函数重载双目运算符。示例代码如下:

  1. #include <iostream>
  2. using namespace std;
  3. class Complex // 复数类
  4. {
  5. private: // 私有
  6. double real; // 实数
  7. double imag; // 虚数
  8. public:
  9. Complex(double real = 0, double imag = 0)
  10. {
  11. this->real = real;
  12. this->imag = imag;
  13. }
  14. friend Complex operator+(Complex com1, Complex com2); // 友元函数重载双目运算符+
  15. void showSum();
  16. };
  17. Complex operator+(Complex com1, Complex com2) // 友元运算符重载函数
  18. {
  19. return Complex(com1.real + com2.real, com1.imag + com2.imag);
  20. }
  21. void Complex::showSum()
  22. {
  23. cout << real;
  24. if (imag > 0)
  25. cout << "+";
  26. if (imag != 0)
  27. cout << imag << "i" << endl;
  28. }
  29. void main()
  30. {
  31. Complex com1(10, 10), com2(20, -20), sum;
  32. sum = com1 + com2; // 或sum = operator+(com1, com2)
  33. sum.showSum(); // 输出复数相加结果
  34. }
  35. /*
  36. *30-10i
  37. */

友元函数重载单目运算符。示例代码如下:

  1. #include <iostream>
  2. using namespace std;
  3. class Point // 坐标类
  4. {
  5. private:
  6. int x;
  7. int y;
  8. public:
  9. Point(int x, int y)
  10. {
  11. this->x = x;
  12. this->y = y;
  13. }
  14. friend void operator++(Point& point);// 友元函数重载单目运算符++
  15. void showPoint();
  16. };
  17. void operator++(Point& point) // 友元运算符重载函数
  18. {
  19. ++point.x;
  20. ++point.y;
  21. }
  22. void Point::showPoint()
  23. {
  24. cout << "(" << x << "," << y << ")" << endl;
  25. }
  26. void main()
  27. {
  28. Point point(10, 10);
  29. ++point; // 或operator++(point)
  30. point.showPoint(); // 输出坐标值
  31. }
  32. /*
  33. *<11,11>
  34. */

注意:像赋值运算符=、下标运算符[]、函数调用运算符()等是不能被定义为友元运算符重载函数。

<2> 运算符重载函数可以返回任何类型,甚至是void,但通常返回类型都与它所操作的类类型一 样,这样可以使运算符使用在复杂的表达式中。

比如把上述双目运算符重载函数示例代码中main()主函数里的com1 + com2 改为 com1 + com2 + com2,那么结果又会不一样了。

<3> 对于成员函数重载运算符而言,双目运算符的参数表中仅有一个参数,而单目则无参数。

示例代码如下:

  1. #include <iostream>
  2. using namespace std;
  3. class Complex // 复数类
  4. {
  5. private: // 私有
  6. double real; // 实数
  7. double imag; // 虚数
  8. public:
  9. Complex(double real = 0, double imag = 0)
  10. {
  11. this->real = real;
  12. this->imag = imag;
  13. }
  14. Complex operator+(Complex com1);// 成员函数重载双目运算符+
  15. void showSum();
  16. };
  17. Complex Complex::operator+(Complex com1)
  18. {
  19. return Complex(real+com1.real, imag+com1.imag);
  20. }
  21. void Complex::showSum()
  22. {
  23. cout << real;
  24. if (imag > 0)
  25. cout << "+";
  26. if (imag != 0)
  27. cout << imag << "i" << endl;
  28. }
  29. void main()
  30. {
  31. Complex com1(10, 10), com2(20, -20), sum;
  32. sum = com1 + com2; // 或sum = com1.operator+(com2)
  33. sum.showSum(); // 输出复数相加结果
  34. }
  35. /*
  36. *30-10i
  37. */

同样是重载,为什么与友元函数在参数的个数上会有所区别呢?原因在于友元函数没有this指针。

<4> C++中只能对已有的C++运算符进行重载,不允许用户自己定义新的运算符。

<5> C++中绝大部分的运算符可重载,除了成员访问运算符.,成员指针访问运算符.*,作用域运算符::,长度运算符sizeof以及条件运算符?:。

<6> 重载后不能改变运算符的操作对象(操作数)的个数。如:”+”是实现两个操作数的运算符,重载后仍然为双目运算符。

<7> 重载不能改变运算符原有的优先级。

<8> 重载不能改变运算符原有结合的特性。比如:z = x / y * a,执行时是先做左结合的运算x/y,重载后也是如此,不会变成先做右结合y*a。

<9> 运算符重载不能全部是C++中预定义的基本数据,这样做的目的是为了防止用户修改用于基本类型数据的运算符性质。

<10> 从上述的示例中可以看到双目运算符可以被重载为友元函数也可以重载为成员函数。

但有一种情况,只能使用友元函数,是什么情况呢?我举个例子,示例代码分析如下:

  1. #include <iostream>
  2. using namespace std;
  3. class Complex // 复数类
  4. {
  5. private: // 私有
  6. double real; // 实数
  7. double imag; // 虚数
  8. public:
  9. Complex(double real = 0, double imag = 0)
  10. {
  11. this->real = real;
  12. this->imag = imag;
  13. }
  14. Complex operator+(int x);
  15. };
  16. Complex Complex::operator+(int x)
  17. {
  18. return Complex(real + x, imag);
  19. }
  20. void main()
  21. {
  22. Complex com1(5, 10), total;
  23. total = com1 + 5; // OK
  24. // total = 5 + com1; // 编译error!!! 注意:com1+5 与 5+com1是两个不同的概念
  25. }

因为左操作数5不是该复数类的对象,不能调用相应的成员函数Complex operator+(int x),所以编译错误。

但如果我们定义一下两个友元函数就能解决上述的问题:

  1. friend Complex operator+(Complex com1, int x);
  2. friend Complex operator+(int x, Complex com1);

示例代码如下:

  1. #include <iostream>
  2. using namespace std;
  3. class Complex // 复数类
  4. {
  5. private: // 私有
  6. double real; // 实数
  7. double imag; // 虚数
  8. public:
  9. Complex(double real = 0, double imag = 0)
  10. {
  11. this->real = real;
  12. this->imag = imag;
  13. }
  14. Complex operator+(Complex com1); // 成员函数重载双目运算符+
  15. // 或friend Complex operator+(Complex com1, Complex com2); // 友元函数重载双目运算符+
  16. friend Complex operator+(Complex com1,int x); // 友元函数重载双目运算符+
  17. // 或Complex operator+(int x);
  18. friend Complex operator+(int x,Complex com1); // 友元函数重载双目运算符+
  19. void showSum();
  20. };
  21. Complex Complex::operator+(Complex com1)
  22. {
  23. return Complex(real+com1.real, imag+com1.imag);
  24. }
  25. Complex operator+(Complex com1, int x) // 左操作数类型为复数,右操作数的类型为整数
  26. {
  27. return Complex(com1.real + x, com1.imag);
  28. }
  29. Complex operator+(int x, Complex com1) // 左操作数类型为整数,右操作数的类型为复数
  30. {
  31. return Complex(x + com1.real, com1.imag);
  32. }
  33. void Complex::showSum()
  34. {
  35. cout << real;
  36. if (imag > 0)
  37. cout << "+";
  38. if (imag != 0)
  39. cout << imag << "i" << endl;
  40. }
  41. class Point // 坐标类
  42. {
  43. private:
  44. int x;
  45. int y;
  46. public:
  47. Point(int x, int y)
  48. {
  49. this->x = x;
  50. this->y = y;
  51. }
  52. friend void operator++(Point & point); // 友元函数重载单目运算符++
  53. Point operator++(); // 成员函数重载双目运算符++
  54. void showPoint();
  55. };
  56. void operator++(Point& point) // 友元运算符重载函数
  57. {
  58. ++point.x;
  59. ++point.y;
  60. }
  61. Point Point::operator++()
  62. {
  63. ++x;
  64. ++y;
  65. return *this; // 返回当前对象
  66. }
  67. void Point::showPoint()
  68. {
  69. cout << "(" << x << "," << y << ")" << endl;
  70. }
  71. int main()
  72. {
  73. // 两个复数相加
  74. cout << "两个复数相加:" << std::endl;
  75. Complex com1(10, 10), com2(20, -20), sum;
  76. sum = com1 + com2;//或sum = com1.operator+(com2)
  77. cout << "(10 + 10i) + (20 - 20i) = ";
  78. sum.showSum();// 输出复数相加结果
  79. // 三个复数相加
  80. cout << "三个复数相加:" << endl;
  81. sum = com1 + com2 + com2;
  82. cout << "(10 + 10i) + (20 - 20i) + (20 - 20i) = ";
  83. sum.showSum();
  84. //整数和复数相加
  85. cout << "整数和复数相加:" << endl;
  86. Complex com3(5, 10), total;
  87. total = com3 + 5; // 或total = operator+(com1, 5);
  88. cout << "(5 + 10i) + 5 = ";
  89. total.showSum();
  90. total=5+com3; // 或total = operator+(5, com1);
  91. // 只能用友元函数来重载运算符
  92. cout << " 5 + (5 + 10i) = ";
  93. total.showSum();
  94. // 单目运算符++重载
  95. cout << "单目运算符++重载:" << std::endl;
  96. // 注意:下述实现部分不能只用一个++point会造成二义性
  97. Point point(10, 10);
  98. // 调用友元函数
  99. operator++(point);// 或++point
  100. cout << "调用友元函数:++(10, 10) = ";
  101. point.showPoint(); // 输出坐标值
  102. // 调用成员函数
  103. point = point.operator++();// 或++point;
  104. cout << "调用成员函数:++(10, 10) = ";
  105. point.showPoint();
  106. system("pause");
  107. }
  108. // run out:
  109. /*
  110. 两个复数相加:
  111. (10 + 10i) + (20 - 20i) = 30-10i
  112. 三个复数相加:
  113. (10 + 10i) + (20 - 20i) + (20 - 20i) = 50-30i
  114. 整数和复数相加:
  115. (5 + 10i) + 5 = 10+10i
  116. 5 + (5 + 10i) = 10+10i
  117. 单目运算符++重载:
  118. 调用友元函数:++(10, 10) = (11,11)
  119. 调用成员函数:++(10, 10) = (12,12)
  120. 请按任意键继续. . .
  121. */

【6】以下关于上述函数调用细节分析

关于调用细节。示例代码如下:

  1. #include<iostream>
  2. using namespace std;
  3. class B
  4. {
  5. public:
  6. B ()
  7. {
  8. cout << "Default Construct" << " " << this << endl;
  9. }
  10. B (int i) : data(i)
  11. {
  12. cout << "Construct By :" << this << " " << data << endl;
  13. }
  14. B (const B &b)
  15. {
  16. cout << "Copy Construct" << " " << this << " " << &b << endl;
  17. data = b.data;
  18. }
  19. B &operator=(const B &obj)
  20. {
  21. cout << "operator= " << " " << this << " " << &obj << endl;
  22. if (this != &obj)
  23. {
  24. data = obj.data;
  25. }
  26. return *this;
  27. }
  28. ~B()
  29. {
  30. cout << "Destructed" << " " << this << " " << data << endl;
  31. }
  32. private:
  33. int data;
  34. };
  35. B Func(B b)
  36. {
  37. B t(8);
  38. return t;
  39. }
  40. void main()
  41. {
  42. {
  43. B t1(1);
  44. B t2(5);
  45. B t3 = t1;
  46. B t4(t3);
  47. B t5;
  48. t5 = t2;
  49. B t6 = Func(t2);
  50. B t7;
  51. t7 = Func(t1);
  52. }
  53. system("pause");
  54. }
  55. // 运行结果如下:
  56. /*
  57. Construct By :003BFA80 1
  58. Construct By :003BFA74 5
  59. Copy Construct 003BFA68 003BFA80
  60. Copy Construct 003BFA5C 003BFA68
  61. Default Construct 003BFA50
  62. operator= 003BFA50 003BFA74
  63. Copy Construct 003BF930 003BFA74
  64. Construct By :003BF910 8
  65. Copy Construct 003BFA44 003BF910
  66. Destructed 003BF910 8
  67. Destructed 003BF930 5
  68. Default Construct 003BFA38
  69. Copy Construct 003BF930 003BFA80
  70. Construct By :003BF910 8
  71. Copy Construct 003BF96C 003BF910
  72. Destructed 003BF910 8
  73. Destructed 003BF930 1
  74. operator= 003BFA38 003BF96C
  75. Destructed 003BF96C 8
  76. Destructed 003BFA38 8
  77. Destructed 003BFA44 8
  78. Destructed 003BFA50 5
  79. Destructed 003BFA5C 1
  80. Destructed 003BFA68 1
  81. Destructed 003BFA74 5
  82. Destructed 003BFA80 1
  83. 请按任意键继续. . .
  84. */

分析过程:

(1)构建对象t1。调用自定义带参构造函数。

(2)构建对象t2。调用自定义带参构造函数。

(3)构建对象t3,尤其注意这里调用的是拷贝构造函数。

由于t3还不存在!这一句t3 = t1; 相当于声明兼定义,本质类似于t3(t1)。

(4)构建对象t4,与(3)原理一样,调用拷贝构造函数。

(5)构建对象t5,声明兼定义t5,调用默认构造函数。

(6)为对象t5赋值,这里才调用赋值构造函数。

(7)依据打印输出结果逐步展开过程:

1、传参。Func函数传参的过程相当于B b = t2;这么一句代码,效果类似于(3),此很好理解调用的拷贝构造函数。

2、构建局部对象t。调用默认构造函数构建局部对象t。

3、返回局部对象t。相当于B t6 = t; 这么一句代码,效果类似于(3),因此仍然调用拷贝构造函数。

4、释放局部对象。先压栈的是b,所以先析构b;再压栈是t,所以再析构5。

(8)构建对象t7。调用默认构造函数。

(9)依据打印输出结果逐步展开过程:

1、传参(同上)。

2、构建局部对象(同上)。

3、返回局部对象。注意:这时创建了一个临时对象,其地址为003BF96C。

4、释放局部对象(同上)。

5、用临时对象为t7赋值。调用赋值构造函数。

6、析构掉临时对象。Destructed 003BF96C 8

(10)主程序结束,析构掉所有对象。

C++类中函数(构造函数、析构函数、拷贝构造函数、赋值构造函数)的更多相关文章

  1. c++中在一个类中定义另一个只有带参数构造函数的类的对象

    c++中在一个类中定义另一个只有带参数构造函数的类的对象,编译通不过 #include<iostream> using namespace std; class A { public:  ...

  2. object C—类中函数的调用

    Object C-类中函数的调用 创建,三个类.然后,在代码中调用相同名字的函数.观察他们的调用次序. @interface test : NSObject - (void)print; @end @ ...

  3. c++类大四个默认函数-构造函数 析构函数 拷贝构造函数 赋值构造函数

    每个类只有一个析构函数和一个赋值函数,但可以有多个构造函数(包含一个拷贝构造函数,其它的称为普通构造函数).对于任意一个类A,如果不编写上述函数,C++编译器将自动为A 产生四个缺省的函数,例如: A ...

  4. 类中函数前、后、参数加const

    1.参数加const:int fun(const int a) a在函数里不可被修改 2.函数前加const:const int* const fun() 这种一般是返回的指针或者是引用,加const ...

  5. python实用小技巧自问自答系列(一):查看类中函数文档doc的方法

    问题:如何查看某个类的方法文档说明或者是函数的参数列表情况? 答: 方法一:直接在需要查询的方法后面加上".__doc__"即可以打印出该方法的文档说明(需要先导入该方法所属模块) ...

  6. java中JFrame类中函数addWindowListener(new WindowAdapter)

    转自:http://blog.csdn.net/datouniao1/article/details/46984987:侵删. 在java编写的过程中常常遇到样的一段代码: frame.addWind ...

  7. Form1调用Unit2类中函数

    Form1有一个button,当Form1.Create时触发Button的OnClick事件,OnClick事件调用Unit2单元中的函数: unit Unit2; interface uses F ...

  8. Android中不混淆类中函数

    情况一:混淆不同的函数aTest.bTest -keep class com.zony.Test { void aTest(byte[], int, int); void bTest(String, ...

  9. opencv关于Mat类中的Scalar()---颜色赋值

    这个 CvScalar就是一个可以用来存放4个double数值的数组(O'Reilly的书上写的是4个整型成员):一般用来存放像素值(不一定是灰度值哦)的,最多可以存放4个通道的. typedef s ...

随机推荐

  1. tensorflow lite 之生成 tflite 模型文件

    下载最新的的tensorflow源码. 1.配置 tflite 文件转换所需环境 安装 bazel 编译工具 https://docs.bazel.build/versions/master/inst ...

  2. 列表推导:python2和python3中作用域的问题

    python2中: x = 'my love' dummy = [x for x in 'ABC'] print x 此时x打印为:'C' python3中: x = 'my love' dummy ...

  3. mysql索引实现原理

    什么是索引: 索引是一种高效获取数据的存储结构,例:hash. 二叉. 红黑. Mysql为什么不用上面三种数据结构而采用B+Tree: 若仅仅是  select * from table where ...

  4. TCP协议探究(二):超时与重试

    1 概述 TCP提供可靠的运输层. 可靠性保证之一:确认从另一端收到的数据. 但数据和确认都有可能会丢失.TCP通过在发送时设置一个定时器来解决这种问题. 如果当定时器溢出时还没有收到确认,它就重传该 ...

  5. React-脚手架

    1. Node和NPM 版本 2. 脚手架 在对React比较熟悉之前,为了避免陷入到开发工具的繁琐配置中,借助react官方提供的脚手架工具Create React App来搭建React应用 np ...

  6. 服务器上office不能正常使用?

    (1)确保dll版本和服务器上office版本一致 (2)配置dcom (3)项目配置文件中添加用户模拟语句 <system.web> <identity impersonate=& ...

  7. WinSockAPI多线程服务器

    运行效果: 程序: // TcpServer.cpp : 定义控制台应用程序的入口点. // #include "stdafx.h" #include <iostream&g ...

  8. caffe prototxt分析

    测试用prototxt name: "CIFAR10_quick"layer { name: "data" type: "MemoryData&quo ...

  9. Windows 下 nvm, node, npm 的下载、安装与配置

    主要解决的问题 下载安装完 nvm 和 node 后,缺失 npm 文件 执行 jasmine 等命令时提示「不是内部或外部命令...」及全局变量的设置 下载与安装 一.nvm github 下载地址 ...

  10. JS的一些简单基础运算题

    1.输入一个四位数,在控制台分别显示个位,十位,百位,千位的数值 var a = prompt("请输入一个四位数的正整数"); var b = parseInt(a/1000); ...