C++ 面向对象高级设计

inline关键字

  • 类声明内定义的函数,自动成为inline函数,类声明外定义的函数,需要加上inline关键字才能成为inline函数

常规函数与内联函数

  • 常规函数与内联函数之间的区别不在于编写方式,而在于C++编译器如何将他们组合到程序当中。

程序的运行可以理解为机器指令的逐步运行,当运行函数的时候,立刻保存指令的内存地址,跳到指定函数,并在结束时跳转到地址被保存的指令处,来回跳跃并记录跳跃位置意味着使用函数时,需要一定的开销。而内联函数的编译代码与其他的程序代码“内联”了起来,也就是说,编译器将使用相应的函数代替函数调用,对于内联函数,程序无需跳到另一个位置执行代码,然后再跳回来,因此,内联函数的运行速度比常规函数快,但代价是消耗更多的内存。当一个程序有10个不同的位置调用同一个内联函数的时候,则该程序包含该函数的10个副本,所以应该有选择的使用内联函数,条件如下:

  • 如果函数体代码的执行时间较长,则函数调用机制占用的时间可以忽略。
  • 如果函数体代码的执行时间较短,则使用内联函数可以节省大部分时间

构造函数

应该使用列表初始化

class complex {
public:
	complex(double r = 0, double i = 0) : re(r), im(i){}  //列表初始化
private:
	double re, im;
}; 

class complex {
public:
	complex(double r = 0, double i = 0){ re = r; im = i; }  //赋值构造函数
private:
	double re, im;
}; 

列表初始化更快,是理想的初始化方式。

常量成员函数

如果函数内不改变成员函数的值,则可以加上const修饰,更加的保险。

class complex {
public:
	...
	double real() const { return re; }
	double imag() const { return im; }
private:
	double re, im;
}; 
  • 常量对象只能调用常量函数,上述成员函数如果不加const, 那complex类型的常量对象则不能调用。

引用变量

  • 引用是已定义的变量的别名
  • 引用变量的主要用途是用作函数的形参
  • 通过将引用用作参数,函数将使用原始数据,而不是其副本
  • 必须在声明引用时将其初始化,而不能像指针那样,先声明,再赋值。
  • 一旦与某个变量关联起来,就将一直效忠于它。

参数的值传递和引用传递

应尽量使用参数的引用传递,避免了参数的复制且提高了效率。

返回值的值传递和引用传递

如果返回值是一个临时对象,那应该使用值传递,因为,一旦离开函数体,内部的临时对象就没了,相反,如果传回的对象是传入的对象的话,就使用引用传递,

ostream对象

对于重载的ostream对象,一般返回值的参数是ostream&, 不是void&, 因为我们输出的形式一般是cout<<t1<<t2, 连续输出, 返回ostream& 对象,可以继续输出。

友元

友元函数不属于对象函数,所以不受publicprivateprotected的限制, 但可以随意访问类内的所有成员和函数。
同一个类内的各个对象互为友元,所以在类定义中可以随意访问其他对象的私有成员, 如下:

class complex {
public:
        ....
	int func(const complex& parm) {
		return parm.re + parm.im;
	}
private:
	double re, im;
}; 

complex c1, c2;
c1.func(c2);

操作符重载

有两种方式实现操作符的重载:1. 在类内声明public类型的函数实现成员函数重载。2. 在类外声明全局函数重载。

类内声明的操作符重载 +=

在类内声明的操作符重载一般是作用在左操作数上的, 例如:

class complex {
public:
        .....
	friend complex& __dopal(complex*, const complex&);
    complex& operator += (const complex& parm) {
    	return __dopal(this, parm);
	}
private:
	double re, im;
}; 
inline complex& __dopal(complex* ths, const complex& r) {
	ths->re += r.re;
	ths->im += r.im; 
	return *ths;
}
complex c1(1, 2);
c1 += 1;  //重载 +=

对于类内重载的函数,其内隐含着一个this指针,可以像下面这样理解。

