C++ 面向对象高级编程
1、头文件结构
1.1 防卫式声明(防止头文件被重复包含)
1.2 前置声明(声明头文件中用到的类和函数)
1.3 类声明: 声明类的函数和变量,部分简单的函数可以在这一部分加以实现
1.4类定义: 实现前面声明的函数
2、访问权限
访问权限限定符只用于修饰类的成员变量和成员函数。
访问权限分为三种,分别为public
、protected
、private
。
访问权限 | 作用 |
---|---|
public | 可以被所有函数访问 |
protected | 能被本类的函数和子类的函数访问 |
private | 只能被本类的函数访问 |
2.1 public
被public
限定符所修饰的成员变量和函数可以被类的函数、子类的函数、友元的函数以及类对象来访问,即可以使用成员运算符来访问。这里的友元函数,可以是该类的友元函数,也可以是该类的友元类的成员函数。
2.2 protected
protected
限定符修饰的成员变量和成员函数可以被该类的成员函数访问,但是不能被类对象所访问,即不能通过类对象的成员运算符来访问。另外,这些成员可以被子类的函数和友元函数访问,相比public
成员,少了一个可以使用类对象直接访问的特性。
2.3 private
被private
限定符修饰的成员变量只能被该类的方法和友元函数访问,子类函数无法访问,在这三个限定符中封装程度是最高的,一般来说,应该尽可能将类的成员变量声明为private
而不是其他,减少成员变量的暴露,只提供getter
和settter
方法给外界访问,这样能提高类的安全性。
2.4 示例
类的声明内可以交叉定义多个不同级别的访问控制块
class Solution {
public:
// public访问控制块1
protected:
// protected访问控制块1
private:
// private访问控制块1
public:
// public访问控制块2
protected:
// protected访问控制块2
private:
// private访问控制块2
};
3、内联函数
C++中支持内联函数,其目的是为了提高函数的执行效率。
设置为内联函数的两种方法:
- 在类声明内定义的函数,自动成为
inline
函数。 - 用关键字
inline
放在函数定义的前面即可将函数指定为内联函数,内联函数通常就是将它在程序中的每个调用点上内联地展开,假设我们将MAX
定义为内联函数:
inline int MAX(int a, int b) {
return a > b ? a : b;
}
//则调用:
cout << MAX(a, b) << endl;
//在编译时展开为:
cout << (a > b ? a : b) << endl;
//从而消除了把MAX写成函数的额外执行开销,提高函数的执行效率。
3.1 类声明内定义的函数,自动成为inline
函数
3.2 类声明外定义的函数,需要加上inline
关键字才能成为inline
函数
inline int MAX(int a, int b) {
return a > b ? a : b;
}
3.3 内联函数的优点
当函数体比较小的时候,内联该函数可以令目标代码更加高效。对于存取函数以及其它函数体比较短,性能关键的函数,鼓励使用内联。
3.4 内联函数的缺点
滥用内联将导致程序变慢。内联可能使目标代码量或增或减,这取决于内联函数的大小。内联非常短小的存取函数通常会减少代码大小,但内联一个相当大的函数将戏剧性的增加代码大小。现代处理器由于更好的利用了指令缓存,小巧的代码往往执行更快。
3.5 内联函数的小结
inline
只是编程者给编译器的一个建议,在编译时未必会真正被编译为inline
函数。因此如果函数足够简单,我们就把它声明为inline
就好了。
4、构造函数
与其他语言类似,C++的构造函数也可以有默认实参。C++构造函数的特殊之处在于列表初始化(initializationlist)。
使用成员初始化列表方法的时候需要注意,在初始化的时候需要按照声明的顺序进行初始化。
4.1 列表初始化
class complex {
private:
double re, im;
public:
complex(double r = 0, double i = 0) :re(r), im(i)
{
}
};
4.2 赋值
class complex {
private:
double re, im;
public:
complex(double r = 0, double i = 0)
{
re = r;
im = i;
}
};
4.3 初始化与赋值的区别
C++ 规定,对象的成员变量的初始化动作发生在进入构造函数本体之前,所以列表初始化的效率更高。
5、值传递、指针传递与引用传递
5.1 值传递
形参是实参的深拷贝,对形参的操作不会影响实参的操作。
5.2 指针传递
形参为指向实参地址的指针,对形参的操作其实就是对实参的操作。
5.3 引用传递
形参相当于是实参的别名,对形参的操作其实就是对实参的操作。
5.4 指针传递与引用传递的区别
指针能够毫无约束地操作内存中的任何东西,尽管指针功能强大,但是非常危险。
如果的确只需要借用一下某个对象的别名,那么就用引用,而不要用指针,以免发生意外。
5.5 值传递、指针传递与引用传递的小结
为了提高效率,使用引用传递参数,避免了参数的复制。
若不希望在函数体内对输入参数进行修改,应使用const
修饰参数。
为提高效率,若函数的返回值是原本就存在的对象,则应以引用形式返回。若函数的返回值是临时变量,则只能通过值传递返回。
6、友元
友元函数不受访问级别的控制,可以自由访问对象的所有成员。
同一类的各个对象互为友元,因此在类定义内可以访问其他对象的私有变量。
使用友元时注意:
- 友元关系不能被继承。
- 友元关系是单向的,不具有交换性。若类 B 是类 A 的友元,类 A 不一定是类 B 的友元,要看在类中是否有相应的声明。
- 友元关系不具有传递性。若类 B 是类 A 的友元,类 C 是 B 的友元,类 C 不一定是类A的友元,同样要看类中是否有相应的申明。
class complex
{
private:
friend class A;
friend complex& __doaml(complex*, const complex&);
};
7、操作符重载
operator 是 C++ 的一个关键字,它和运算符(如=)一起使用,表示一个运算符重载函数,在理解时可将 operator 和运算符(如operator=)视为一个函数名。
使用 operator 重载运算符,是 C++ 扩展运算符功能的方法。使用 operator 扩展运算符功能的原因如下:
- 使重载后的运算符的使用方法与重载前一致。
- 扩展运算符的功能只能通过函数的方式实现。(实际上,C++中各种功能都是由函数实现的)
7.1 在类中声明并定义需要重载的操作符
示例:
class Complex {
private:
double re, im;
public:
Complex():re(),im()
{
}
Complex(double r = 0, double i = 0) :re(r), im(i)
{
}
bool operator==(const Complex& second) const
{
if (re == second.re && im == second.im) {
return true;
}
else {
return false;
}
}
};
int main(){
Complex a(2, 1);
Complex b(1, 1);
Complex c(2, 1);
cout << "a == b:" << (a == b) << endl;
cout << "a == c:" << (a == c) << endl;
return 0;
}
输出:
a == b:0
a == c:1
7.2 在类中声明需要重载的操作符,在类外定义
示例:
class Complex {
private:
double re, im;
public:
Complex():re(),im()
{
}
Complex(double r = 0, double i = 0) :re(r), im(i)
{
}
bool operator==(const Complex& second) const;
};
bool Complex::operator==(const Complex& second) const
{
if (re == second.re && im == second.im) {
return true;
}
else {
return false;
}