面向对象基础常识
2168 字
11 分钟
面向对象基础常识
C++ 面向对象基础
访问权限
| 权限 | 类自身(成员/友元) | 派生类(成员/友元) | 外部/普通用户 |
|---|---|---|---|
| public | ✓ | ✓ | ✓ |
| protected | ✓ | ✓ | ✗ |
| private | ✓ | ✗ | ✗ |
虚析构
delete p 只会删除静态类型,如果 p 指向 Base 这个基类,那么 delete 无法删除它的派生类 Derived,比如 Base* p = new Derived(); , 但使用虚析构就可以了: virtual ~Base();
class Animal { public: ~Animal() { cout << "~Animal\n"; }};
class Dog : public Animal { char* name_; public: Dog(const char* s) { name_ = new char[20]; cout << "Dog born\n"; } ~Dog() { delete[] name_; cout << "~Dog\n"; }};
int main() { Animal* p = new Dog("Tom"); delete p; return 0;}派生类权限:
分两步看:基类成员的原始权限 + 继承方式,取更严格的那个。
| 基类权限 | public 继承 | protected 继承 | private 继承 |
|---|---|---|---|
public | public | protected | private |
protected | protected | protected | private |
private | 不可直接访问 | 不可直接访问 | 不可直接访问 |
class Base {public: int a;protected: int b;private: int c; // 派生类永远不能直接碰};
class Pub : public Base { // a: public, b: protected, c: 不可访问};
class Pro : protected Base { // a: protected, b: protected, c: 不可访问};
class Pri : private Base { // a: private, b: private, c: 不可访问};上面的权限指的是”对外展示的权限”,而不是派生类”内部访问的权限”。
- 内部访问:只看基类自己的权限标签(public/protected/private),不管你怎么继承。
- 外部访问:才看继承方式,那是”降级规则”——把基类成员的对外可见度往下压一级或两级。
友元 & Tips
- 友元关系不能继承、不能交换也不能传递
- 友元函数不是成员函数,只是一个类对一个函数的访问授权。
- 友元函数是
private权限。 private是本类成员函数可访问,protected是本类和派生类,public是任何地方都可以访问。- 类内定义的函数默认是内联函数,但是类内声明但是在类外定义的函数要加上
inline关键字。 inline用于实现而非用于声明。- 析构和构造函数都必须在类内声明,但可以在类外定义。
delete删除一个动态对象,会先析构,再释放其占用的内存。- 类的定义体中包括:数据成员、成员函数和成员的访问控制信息。
- 静态成员数据由该类的所以实例化对象共享,其生命周期是整个程序周期。
i和j声明在A的public:之前,默认就是private。到了B::make()里想直接访问,就碰壁了。
class A { int i, j; public: void get();};class B : public A { int k; public: void make();};
void B::make() { k = i * j;}- 类内可以定义赋值语句,但是不能有执行语句,比如:
class B { int k; int z = 42; int z = x * y; k = 1; // false};- 虚析构函数的作用是避免在 delete 基类指针时,只析构基类部分,漏掉派生类部分,造成资源泄露。
- 语法上不存在虚构造函数,但可以通过虚函数(如
clone())实现类似效果。
class Base {public: virtual ~Base() {} virtual Base* clone() const = 0; // 虚函数模拟"虚构造"};
class Derived : public Base {public: Derived* clone() const override { return new Derived(*this); }};- 纯虚函数是一个在基类中说明虚函数,它在该基类中没有定义,但要求任何派生类中的可访问性的规定是相同的。
静态 & 常
- 静态成员函数只能访问静态成员变量,且它属于整个类,可以用
static void show()和Student::show()也可以通过对象访问普通成员变量,比如:
class A {public: int x; static void set(A &a) { a.x = 100; }};
int main() { A obj; A::set(obj); cout << obj.x << endl;}- 由于数据隐藏的必要,静态成员数据通常为 private(私有成员)。
X a[3] *p[2]里面实际上只调用了 3 次构造函数,因为 X 类的指针类型数组没有实例化对象,无需调用构造函数。int *p = new A|new A[10]|new int[3]{1,2,3}|int[5]{}(初始化为0)- 常成员函数案例:
void show() const - 常对象只能调用常成员函数,不能修改成员变量。
多态
静态多态:编译时
包括函数重载和运算符重载
// 函数重载int add(int a, int b) { return a + b; }double add(double a, double b) { return a + b; }
// 模板template<typename T>T max(T a, T b) { return a > b ? a : b; }运算符重载:复数相加
class Complex { double real, imag;public: Complex(double r = 0, double i = 0) { real = r; imag = i; } virtual ~Complex() { cout << "Complex deleted" << endl; }
// getter 函数,让外部能读取私有成员 double getReal() const { return real; } double getImag() const { return imag; }
// 声明为友元函数,使其可以访问私有成员 friend Complex operator+(const Complex& om1, const Complex& om2); friend Complex operator+(const int il, const Complex& om1);};
Complex operator+(const Complex& om1, const Complex& om2) { Complex temp; temp.real = om1.real + om2.real; temp.imag = om1.imag + om2.imag; return temp;}
Complex operator+(const int il, const Complex& om1) { Complex temp; temp.real = om1.real + il; temp.imag = om1.imag; return temp;}
int main() { Complex a(5.2, -1.3); Complex b(1.1, 0.3); Complex c = a + b; cout << c.getReal() << " " << c.getImag() << endl; Complex d = operator+(1, a); cout << d.getReal() << " " << d.getImag() << endl;}
运算符重载函数
- 成员运算符函数
- 友元函数重载
- 双目运算符函数
- 单目运算符函数
Tips
- 不能用友元函数重载的运算符是:
= | () | [ ] | ->其余都可以,因为这些运算符必须是非静态成员函数,这是C++标准的硬性规定。 =可以被重载,但是重载了的=函数不能被继承。(不能继承实际是被系统缺省定义替换掉了)- 每一个类对象实例在创建的时候,如果用户没有定义“赋值运算符重载函数”编译器会自动生成一个隐含和默认的“赋值运算符重载函数”。
- 除
=以外的所有基类运算符都会被自动继承。
类的 6 个默认成员函数
| 分类 | 默认成员函数 | 作用 |
|---|---|---|
| 初始化 | 构造函数 | 对象初始化 |
| 销毁 | 析构函数 | 对象销毁 |
| 拷贝 | 拷贝构造函数 | 创建对象时复制数据 |
| 赋值 | 赋值运算符重载(operator=) | 对已有对象赋值 |
| 取地址 | operator& | 获取普通对象地址 |
| 取地址 | const operator& | 获取 const 对象地址 |
重载 = 和 <<
// 重载 =class Complex { double real, imag;public: Complex(double r = 0, double i = 0) : real(r), imag(i) {}; Complex& operator=(const Complex& c) { if (this == &c) { return *this; } real = c.real; imag = c.imag; return *this; } friend ostream& operator<<(ostream& os, const Complex& c);};
ostream& operator<<(ostream& os, const Complex& c) { os << "(" << c.real << " + " << c.imag << "i)"; return os;}
// 重载 <<class Complex { double real, imag;public: Complex(double r, double i) : real(r), imag(i) {}; friend ostream& operator<<(ostream& os, const Complex& c);};ostream& operator<<(ostream& os, const Complex& c) { os << c.real << " + " << c.imag << "i"; return os;}重载 圆括号 和 中括号(必须用成员函数)
class Array { int data[10];public: Array() { for (int i = 0; i < 10; i++) { data[i] = i; } } int& operator[](int i) { return data[i]; } int operator()(int i) { return data[i] * data[i]; }};动态多态:运行时
class Animal {public: virtual void speak() = 0; // 虚函数};class Dog : public Animal {public: void speak() override { cout << "wang wang"; }};
Animal* p = new Dog();p->speak(); // 运行时通过虚表找到 Dog::speak,输出"wang wang"异常
double divide(double a, double b) { if (b == 0) throw runtime_error("can not be 0"); return a / b;}int main() { try { cout << divide(10, 0); } catch (const runtime_error e) { cerr << "err: " << e.what() << endl; }}模板
#include <bits/stdc++.h>using namespace std;
template <class AT> AT max(AT x,AT y){return (x>y)?x:y;}int main(){ int x1=1,y1=2; cout<<max(x1,y1)<<endl; float x3=1.1,y3=2.2; cout<<max(x3,y3)<<endl; char x4='a',y4='b'; cout<<max(x4,y4)<<endl; int x2 = 3; char y2 = 4; cout << max(x2, y2) << endl;}C++ 函数调用的一般顺序 C++中函数调用的一般顺序为: (1)寻找一个参数完全匹配的函数,若找到则调用之,否则:(2)寻找一个函数模板,若找到则将其实例化为一个模板函数,然后调用之,否则: (3)寻找重载函数,考察有无可通过产生参数匹配的函数,若有则调用之。
类模板和模板类的区别
template<typename T>class Stack { // ← 这是"类模板" T* data; void push(const T&);};
Stack<int> s; // ← Stack<int> 是一个"模板类"(实例化后的具体类)Stack<double> d; // ← Stack<double> 是另一个"模板类"输入输出
ios_base └── ios ├── ostream ← 输出流 │ └── ofstream (文件输出) │ └── ostringstream (字符串输出) ├── istream ← 输入流 └── iostream ← 双向流(同时继承 istream 和 ostream)缺省
class A {};// 等价于class A : private B {};struct A {};// 等价于struct A : public B {};


