c++面向对象的三大特性为:封装、继承和多态。
c++认为一切皆对象,对象有其属性和行为。
以人为例子 属性:身高、体重、年龄。(一般使用变量) 行为:唱、跳、rap、篮球。(一般使用函数) 就是把属性和行为看做一个整体,对外只暴露接口
类的定义语法: class 类名 { 访问权限: 属性/行为 };通过类可以创造一个具体的对象
类名 对象名;例子:
class crice//定义一个圆类 { public: //设置访问权限 float pi = 3.14; int r; float getfun() { return pi * r*r; } }; int main() { crice c1;//给圆类定义一个对象c1 c1.r = 10;//把10传递给对象c1中的r cout << c1.getfun() << endl;//调用c1中的getfun函数 }访问权限有三类: ①公共权限:public(类内可访问,类外可访问) ②保护权限:protected(类内可访问,类外不可访问) ③私有权限:private(类内可访问,类外不可访问)
①struct的默认访问权限是公有 ②class的默认访问权限是私有
这两个函数会被编译器自动调用,完成对象的初始化和清理工作。 如果用户没有书写这两个函数,那么编译器会调用内部的提供的。 编译器内部提供的这两个函数都是空实现
构造函数:对象创建时自动调用 析构函数:对象销毁前自动调用
构造函数语法: 类名(){ }
class A { public: A()//构造函数,与类同名,可以有参数,会发生重载 { cout << "构造函数" <<endl; } }; int main() { A a1;//创建了一个对象 return 0; }析构函数语法: ~类名(){ }
class A { public: ~A()//析构函数,不能发生重载 { cout << "析构函数" <<endl; } }; int main() { A a1; return 0;//程序结束时,a1的空间被销毁,调用析构函数 }两种分类方式: 按照参数分:有参构造和无参构造。 按照类型分:普通构造和拷贝构造。
拷贝构造函数: 类名(const 类名& 对象名) class crice { int r; //只有这种形式才叫拷贝构造,这里的参数必须是一个该类的引用 crice (crice& p) { r=p.r;//将传入人身上的所有属性拷贝到我身上, } }调用方法
1.括号法 crice p1; //没有括号,默认构造函数调用 crice p2(10);//会调用有参构造函数 crice p3(p2);//参数为crice类,调用拷贝构造函数 crice p1();//这样写会被认为是一个函数的声明,不会调用构造函数 2.显示法 crice p1; crice p2 = crice(10);//有参构造 crice p2 = crice(p3);//拷贝构造 crice(10);//当前行结束后系统会自动回收掉无名对象,所以它的构造和析构都发生在这一行 3.隐式转化法 crice p4 = 10;//相当于crice p4(10),有参构造 crice p5 = p4;//相当于crice p5(p4),拷贝构造1.使用已经创建完毕的对象来对象来初始化一个新对象
int main() { preson p1;//构造了一个对象。 person p2(p1);//使用了方法一 }2.以值传递的方式给函数参数传值
void test(person p) { } int main { person p1; test(p1);//函数的调用为值的传递,使用了方法二,相当于peson p = p1; }3.值方式返回局部对象
person test() { person p1; return p1; //p1是一个局部对象,返回的仅仅只是它的值,实际上创造了一个无名对象来接收它 }默认情况下,c++编译器会给一个类默认添加三个函数 1.默认构造函数(空),2.默认析构函数(空)3.默认拷贝构造函数(进行值拷贝)
调用规则: 1.没有书写任何函数时,编译器会默认启用三个默认函数。 2.如果我们书写了有参构造,编译器不在启用无参构造,但是会启用拷贝构造。 3.如果我们书写了拷贝构造,编译器不会提供任何其他构造。 注:默认的拷贝构造里是成员值的拷贝的,程序员自己书写拷贝构造后,需要自主实现值拷贝。
浅拷贝:简单的赋值,编译器默认的拷贝构造函数用的是浅拷贝。 深拷贝:在堆区重新申请空间 ,需要自定义拷贝构造函数。
指针变量的值传递,传递的是它们指向空间的地址,当其中一个空间被释放后就无法再使用该空间了(一个空间只能被释放一次)
语法: 构造函数( ):属性1(值1),属性2(值2){构造函数体}
class person { private: int age; int weight; public: //无参情况,age=18,weight=180 person():age(18),weight(180) { cout << "无参构造函数"<<endl; } //有参情况, m_age,weight = m_weight; person(int m_age,int m_weight):age(m_age),weight(m_weight) { cout << "有参构造函数"<<endl; } };c++中一个类的成员可以是另一个类的对象
class A {};//类A必须书写在类B的前面 class B { A a1; A a2; };如上例,当创建类B对应的对象时,需要先创建两个A类对象。 先构造其他类的对象,当构造完所有的后,再构造自身
在c++中,类的成员变量和函数分开存储, 只有非静态成员变量才属于类的对象。 空类占用内存大小为一个字节
this指针指向被调用成员函数所属的对象,不需要定义,可以直接使用 用途: 1.当形参和员变量同名时,可以使用this指针区分 2.在类的非静态成员函数中返回对象本身,可以使用return *this。
class A { private: int age; public: A(int age)//这里形参和变量同名 { //age = age;这样写实际是形参给形参赋值 this->age = age;//this指针指向p1的age } A set(int x) { age = age + x; return *this; } }; A p1(18);//给p1的age赋值18 p1.set(18);//给p1的age加18常函数: 1.成员函数后加const就变成了常函数。 2.常函数不可以修改成员属性。 3.成员属性声明时加mutable后,在常函数中依然可以修改。 常对象: 1.声明对象前加const就变成了常对象。 2.常对象只能调用常函数。
class person { public: //this指针的本质是指针常量,其指向不可以被修改(person * const this) void showperson()const//相当于const person * const this { this->name = 10;//错误代码 this->name1= 10;//正确代码 int name; mutable int name1; void func() { } }; int main() { const person p;//常对象 p.name = 100;//错误代码 p.name1 = 100;//正确代码 p.func()//错误代码,常对象不能调用普通函数,因为普通函数有可能修改属性 }友元的目的就是让一个函数或者类能访问另一个类中的私有成员 关键字:friend
友元函数仅仅只是类A的朋友,不属于类A的成员,不受权限影响
概念:给已有的运算符重新进行定义,赋予其另一种功能
汽车、火车、轮船除了拥有交通工具的共性之外,还拥有自己的特性。 通过继承,我们可以减少代码的书写量,继承的过程,实际上也就是封装范围逐步扩充的过程。
语法:class 子类名:继承方式1 父类名1,继承方式2 父类名2 ……
class A//声明父类A { }; class B:public A//B以公有的方式继承A { //成员1,从基类继承而来的成员,体现其共性 //成员2,属于自己的成员,体现了其个性 }子类也叫派生类,父类也叫基类。 派生和继承其实说的是一件事情,只不过它们的发起和承受方相反。
继承方式有三种:public,private,protected。 规则: 1.以公有方式继承时,公有权限和保护权限不变,私有权限不可访问 2.以保护方式继承时,公有权限和保护权限都变为保护,私有权限不可访问。 3.以私有方式继承时,公有权限和保护权限都变为私有,私有权限不可访问。
无论变量访问权限是什么,子类会将父类的所有非静态变量继承
子类继承父类后,当创建子类对象后,也会调用父类的构造函数,当创建一个子类后,构造它的父类,再构造它自己。
class base { base() { cout <<"base 构造"<<endl; } ~base() { cout <<"base 析构"<<endl; } }; calss son:public base { son() { cout <<"son 构造"<<endl; } ~son() { cout <<"son 析构"<<endl; } }; son s1的运行结果: base 构造 son 构造 son 析构 base 析构当子类中出现父类的同名函数时,子类会隐藏所有父类的同名函数(即使参数不一致)
继承关系如图,B和C继承自A,而D又继承自B和C,这样的继承就叫做菱形继承。
当B和C中有同名数据时,在D中使用时会有二义性,需要加作用域区分当D继承A的独有数据时,由于分两条线继承,也会报二义性。虚继承: class A : virtual public B — A 称为为虚基类(这样继承的数据就只有一份)
多态分为两类: 1.静态多态:函数重载和运算符重载(早绑定,编译阶段确定地址) 2.动态多态:派生类(有父子关系)和虚函数实现(晚绑定,运行阶段确定地址)
class A { public: void speak()//前面加virtual可实现地址晚绑定,打印B speak { cout << "A speak" <<endl; } }; class B:public A { public: //返回值类型 函数名 参数列表完全相同才能实现重写 void speak()//重写父类的虚函数 { cout << "B speak" <<endl; } }; //早绑定,在编译时就确定了是A的地址 void dospeak(A &pa)//A &pa = b1; 父类的指针或引用可指向子类对象 { pa.speak();//结果为A speak(非虚看指针,虚看对象) } B b1; dospeak(b1);总结: 多态的满足条件:1.有继承关系. 2.子类重写父类的虚函数
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wo4sxzY3-1603264360265)(https://note.youdao.com/yws/api/personal/file/E706CD44DFB046E3BAE3FB5C712C1400?method=download&shareKey=7dfb0e42f1c0431fbec8f9dbd7c3d230)]
多态中,通常父类中虚函数的实现是毫无意义的,因此可以将虚函数写为纯虚函数 拥有纯虚函数的类称为抽象类
virtual 返回值类型 函数名 参数列表(参数列表)=0;//父类约定访问规则,子类具体实现特点: ①无法实例化对象 ②子类必须重写父类中的纯虚函数,否则也属于抽象类
当以父类指针访问子类时,在释放父类指针时不会调用析构函数 虚析构:
class A { public: A() { cout << "A 构造" << endl; } ~A()//想要调用B的析构需要加virtual { cout << "A 析构" << endl; } virtual void fun() = 0; }; class B:public A { public: B() { cout << "B 构造" << endl; } ~B() { cout << "B 析构" << endl; } void fun() { cout << "fun" << endl; } }; int main() { A* a = new B(); a->fun(); delete a;//释放时只会调用A的析构函数 }