目录
1、内联函数要求:
- 函数简短,通常3-5行;
- 函数内没有复杂的实现,不能while、for 循环、switch、递归,且不包含静态成员变量;
- 通常在多处有调用;
- 内联函数能够减少函数调用的开销。
2、面向对象的关键要素:
- 封装、继承、多态、抽象性、模块化、信息隐蔽、对象标识、动态绑定。
- 类是对象的抽象,而对象是类的具体化。
- 任何一个对象都有两个要素:属性和行为。
- 对一个对象进行封装,从而实现对外界的屏蔽。(类的公用接口和私有实现的分离形成了信息隐蔽)
- 一个对象所占用存储空间大小,只取决于数据成员所占用的空间。
- 通过成员函数对数据成员进行操作,称为类功能的实现。
- 不是类的成员函数:普通全局函数,友元函数、静态函数。
3、函数继承:
- 不能被继承的主要函数:构造函数、析构函数、友元函数。
- 能被继承的主要函数:静态成员变量/函数、公有和保护成员函数/变量、虚函数、赋值操作符。
- 派生类的构造函数,通常包含:基类构造函数、派生类构造函数。
4、运算符重载:
- 只能用成员函数重载的运算符:“=”、“()”、“[]”、“—>”
- 即可以用成员函数也可以用友元函数重载的运算符:“+”、“-”、“*”、“/”、“==”、“!=”、“>=”、“<=”。
5、抽象类不能被实例化
- 抽象类目的是定义一个通用的接口,让派生类去实现这些纯虚类。
- (带有纯虚函数的类叫做抽象类,抽象类不能定义对象的类),而派生类、具体类、嵌套类、虚基类可以定义对象的类。
1、cout实际上是C++系统定义的对象名,叫做输入流对象。
2、c++将对象间的信息流动称为“流”,获取流的操作称为(提取)操作。
3、编译时给出的出错信息分两种:错误和警告。
1、不同对象调用相同名称函数,但可导致完全不同的行为的现象称为多态性
- C++支持两种多态性:静态多态性、动态多态性
- 静态关联支持的多态性称为(编译时多态)
- 动态关联支持的多态性称为(运行时多态)
- 动态多态要满足两个条件:用指针调用的成员函数、成员函数为虚函数
2、重载、重写、重定义总结:
重载是编译时多态的一种体现,允许在同一作用域内定义多个同名但参数列表不同的函数。
重写是运行时多态的基础,通过基类指针或引用来调用派生类中的函数,前提是基类函数必须是虚函数。
重定义(隐藏)可能导致意外的行为,特别是当基类函数被设计为通过基类指针调用时。它发生在编译时,且不受基类函数是否为虚函数的影响。
一、类与结构体
1.类与结构体
1、类与结构体的主要区别:
- 类是引用类型,结构是值类型。
- 结构不支持继承、不能声明默认的构造函数。
2、结构和类的适用场合:
- 当堆栈的空间很有限,或有大量的逻辑对象时,或在表现抽象和多级别的对象层次时,创建类要比创建结构好一些。(因为结构不支持继承)
- 对于点、矩形和颜色这样的轻量对象,假如要声明一个含有许多个颜色对象的数组,则CLR需要为每个对象分配内存,在这种情况下使用结构的成本较低。
2.new与delete
1、new的用法:
- new int;(开辟一个存放整数的存储空间,返回一个指向该存储空间的地址)
- new int(100);(开辟空间,并初始化整数为100,返回地址)
- new char[10];(开辟字符数字空间,返回首元素地址)创建数组对象时,不能为对象指定初始值。
- new int[5][5];(开辟二维数组空间,返回首元素地址)
- float *p=new float(3.14159);(开辟空间,赋初值,并将地址赋给指针变量p)
2、delete的用法:释放已分配的空间
- (delete 指针变量)(delete[] 指针变量)(指针变量必须是一个new返回的地址)
3.类的封装访问属性
1、结构体能做的事类都能做,类能做的事结构体不一定能做(本质是:结构体内不能封装函数,但类可以)(故在c++中学会了类也就学会了结构体)
2、封装对内开放数据,对外屏蔽数据,对外提供接口,从而达到信息的隐蔽作用。
3、封装访问属性:
- 用struct定义类的时候,其所有成员默认为public。
- 用class定义类的时候,其所有成员默认为private。
访问属性 属性 对象内部 对象外部 public 公有 可访问 可访问 protected 保护 可访问 不可访问 private 私有 可访问 不可访问
二、类的封装
1.构造函数
1、构造函数:(数组多大就会调用多少次构造,但指针的创建不涉及构造函数的调用)
- 一个类中可以有多个构造函数,可以进行构造函数重载,默认无参数可重载参数,但只能又一个析构函数。(多个构造函数之间是重载关系)
- 构造函数在对象创建时自动调用且只调用一次,一经实现,默认将不复存在。
- 拷贝构造函数的参数一定是引用类型。
2、无参构造函数、带参构造函数、拷贝构造函数的实现。
#include<iostream> using namespace std; class Person { public: int age; Person()//无参构造函数 { cout << "Person 无参构造函数的调用" << endl; } Person(int a)//有参构造函数 { age = a; cout << "Person 有参构造函数的调用" << endl; } Person(const Person &p)//拷贝构造函数 { //将传入的人身上的所有属性,拷贝在我的身上 age = p.age; cout << "Person 拷贝构造函数的调用" << endl; } //析构函数不可以有参数,不可以发生重载 //对象销毁前 会自动调用析构函数 而且只会调用一次 ~Person() { cout << "Person 析构函数的调用" << endl; } }; void test01() { Person p1; //默认构造函数的调用 Person p2(10); //有参构造函数的调用 Person p3(p2); //拷贝构造函数的调用 //注意事项1:调用默认构造函数时候,不用加() //因为下面这行代码,编译器会认为是一个函数的声明 //Person p1(); cout << "p2的年龄为:" << p2.age << endl; cout << "p3的年龄为:" << p3.age << endl; } int main() { test01(); system("pause"); return 0; }
//运行结果: Person 无参构造函数的调用 Person 有参构造函数的调用 Person 拷贝构造函数的调用 p2的年龄为:10 p3的年龄为:10 Person 析构函数的调用 Person 析构函数的调用 Person 析构函数的调用
2.析构函数
1、析构函数
- 构造函数是在实例化的时候被调用,而析构函数在对象销毁的时候被调用。
- 在对象销毁前会自动调用析构函数,而且只会调用一次,目的时清空数据,释放内存,防止内存外泄。
- 无返回值,与类名相同,前面带一个 ~ 符号,无参数,不可重载默认参数。
- 析构函数的作用并不是删除对象,而是在对象销毁前完成一些清理工作。
2、析构函数的实现
#include<iostream> using namespace std; class Person { public: Person() { cout << "Person 构造函数的调用" << endl; } //1.2、析构函数没有返回值 不写void //析构函数名和类名相同 在名称前加~ //析构函数不可以有参数,不可以发生重载 //对象销毁前 会自动调用析构函数 而且只会调用一次 ~Person() { cout << "Person 析构函数的调用" << endl; } }; void test01() { Person P; } int main() { test01(); system("pause"); return 0; }
3.静态成员
1、静态成员分为静态成员变量和静态成员函数。静态成员就是在成员变量和成员函数前加上关键字static,称为静态成员。
2、静态成员属于整个类而不是某个对象,类的静态成员属于类也属于对象,最终归于类。
3、静态成员变量
- 所有对象共享同一份数据,实现了同类对象的信息共享;
- 在编译阶段分配内存;
- 类内声明,类外初始化(类外存储,求类大小,并不包括在内);
- 可以通过类名访问,也可通过对象访问。
4、静态成员的实现
- 静态成员函数只能访问静态成员变量,无法访问非静态成员变量。(因为非静态成员函数在调用的时候this指针会被当作函数参数进行传递,而静态成员函数属于类不属于对象,没有this指针,故无法进行访问)
- private权限,类外无法访问。
- 不在类内部初始化 。
#include<iostream> using namespace std; class person { public: static int m_A; static void func() { m_C = 100; // m_D = 200;报错,在类中m_B无法赋值,因为静态成员函数只能访问静态成员变量; cout << "func函数" << endl; } static int m_C; //不在类内部初始化 int m_D; private: static int m_B;//private权限,类外无法访问 static void func2()//func2()是private,类外无法访问 { cout << "func2函数" << endl; } }; //在类外部初始化静态成员变量 int person::m_A = 100; int person::m_B = 200; int person::m_C = 0; int main() { person p; cout << p.m_A << endl; cout << person::m_A << endl; // cout << person::m_B << endl;此处报错,private权限类外无法访问 p.func(); person::func(); // p.func2();// 注意:func2()是private,无法从 main 直接调用 return 0; }
4.友元
1、友元分为:友元函数和友元类。(目的:提高程序的运行效率,但是破坏了类的封装性和隐蔽性,使得非成员函数可以访问类的私有函数)
2、友元函数、友元类定义(友元=友元函数+友元类):
- 友元的声明位置没有要求,可以在private、protected、public权限区,效果都是一样的;
- 友元是单向的,A在B类中被声明为友元,表示A是B的友元,但B不是A的友元;
- 友元具有和类成员一样的权限,可以访问protected和private权限的成员,但不是类的成员;
3、友元函数
- 友元函数在类中声明时用friend修饰,但是在定义时不需要用friend修饰;
- 友元函数不能被继承:父类的友元函数,继承后并不会成为子类的友元函数;
- 友元函数不具有传递性:A类和B类都是C类的友元类,但是A类和B类并不是友元类;
4、优缺点:
- 缺点:友元函数不是类的成员但是却具有成员的权限,可以访问类中受保护的成员,这破坏了类的封装特性和权限管控;(代码更灵活,但是不能滥用)
- 优点:可以实现类之间的数据共享;
5、友元函数的实现
#include <iostream> using namespace std; class Person{ private: int age; public: Person(){}; Person(int x); friend void print(Person &pn);//声明print是友元函数 }; Person::Person(int x){ this->age = x; } void print(Person &pn){ //因为print是Person类的友元函数,所以在内部可以访问Person类的私有成员age cout << "age=" << pn.age << endl; } int main(void) { Person p(22); print(p); return 0; }
6、友元类的实现
#include <iostream> using namespace std; class Person { private: int age; public: Person() {} Person(int x) : age(x) {} // 使用初始化列表初始化age friend class Printer;// 声明Printer类为Person的友元类 }; class Printer { public: // 因为Printer是Person的友元类,所以可以访问Person的私有成员 void print(Person &pn) { cout << "age=" << pn.age << endl; } }; int main(void) { Person p(22); Printer printer; printer.print(p); return 0; }
5.运算符重载
1、重载的理解:
- 在一个作用域内,可以声明几个功能类似的同名函数(也就是“一名多用”),但这些同名函数的形式参数(参数的个数、类型或顺序)必须不同。
2、总结重载:
- 函数名相同。
- 参数个数不同,参数类型不同,参数顺序不同,均可以构成重载。
- 如果只有返回值类型不同的时候则不可以构成重载。
3、运算符重载:运算符重载的实质就是函数重载。
- 运算符重载格式类型:
返回类型 operator 运算符(参数列表) { 函数体 ; }
4、重载运算符规则:
C++中不允许用户定义新的运算符,只能对已有的运算符进行重载。
重载后的运算符的优先级、结合性也应该保持不变,也不能改变其操作个数和语法结构。
重载后的含义,与操作基本数据类型的运算含义应类似,如加法运算符“+”,重载后也应完成数据的相加操作。
有5个运算符不可重载:类关系运算符“:”、成员指针运算符“*”、作用域运算 符“::”、sizeof运算符、三目运算符“?:”
运算符重载函数不能有默认参数,否则就改变了运算符操作数的个数,是错误的。
用于类对象的运算符一般必须重载,但有两个例外(“=”和“&”不必重载)。
运算符重载函数既可以作为类的成员函数,也可以作为类的友元函数(全局函数)。
三、类的继承
1.继承和派生
1、继承:所谓继承就是在一个已存在的类的基础上建立一个新的类。
- 在继承关系中被继承的类称为基类(或父类),把通过继承关系创建出来的新类称为派生类(子类)。
- 派生类对基类对象的访问由继承方式和成员性质决定。
- 派生类不仅可以继承原来类的成员,还可以增加新的数据成员、增加新的成员函数、重新定义已有成员函数、改变现有成员的属性。
- 继承具有传递性,即派生类能自动继承上层基类的全部数据结构及操作方法(数据成员及成员函数)
2、继承与派生的对应关系:
- 一个派生类只从一个基类派生,这是最常见的继承形式
- 一个派生类有两个及两个以上的基类。如:类C从类A和类B派生。
3、继承方式有public(公有继承)、protected(保护继承)和private(私有继承)
- public(公有继承):基类的公有成员和保护成员被继承为派生类成员时,其访问属性不变。
- protected(保护继承):基类的公有成员和保护成员在派生类中成了保护成员,私有成员仍为基类私有。
- private(私有继承):基类中的公有成员和保护成员在派生类中皆变为私有成员。
- 无论哪种继承方式,基类的私有成员均不能继承。这与私有成员的定义是一致的,符合数据封装的思想。
2.继承中的构造与析构
1、构造顺序(析构是相反的顺序):
- 先构造父类,再构造成员变量,最后构造自己。
- 先构造自己,再构造成员变量,最后构造父类。
#include <iostream> using namespace std; //基类 Object class Object { public: Object(const char* s) { cout << "Object(" << s << ")" << endl; } ~Object() { cout << "~Object()" << endl; } }; //派生类 Parent,继承自 Object class Parent : public Object { public: Parent(const char* s) : Object(s) { cout << "Parent(" << s << ")" << endl; } ~Parent() { cout << "~Parent()" << endl; } }; //派生类 Child,继承自 Parent class Child : public Parent { Object o1; // 成员变量 Object o2; // 成员变量 public: Child(const char* s) : Parent(s), o1("o1"), o2("o2") { //按照声明顺序初始化成员变量 o1 和 o2(每个都会调用 Object 的构造函数) cout << "Child(" << s << ")" << endl; } ~Child() { cout << "~Child()" << endl; } }; int main() { Child c("Parameter from Child!"); // 析构顺序与构造顺序相反 return 0; }
//输出结果如下所示: Object(Parameter from Child!) Parent(Parameter from Child!) Object(o1) Object(o2) Child(Parameter from Child!) ~Child() ~Object() ~Object() ~Parent() ~Object()
1、构造函数调用顺序:
- 首先调用基类 Object 的构造函数(通过 Parent 的构造函数传递参数)。
- 然后调用 Parent 的构造函数。
- 接着,在 Child 的构造函数体内,按照声明顺序初始化成员变量 o1 和 o2(每个都会调用 Object 的构造函数)。
- 最后,调用 Child 的构造函数体。
2、析构函数调用顺序:
- 与构造函数顺序相反,首先调用 Child 的析构函数。
- 然后是成员变量 o2 和 o1 的析构函数(按照声明顺序的逆序)。
- 接着是 Parent 的析构函数。
- 最后是 Object 的析构函数(在 Parent 的析构函数中隐式调用,因为 Parent 继承自 Object)。
3.多继承中的二义性
1、继承中的二义性问题所在:
- 相当于初始化了2个变量,而且地址不同,互相不影响,因此不知道访问哪个地址的变量,进而出现二义性的问题。
- 解决二义性的两种方法:一种是直接限定空间区域、一种是虚继承。
- C++提供虚继承机制,就是防止继承关系中成员访问的二义性。
- 多继承提供了软件重用的强大功能,也增加了程序的复杂性。
#include <iostream> class Base1 { public: void show() { std::cout << "Base1::show()" << std::endl; } }; class Base2 { public: void show() { std::cout << "Base2::show()" << std::endl; } }; class Derived : public Base1, public Base2 { public: void test() {//这里出错了 show(); //尝试调用show,这里将产生二义性错误 //相当于初始化了2个变量,而且地址不同,互相不影响,因此不知道访问哪个地址的变量 } }; int main() { Derived d; d.test(); return 0; }
2、直接限定空间区域方法
#include <iostream> class Base1 { public: void show() { std::cout << "Base1::show()" << std::endl; } }; class Base2 { public: void show() { std::cout << "Base2::show()" << std::endl; } }; class Derived : public Base1, public Base2 { public: void test() { Base1::show(); // 调用Base1的show Base2::show(); // 调用Base2的show } }; int main() { Derived d; d.test(); return 0; }
3、虚继承方法
#include <iostream> class Base { public: void showBase() { // 假设我们还有一个不同的函数来展示虚继承的效果 std::cout << "Base::showBase()" << std::endl; } }; class Base1 : virtual public Base { // 虚继承自Base public: void show() { std::cout << "Base1::show()" << std::endl; showBase(); // 调用Base的showBase } }; class Base2 : virtual public Base { // 虚继承自Base public: void show() { std::cout << "Base2::show()" << std::endl; showBase(); // 调用Base的showBase } }; class Derived : public Base1, public Base2 { public: // 使用作用域解析运算符解决二义性 void test() { Base1::show(); // 调用Base1的show Base2::show(); // 调用Base2的show } }; int main() { Derived d; d.test(); return 0; }
四、类的多态
1.多态
1、C++中的多态:由继承而产生的相关的不同的类,其对象对同一消息会做出不同的响应,称为多态性。
2、多态成立的条件(缺一不可):——>(结果:派生类函数覆盖父类函数)
- 要有继承(父类到子类之间的继承)
- 要有虚函数重写(有关键字virtual关键字)
- 要有父类指针(父类引用)指向子类指针
3、多态实现
#include <iostream> using namespace std; // 定义基类 class Parent { public: virtual void print() { // 声明为虚函数 cout << "Parent:print() do." << endl; } virtual ~Parent() {} // 虚析构函数,用于安全删除派生类对象 }; // 定义派生类 class Child : public Parent { public: void print() override { // 重写基类的虚函数 cout << "Child:print() do." << endl; } }; // 定义一个函数,接收Parent类型的指针 void function(Parent* p) { p->print(); // 通过基类指针调用虚函数,实现多态 } int main() { // 首先是普通调用 // 输出Parent:print() do. 多态未发生,因为这里的指针指向的是基类对象 Parent* pp = new Parent(); pp->print(); // 多态发生,满足条件:基类指针指向派生类对象 Parent* pc = new Child(); pc->print(); // 输出:Child:print() do. // 在函数中作用体现更明显 Parent* pcf = new Child(); function(pcf); // 输出:Child:print() do. //代码逻辑: //当基类指针 pc 或 pcf 指向一个 Child 类的对象时,调用 pc->print() 或 pcf->print() //会执行 Child 类的 print() 函数。这是因为虽然指针的类型是 Parent*, //但它实际上指向的是一个 Child 类的对象。由于 print() 是虚函数, //所以在运行时,C++ 运行时系统会根据指针实际指向的对象的类型 //来确定调用哪个版本的 print() 函数。这就是多态性的体现。 return 0; }