complex& operator += (complex* this, const complex& parm) {
    	return __dopal(this, parm);
	}
  • 注意重载的操作符 += 其返回的参数类型为complex&, 是因为可能不止一个两个对象相加,可能多个连加: c1 += c2 += c3;
  • 注意不会改动的值或者对象尽量加 const 修饰。

在类外声明的操作符重载 +

类外声明的操作符重载,参数不局限于作用在左操作数,考虑左右操作数的不同形式,所以使用类外重载操作符。

complex c1(2,1);
complex c2;

c2 = c1 + c2;	// 用法1: complex + complex
c2 = c1 + 5;	// 用法2: complex + double
c2 = 7 + c1;	// 用法3: double + complex

在类外声明函数重载 <<

与重载+的考虑方法类似,<<操作符通常的使用方式是cout<<c1而非c1<<cout,因此不能使用成员函数重载<<运算符.
考虑到形如cout<<c1<<c2<<c3的级联用法,重载函数的返回值为ostream&而非void&.

特殊函数

类中有三个特殊的函数:拷贝构造函数, 拷贝赋值函数,析构函数。
对于不带指针的类,这三个特殊的函数一般都使用默认版本,对于带指针的类,那就有必要自己定义这三个函数。

构造函数

构造函数有两种:深拷贝和浅拷贝。
主要区别:

  • 深拷贝会在堆内存中另外申请空间来储存数据
  • 浅拷贝则是实现类中成员的一一复制
  • 当执行两个类之间赋值的时候,在默认版本下,类中执行的都是浅拷贝,当类成员中没有指针的时候,执行浅拷贝是没有问题的,但是当有指针成员的时候,如果执行这样会造成一个现象,那就是两个指针指向同一个对象,等到结束对象使用的时候,会执行两次析构函数,从而造成指针悬挂现象,所以这种情况必须使用深拷贝。
    执行浅拷贝解决了指针悬挂问题,当类中有指针成员的时候,必须使用深拷贝。

析构函数

当类中有指针类型成员的时候,在构造函数中一般都使用了new或者new...[]来申请内存,所以当对象使用完毕调用析构函数的时候需要调用delete 指针成员或者delete[] 指针成员来释放内存。切记new 和 delete是相对的,有new就肯定有delete。

拷贝构造函数和拷贝赋值函数

拷贝构造函数和拷贝赋值函数函数的使用场景不同,下面程序的拷贝3虽然使用了=赋值,但是因为是在初始化过程中使用的,所以调用的是拷贝构造函数.

String s1 = "hello";
String s2(s1);      // 拷贝1: 调用拷贝构造函数
String s3;
s3 = s1;            // 拷贝2: 调用拷贝赋值函数
String s4 = s1;     // 拷贝3: 调用拷贝构造函数

拷贝赋值函数中要检测自我赋值,这不仅是为了效率考虑,也是为了防止出现bug.

inline String& String::operator = (const String& str) {
   if(this == &str) {  //检测自我赋值
     return *this;
   }
   delete[] m_data;
   m_data = new char[strlen(str.m_data) + 1];
   strcpy(m_data, str.m_data);
   return *this;
}

堆栈与内存管理

堆栈及对象的生命周期

栈(stack),在函数中声明的任何局部变量(非静态)都是在栈中分配的(编译期间完成)。并且函数的参数,以及返回值也是依赖于栈。
堆(heap),即动态存储区,与栈不同,堆是在程序运行时被分配的。C语言中的malloc,C++中的new完成的都是堆上的操作。堆不会自动释放所以需要free和delete。

class complex{...};
...
{
  complex c1(1, 2);   // 所占空间来自于栈(stack)
  complex *p = new complex(3);  //new分配的,控件来自于堆(heap)
}
  • 栈(stack)对象的生命周期:函数体结束之后,堆对象会被自动清理。
  • static对象,函数结束之后不会被清理,直到程序结束才会被清理。
  • 全局对象是直到程序结束才会被清理
  • 堆(heap)对象(new 申请空间定义的对象),会在delete ..释放对象之后被清理
posted @ 2021-05-04 23:45  tlfclwx  阅读(165)  评论(0)    收藏  举报