C++面向对象总结

记录侯捷老师C++面向对象课程[1]的笔记。

面向对象上篇

C++编程简介

  • 当以类(class)来组织编写C++,可以把C++编写方式分为:Object Oriented(面向对象)和Object Based(基于对象),其区别主要看类与类之间是否有关联(继承,复合,委托)。
  • 单一类有包含指针的类和不包含指针的类,以此为区分的类有很大的不同。
  • C++由C++语言和C++标准库组成。
  • C++经典书籍推荐:《C++ Primer》、《The C++ programming language(C++11)》、《Effective C++》、《The standard library》、《STL 源码剖析》。

头文件与类的声明

  • C语言与C++的明显的不同在于,在C语言中,数据变量是暴露在所有处理函数面前的,而C++中,特定的数据由特定的函数体处理(由class,struct包装起来的变量和函数)。
  • 在C++中引用C语言的库,#include<stdio.h>或者 #include<cstdio>都可以。
  • 头文件的防卫式声明
1
2
3
4
#ifndef __COMPLEX__
#define __COMPLEX__
...
#endif
  • 类的声明
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
#ifndef __COMPLEX__
#define __COMPLEX__

template<typename T>
class complex
{
public:
	complex (T r = 0, T i = 0)
		:re(r), im(i){}
	T real () const {return re;}
	T imag () const {return im;}

private:
	T re, im;
	
	friend complex& __doapl__ (complex*, const complex&);
		
}

#endif

模板类的实例化:

1
2
complex<double> c1(2.5, 1.5);
complex<int> c2(2,6);

在这里,使用模板template的好处在于:不需要为不同类型double、int等单独定义不同的复数类,而这些类的区别仅仅在于数据类型不同而已。

构造函数

  • inline(内联)函数:若函数在class内定义完成,则成为inline函数,inline函数相较于一般函数,编译速度更快,但如果函数体过于复杂,编译器实际上不会把它当作inline函数。简洁来说,我们在函数前面加inline关键字,只不过是对编译器的建议而已,该函数最终不一定是inline函数。
  • 大部分的函数访问级别标记为public,而所有的类的数据的访问级别标记为private
  • 类的构造函数写法
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
class complex
{
public:
	//构造函数的名称与类的名称相同,没有返回类型
	complex (double r = 0, double i = 0) //默认实参
		:re(r), im(i) //initialization list(初值列表):如果构造时引入参数,则使用该参数为类的变量赋值
		{} //构造函数的函数体
	complex& operator += (const complex&);
	double real () const {return re;}
	double imag () const {return im;}

private:
	double re, im;
	
	friend complex& __doapl__ (complex*, const complex&);
		
}

使用initialization list(初值列表)为类内变量赋值效率会更高,因为为一个变量赋值分为初始化和赋值(即{}内完成的部分)两个阶段,initialization list会在初始化阶段直接为变量赋值,省了一步。

  • 不带指针的类多半不需要写析构函数。
  • 构造函数可以有很多个--overloading(C++允许函数重载,不仅仅限于构造函数) 但需要注意下面的例子,这两个构造函数却不可以共存。
1
2
3
4
5
complex(double r = 0, double i = 0) //该构造函数有默认参数
		:re(r), im(i) {}
complex() : re(0), im(0) {} //上面的构造函数有默认参数,导致该构造函数不能成功构建,
//因为对于一个例子:conplex C;该实例化会让编译器无法决定使用哪一个,
//因为上面两个构造函数都是相同的效果

参数传递与返回值

  • 构造函数放在private作用域:不允许随意外界创建该类的实例。(singleton)
  • const:当确保函数不改变数据的内容,请加const。
1
2
	double real () const {return re;}
	double imag () const {return im;}
  • 尽量使用引用传递参数(pass by reference)。
  • 常量引用:const conplex&, 引用保证参数传递很快,常量const保证参数不可修改。
  • 尽量使用引用返回函数值(return by reference)。
  • 相同class的各个objects互为友元(friends)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class complex
{
public:
	//构造函数的名称与类的名称相同,没有返回类型
	complex (double r = 0, double i = 0) //默认实参
		:re(r), im(i) 
		{} 
	int func(const complex& param){
	return param.re + param.im;
	}
private:
	double re, im;
		
}
  • 当返回的变量来自于一个局部变量的赋值,这种情况不可以返回引用,因为局部变量超出函数作用域后就会消亡。

操作符重载与临时对象

  • 可以认为操作符就是函数
  • 操作符重载--写成成员函数
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# 传递者无需知道接收者是否以 reference形式接收参数(reference的另外一个好处)
inline complex&
__doapl(complex* ths, const complex& r)
{
	ths->re += r.re;
	ths->im += r.im;
	return *ths;
}
 #所有的成员函数都有一个隐藏的this
 #之所以这里不能是void 是考虑到:a+=b+=c 这种用法
inline complex&
complex::operator += (const complex& r)
{
	return __doapl(this, r); # this在这里显式调用
}
  • 操作符重载--写成非成员函数,它是全局函数,没有this指针,必须显示调用对象的实例
  • 临时对象的返回不可以返回引用
1
2
3
4
5
inline complex
conj (const complex& x)
{
	return complex (real(x), -imag(x));
}
  • 重载 << 时只能用全局的非成员函数的形式
1
2
3
4
5
6
7
#include <iostream.h>
# 返回类型不是void 是考虑到 cout<<c1<<c2;的形式
ostream&
operator << (ostream& os, const complex& x)
{
	return os << '(' << real(x)<<','<<imag(x)<<')';
}
updatedupdated2022-04-242022-04-24