本笔记是建立在自己理解的基础上, 记下自己不太熟悉的部分, 仅供自己学习使用. 基础语法和数据部分就略过了, 直接面向对象编程, 程序员从来不缺对象, 如果缺, 就new一个, 😄😄😄.
类 和 对象
类相关概念
- 成员: 指成员变量 和 成员函数
- 成员访问属性:
public,protected,private.
类定义
class Person
{
};
注意: 类定义的结尾有分号
成员定义
- 定义一个
int类型的成员变量eyeNum - 在为的定义内部声明的同时实现, 如示例中的
run(). - 在类的定义内部声明, 在类的外部实现, 如示例中的
eat().
此处明确指出
eat()为public公有的, 可以在类的外是访问, 否则eat()就不能在外部实现.
- 成员定义示例:
class Person { // 成员变量 int eyeNum; // 类内部实现的成员函数 void run() { cout << "我正在跑步..." << endl; } public: // 类外部实现的成员函数 void eat(); }; // 类外部实现格式: // 返回值类型 类名::函数名 { 函数体 } void Person::eat() { cout << "我正在吃饭..." << endl; }
类的成员访问属性
public:- 公有成员在类的
外部是可以访问的
- 公有成员在类的
protected:- 受保护成员在类的外部是
不可以访问的
- 受保护成员在类的外部是
private:- 类内默认是私有成员,
- 在类的外部是
不可以访问的, - 在
友元函数中是可以访问.
用法: 一般在私有区域定义数据, 在公有区域定义相关函数, 调用成员函数来操作成员变量;
构造函数
- 构造函数特点:
- 构造函数是
特殊的成员函数, 在创建新对象时调用. - 名称与类名完全相同, 不会返回任何类型, 也不会返回 void.
- 可以带参数, 也可以不带参数.
- 可以为成员变量初始化.带参数可以使用初始化列表进行成员初始化
- 可以访问
this指针
- 构造函数是
- 构造函数定义和使用:
class Person { int age; int money; int books; public: // 声明两个构造函数 Person(); Person(int age, int num, int bookNum); }; // 在类的外部实现两个构造函数 Person::Person() { age = 0; money = 0; cout << "无参构造函数: age = " << age << ", money = " << money << endl; } // 使用初始化列表为成员变量进行初始化 Person::Person(int age, int num, int bookNum): age(age), books(bookNum) { // 或者用this指针 // this->money = num; money = num; cout << "有参构造函数: age = " << age << ", money = " << money << endl; } int main() { // 构造函数的使用 Person p1; // 创建对象 p1 会调用无参构造函数 Person p2(18, 100, 20); // 创建对象 p2 会调用有参构造函数 return 0; }
析构函数
-
析构函数特点:
- 析构函数是特殊的成员函数,在对象
销毁的时候调用 - 它的名称与类名相同, 只是需要在名称前加上前缀
~,不能带参数, 不能有返回值. - 有助于跑出程序前释放资源.
- 可以访问
this指针
- 析构函数是特殊的成员函数,在对象
-
析构函数定义和使用
class Person { public: // 指针成员, 需要动态分配内存, 在对象销毁时要释放内存 int *ptr; // 声明析构函数 ~Person(); }; // 在类外部实现析构函数 Person::~Person() { // 释放内存 delete *ptr cout << "析构函数 << endl; } int main() { // 当 main 函数执行完毕时, p1 被释放, 调用 p1 的析构函数 Person p1; return 0; }
拷贝构造函数
- 拷贝构造函数特点:
- 特殊的构造函数,
- 通过另一个同类型对象
创建新的对象, 和 OC 深拷贝类似. - 常用于复制对象后, 作为参数传递 或者 返回值.
- 如果在类中没有定义, 编译器会自行定义一个.
- 如果类带有指针变量, 一定要有拷贝构造函数, 为指针动态分配内存, 否则在外部调用时如果没有初始化, 就会空指针崩溃.
- 可以访问
this指针
- 拷贝构造函数定义和使用:
class Person { int age; int money; int books; public: // 指针成员, 需要动态分配内存, 在对象销毁时要释放内存 int *ptr; // 声明拷贝构造函数 Person(const Person& obj); Person(int age, int num, int bookNum); // 声明析构函数 ~Person(); }; // 在类的外部实现拷贝构造函数 Person::Person(const Person& obj) { // 拷贝构造函数会默认为其他成员变量初始化为 obj 中对应的值 // 指针成员变量, 需要手动重新动态分配内存 ptr = new int; *ptr = *obj.ptr; cout << "无参构造函数: age = " << age << ", money = " << money << endl; } Person::Person(int age, int num, int bookNum): age(age), books(bookNum) { this->money = num; ptr = new int; *ptr = age; cout << "有参构造函数: age = " << age << ", money = " << money << endl; } // 在类外部实现析构函数 Person::~Person() { // 释放内存 delete *ptr cout << "析构函数 << endl; } int main() { Person p1(18, 100, 20); // 用 p1 创建 p2, // 将 p1 成员变量的值都赋值给 p2 对应的成员变量 Person p2(p1); return 0; }
类的友元
类的友元包含友元函数和友元类, 用关键字 friend 标记, 如果一个类B是类A友元类, 那么B的所有函数都是A的友元函数, 此处仅以 友元函数 举例, 友元类同理.
-
友元函数特点:
- 用
friend在类的内部将函数标记为友元函数,友元函数的定义在类的外部. 友元函数并不是该类的成员函数.- 友元函数有权访问类的
private和protected成员, - 没有
this指针.
- 用
-
友元函数的用法:
class Person { int age; int money; int books; public: // 声明拷贝构造函数 Person(int age, int num, int bookNum); friend void getAge(Person p); // 友元函数 }; Person::Person(int age, int num, int bookNum): age(age), books(bookNum) { money = num; cout << "有参构造函数: age = " << age << ", money = " << money << endl; } void getAge(Person p) { // 友元函数访问私有成员 cout << "友元函数, age = " << p.age << endl; } int main() { Person p1(18, 100, 20); getAge(p1); return 0; }
静态成员
- 静态成员概念和特点:
概念: static 关键字修饰的成员称为静态成员, 包括成员变量和成员函数.
- 静态成员特点:
- 类的所有对象共享一个
静态成员. 静态成员的访问, 有两种方式:对象.成员类名::成员
静态成员函数只能在类的内部实现.静态成员函数内只能访问静态成员;普通成员函数可以访问任意成员.静态成员函数内没有this指针.静态成员变量不能在类内部初始化, 只能在类外部初始化; 如果不存在初始化语句,在创建第一个对象时静态成员变量会被初始化为零.
- 类的所有对象共享一个
- 静态成员定义使用:
class Person { // 静态变量 static int eyeNum; public: // 在类内部实现静态成员函数 static int fingerNum() { return 10; } }; // 在类外部初始化静态成员变量 int Person::eyeNum = 2; int main() { // 静态成员的访问, 有两种方式 // 1. 对象.成员 // 2. 类名::成员 Person p1; // 访问成员变量 cout << "eye num is " << p1.eyeNum << endl; cout << "eye num is " << Person::eyeNum << endl; // 访问成员函数 cout << "finger num is " << p1.fingerNum() << endl; cout << "finger num is " << Person::fingerNum() << endl; return 0; }
this 指针
- 概念:
this指针是所有成员函数的隐含参数, 在成员函数内部, 用来指向调用对象. - this 指针特点:
- 友元函数没有
this指针,因为友元不是类的成员. - 只有成员函数才有
this指针. - 因为是指针, 所以访问成员需要使用
->, 如this->成员
- 友元函数没有
类继承:
C++ 支持多继承, 即一个子类可以继承多个基类. 继承方式有三种, 分别是 public ,protected, private.
- 继承语法:
class <子类名>: <继承方式1> <基类名1>, <继承方式2> <基类名2> { } - 继承方式规则:
- 不能继承的成员函数 :
- 基类的
构造函数、析构函数和拷贝构造函数. - 基类的
重载运算符. - 基类的
友元函数.
- 基类的
public:- 基类的
公有成员也是子类的公有成员. - 基类的
保护成员也是子类的保护成员.
- 基类的
protected:- 基类的
公有和保护成员将成为子类的保护成员.
- 基类的
private:- 默认继承方式.
- 基类的
公有和保护成员将成为子类的私有成员.
- 基类
私有成员 :- 三种继承方式中, 基类的
私有成员都不能直接被子类访问,但是可以通过调用基类的公有和保护成员来函数访问.
- 三种继承方式中, 基类的
- 不能继承的成员函数 :
日常开发中, 通常使用
public继承. 这样可以保证操作私有成员的成员函数访问属性不变.
- 基类成员变量的初始化:
- 所有基类变量都可以在
子类构造函数的初始化列表中调用基类的构造函数进行初始化. - 所有基类变量都可以在
子类构造函数的函数体内依次赋值初始化. 注意:不能在子类构造函数中使用初始化列表直接对基类成员变量进行初始化,即:变量名(参数)
- 示例:
class Person { public: int age; Person(int a): age(a) { cout << "Person init" << endl; } }; class Student: public Person { public: int score; // age 通过基类构造函数初始化 Student(int a, int s): Person(a), score(s) { // age 也可以直接赋值 age = a; cout << "Student init" << endl; } /** // age 也可以直接赋值 Student(int a, int s): score(s) { age = a; cout << "Student init" << endl; } */ /** // age 不能直接在初始化列表中赋值 Student(int a, int s): age(a), score(s) { cout << "Student init" << endl; } */ };
- 所有基类变量都可以在
多态
-
多态:- 就是调用成员函数时, 会根据调用函数的对象的类型来执行不同的函数.
-
动态链接- 动态链接就是在程序中任意点可以根据所调用的对象类型来选择调用的函数.
-
虚函数:- 关键字
virtual修饰的函数称为虚函数, - 写在基类函数声明前,
- 在派生类中重新定义基类中定义的虚函数时, 会告诉编译器不要静态链接到该函数.
- 多态情景中, 使用
基类指针调用虚函数时会根据指针指向的对象的实际类型来调用对应的函数实现,而不会调用指针的类型对应的实现.
- 关键字
-
纯虚函数:- 虚函数没有主体, 即没有实现的虚函数,表示语法:
virtual int area() = 0; - 在基类中不能对虚函数给出有意义的实现,此时就需要纯虚函数.
- 虚函数没有主体, 即没有实现的虚函数,表示语法:
-
多态的使用:
- 示例代码:
#include <iostream> using namespace std; class Shape { protected: int width, height; public: Shape( int a=0, int b=0) { width = a; height = b; } // virtual 修饰表示虚函数 // 在调用此函数时会根据指针指向对象的实际类型来调用此对象的 area() virtual int area() { cout << "Parent class area :" << endl; return 0; } }; class Rectangle: public Shape { public: Rectangle( int a=0, int b=0): Shape(a, b) { } int area () { cout << "Rectangle class area :" << endl; return (width * height); } }; class Triangle: public Shape { public: Triangle( int a=0, int b=0): Shape(a, b) { } int area () { cout << "Triangle class area :" << endl; return (width * height / 2); } }; int main( ) { // 基类指针 Shape *shape; Rectangle rec(10,7); Triangle tri(10,5); // 存储矩形的地址 shape = &rec; // 调用矩形的求面积函数 area shape->area(); // 存储三角形的地址 shape = &tri; // 调用三角形的求面积函数 area shape->area(); return 0; }- 执行结果如下所示:
Rectangle class area Triangle class area -
分析:
- 上例中
shape指针先后指向不同的子类对象, 根据指针shape指向的实际对象的类型调用对应的实现, 满足了我们对多态的需求. - 如果基类中
area()函数没有用virtual修饰, 即非虚函数, 输出结果如下:导致错误输出的原因是,调用函数Parent class area Parent class areaarea()被编译器设置为基类中的版本,这就是所谓的静态多态,或静态链接; 函数调用在程序执行前就准备好了,这也被称为早绑定, 因为area()函数在程序编译期间就已经设置好了.
- 上例中