C++面向对象的三大特性为:封装、继承、多态。C++认为万事万物都皆为对象,对象上有其属性和行为。如人可以作为对象,属性有姓名、年龄、身高、体重等,行为有吃、喝、走、跑等。
1.封装
1.1 封装的意义
封装是C++面向对象的三大特性之一。其意义为: 1)将属性和行为作为一个整体:在设计类的时候,属性和行为写在一起,表现事务语法class 类名{ 访问权限 : 属性 / 行为 };
#include<iostream>
using namespace std;
const double PI = 3.14;
class circle {
public:
circle(int r) {
this->r = r;
}
double GetZC() {
return 2 * r * PI;
}
private:
int r;
};
int main() {
circle c(10);
cout << c.GetZC() << endl;
return 0;
}
类中的属性和行为,统一称为成员;属性:成员属性、成员变量;行为:成员函数、成员方法。
2)将属性和行为加以权限控制,访问权限有三种: i:public 公共权限:成员 类内可以访问,类外可以访问 ii:protected 保护权限:成员 类内可以访问,类外不可访问,子类可以访问父类中的保护内容 iii:private 私有权限:成员 类内可以访问,类外不可访问,子类不可以访问父类中的私有内容
#include<iostream>
#include<string>
using namespace std;
class person{
public:
string name;
protected:
string car;
private:
int password;
public:
void func() {
name = "张三";
car = "拖拉机";
password = 123456;
}
};
int main() {
person p;
p.name = "李四";
return 0;
}
1.2 struct和class区别
在C++中struct和class唯一的区别就在于默认的访问权限不同,struct默认权限为公共,class默认权限为私有。
#include<iostream>
#include<string>
using namespace std;
class person{
int ID;
};
struct MyStruct{
int ID;
};
int main() {
person p;
MyStruct m;
m.ID = 100;
return 0;
}
1.3 成员属性设置为私有
优点: 1)将所有成员属性设置为私有,可以自己控制读写权限。
#include<iostream>
#include<string>
using namespace std;
class person{
public:
void setname(string name) {
this->name = name;
}
string getname() {
return name;
}
private:
string name;
};
int main() {
person p;
p.setname("zhinen");
cout << p.getname() << endl;
return 0;
}
2)对于写权限,可以检测数据的有效性。
#include<iostream>
#include<string>
using namespace std;
class person{
public:
void setname(string name) {
this->name = name;
}
string getname() {
return name;
}
void setage(int age) {
if (age >= 0 && age <= 100) {
this->age = age;
}
else {
cout << "年龄设置错误!已默认为0。" << endl;
this->age = 0;
}
}
int getage() {
return age;
}
private:
string name;
int age;
};
int main() {
person p;
p.setname("zhinen");
cout << p.getname() << endl;
p.setage(200);
cout << p.getage() << endl;
p.setage(60);
cout << p.getage() << endl;
return 0;
}
2.对象的初始化和清理
2.1 构造函数和析构函数
C++中的构造函数和析构函数会被编译器自动调用,完成对象的初始化和清理工作。如果我们不提供构造和析构函数,编译器会提供编译器提供的构造函数和析构函数是空实现。 构造函数:在创建对象时为对象的成员属性赋值,构造函数自动调用,无需手动调用 析构函数:在对象销毁前系统自动调用,执行一些清理工作。 构造函数语法:类名 (){} 1)没有返回值也不写void; 2)函数名称和类名相同; 3)构造函数可以有参数,因此可以发生重载; 4)程序在调用对象时会自动调用构造函数,无需手动调用而且只会调用一次。 析构函数语法:~类名 (){} 1)没有返回值也不写void; 2)函数名称和类名相同,在名称前面加上~; 3)构造函数不可以有参数,因此不可以发生重载; 4)程序在对象销毁前时会自动调用析构函数,无需手动调用而且只会调用一次。
#include<iostream>
#include<string>
using namespace std;
class person{
public:
person() {
cout << "person" << endl;
}
~person() {
cout << "~person" << endl;
}
};
int main() {
person p;
return 0;
}
2.2 构造函数的分类及调用
两种分类方式: 1)按参数分为:有参构造和无参构造(默认构造) 2)按类型分为:普通构造和拷贝构造 三种调用方式: 1)括号法 2)显示法 3)隐式转换法
#include<iostream>
#include<string>
using namespace std;
class person{
public:
person() {
cout << "person()" << endl;
}
person(int a) {
age = a;
cout << "person(int a)" << endl;
}
person(const person &p) {
age = p.age;
cout << "person(person p)" << endl;
}
private:
int age;
};
int main() {
person p;
person h(10);
person q(p);
person p1;
person p2 = person(10);
person p3 = person(p2);
person p4;
person p5 = 10;
person p6 = p5;
return 0;
}
2.3 拷贝构造函数调用时机
C++中拷贝函数调用时机通常有三种情况: 1)使用一个已经创建完毕的对象来初始化一个新对象 2)值传递的方式给函数参数传值 3)以值返回局部对象
#include<iostream>
#include<string>
using namespace std;
class person{
public:
person() {
age = 0;
cout << "person()" << endl;
}
person(int a) {
age = a;
cout << "person(int a)" << endl;
}
person(const person &p) {
age = p.age;
cout << "person(const person & p)" << endl;
}
int getage() {
return age;
}
private:
int age;
};
void fun(person) {
}
person fun2() {
person p;
return p;
}
int main() {
person p1(10);
person p2(p1);
cout << p2.getage() << endl;
person p3;
fun(p3);
person p4 = fun2();
return 0;
}
2.4 构造函数调用规则
默认情况下,c++编译器至少给一个类添加3个函数 1)默认构造函数(无参,函数体为空) 2)默认析构函数(无参,函数体为空) 3)默认拷贝函数,对属性进行值拷贝 构造函数调用规则如下: 1)如果用户定义有参构造函数,C++不再提供默认无参构造,但是会提供默认拷贝函数
#include<iostream>
#include<string>
using namespace std;
class person{
public:
person(int a) {
age = a;
cout << "person(int a)" << endl;
}
int getage() {
return age;
}
~person() {
cout << "~person()" << endl;
}
private:
int age;
};
int main() {
person p1(20);
person p2(p1);
cout << p2.getage() << endl;
return 0;
}
2)如果用户定义拷贝构造函数,C++不会再提供其他构造函数
#include<iostream>
#include<string>
using namespace std;
class person{
public:
person(const person &p) {
age = p.age;
cout << "person(const person & p)" << endl;
}
int getage() {
return age;
}
~person() {
cout << "~person()" << endl;
}
private:
int age;
};
int main() {
return 0;
}
2.5 深拷贝和浅拷贝
浅拷贝:简单的赋值拷贝操作 深拷贝:在堆区重新申请空间,进行拷贝操作 如果属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题。
#include<iostream>
#include<string>
using namespace std;
class person{
public:
person() {
age = 0;
cout << "person()" << endl;
}
person(int a,int h) {
age = a;
height = new int(h);
cout << "person(int a,int h)" << endl;
}
person(const person &p) {
age = p.age;
height = new int(*p.height);
cout << "person(const person & p)" << endl;
}
int getage() {
return age;
}
int* getheight() {
return height;
}
~person() {
if (height != NULL) {
delete height;
height = NULL;
}
cout << "~person()" << endl;
}
private:
int age;
int* height;
};
int main() {
person p(18,160);
cout << p.getage() << " " << *p.getheight() << endl;
person p1(p);
cout << p1.getage() << " " << *p1.getheight() << endl;
return 0;
}
2.6 初始化列表
C++提供了初始化列表语法,用来初始化属性 语法:构造函数(): 属性1(值1), 属性2(值2)…{}
#include<iostream>
#include<string>
using namespace std;
class person{
public:
person(int a,int h) :age(a),height(h) {}
int getage() {
return age;
}
int getheight() {
return height;
}
~person() {
cout << "~person()" << endl;
}
private:
int age;
int height;
};
int main() {
person p(18,160);
cout << p.getage() << " " << p.getheight() << endl;
return 0;
}
2.7 类对象作为类成员
C++类中的成员可以是另一个类的对象,称该成员为对象成员。
#include<iostream>
#include<string>
using namespace std;
class Phone{
public:
Phone(string name) {
this->name = name;
cout << "phone" << endl;
}
string name;
~Phone() {
cout << "~phone" << endl;
}
};
class person{
public:
person(string myname, string pname) : name(myname), myphone(pname) {
cout << "person" << endl;
}
~person() {
cout << "~person" << endl;
}
string name;
Phone myphone;
};
int main() {
person p("zhinen","huawei");
cout << p.name << " " << p.myphone.name << endl;
return 0;
}
2.8 静态成员
静态成员就是在成员变量和成员函数前加上关键词static。静态成员分为: 1)静态成员变量:所i有对象共享同一份数据;在编译阶段分配内存;类内声明,类外初始化。 2)静态成员函数:所有对象共享同一个函数;静态成员函数只能访问静态成员变量。
#include<iostream>
#include<string>
using namespace std;
class person{
public:
static void fun() {
id = 1;
cout << "static function" << endl;
}
static int id;
int grade;
private:
static void func() {
cout << "func" << endl;
}
};
int person::id = 0;
int main() {
person p;
p.fun();
person::fun();
return 0;
}
3. C++对象模型和this指针
3.1 成员变量和成员函数分开存储
在C++中,类内的成员变量和成员函数分开存储,只有非静态成员变量菜属于类的对象上。
#include<iostream>
#include<string>
using namespace std;
class person{
public:
private:
};
class animal {
public:
int a;
};
class cat {
public:
int a;
static int b;
};
class dog {
public:
int a;
void fun() {}
};
class tiger {
public:
int a;
static void fun() {}
};
int main() {
person p;
cout << sizeof(p) << endl;
animal a1;
cout << sizeof(a1) << endl;
cat c1;
cout << sizeof(c1) << endl;
dog d1;
cout << sizeof(d1) << endl;
tiger t1;
cout << sizeof(t1) << endl;
return 0;
}
3.2 this指针的概念
this指针指向被调用的成员函数所属的对象,this指针是隐含每一个非静态成员函数内的一种指针。this指针不需要定义,直接使用即可。this指针的用途: 1)当形参和成员变量同名时,可用this指针来区分 2)在类的非静态成员函数中返回本身对象,可使用return *this;
#include<iostream>
#include<string>
using namespace std;
class person{
public:
person(int age) {
this->age = age;
}
person& add(person &p) {
this->age += p.age;
return *this;
}
int age;
private:
};
int main() {
person p1(18);
cout << p1.age << endl;
person p2(20);
p2.add(p1).add(p1);
cout << p2.age << endl;
return 0;
}
3.3 空指针访问成员函数
C++中空指针也可以调用成员函数的,但是也要注意有没有用到this指针。如果用到,需要加以判断保证代码的健壮性。
#include<iostream>
#include<string>
using namespace std;
class person{
public:
person(string name = "zhinen", int age = 18) {
this->name = name;
this->age = age;
}
void getname() {
cout << name << endl;
}
void getage() {
cout << age << endl;
}
void fun() {
cout << "person" << endl;
}
private:
string name;
int age;
};
class cat {
public:
cat(string name = "zhinen", int age = 18) {
this->name = name;
this->age = age;
}
void getname() {
if (this == NULL)
return;
cout << name << endl;
}
void getage() {
if (this == NULL)
return;
cout << age << endl;
}
void fun() {
cout << "person" << endl;
}
private:
string name;
int age;
};
int main() {
person* p=NULL;
p->fun();
cat* c = NULL;
c->getage();
c->getname();
return 0;
}
3.4 const修饰成员函数
常函数: 1)成员函数后加const称之为常函数; 2)常函数内不可以修改成员属性 3)成员属性声明时加关键字mutable后,在常函数中依然可以修改 常对象: 1)声明对象前加const称该对象为常对象 2)常对象只能调用常函数
#include<iostream>
#include<string>
using namespace std;
class person{
public:
person(string name = "zhinen", int age = 18) {
this->name = name;
this->age = age;
}
void getname() const{
mininame = "awen";
cout << name << " " << mininame << endl;
}
void getage() {
cout << age << endl;
}
void fun() {
cout << "person" << endl;
}
string name;
int age;
mutable string mininame;
};
int main() {
const person p;
p.mininame = "zhi";
p.getname();
return 0;
}
4.友元
友元的目的就是让一个函数或者类访问另一个类中私有成员。友元的关键字为friend。友元函数的三种实现:
4.1全局函数做友元
#include<iostream>
#include<string>
using namespace std;
class person{
friend void fun(person& p);
public:
person() {
name = "zhinen";
phone = "private";
}
string name;
private:
string phone;
};
void fun(person& p) {
cout << p.name << endl;
cout << p.phone << endl;
}
int main() {
person p;
fun(p);
return 0;
}
4.2 类做友元
#include<iostream>
#include<string>
using namespace std;
class person{
friend class person1;
public:
person() {
name = "zhinen";
phone = "private";
}
string name;
private:
string phone;
};
class person1 {
public:
void visit();
person* p;
person1() {
p = new person;
}
};
void person1::visit() {
cout << p->name << endl;
cout << p->phone << endl;
}
int main() {
person1 p;
p.visit();
return 0;
}
4.3 成员函数做友元
#include<iostream>
#include<string>
using namespace std;
class person;
class person1 {
public:
void visit();
person * p;
person1();
};
class person{
friend void person1::visit();
public:
person() {
name = "zhinen";
phone = "private";
}
string name;
private:
string phone;
};
void person1::visit() {
cout << p->name << endl;
cout << p->phone << endl;
}
person1::person1() {
p = new person;
}
int main() {
person1 p;
p.visit();
return 0;
}
5.运算符重载
运算符重载概念:对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型。
5.1 加号运算符重载
定义两个自定义数据类型相加的运算。 全局函数实现运算符重载: 本质:person p3=operator+(p1,p2);
#include<iostream>
#include<string>
using namespace std;
class person{
public:
int a;
int b;
};
person operator+(person& p1, person& p2) {
person temp;
temp.a = p1.a + p2.a;
temp.b = p1.b + p2.b;
return temp;
}
int main() {
person p1;
p1.a = 10;
p1.b = 10;
person p2;
p2.a = 10;
p2.b = 10;
person p3 = p1 + p2;
cout << p3.a << " " << p3.b << endl;
return 0;
}
或者,成员函数实现函数重载: 本质person p3=p1.operator+(p2);
#include<iostream>
#include<string>
using namespace std;
class person{
public:
person operator+(person& p) {
person temp;
temp.a = this->a + p.a;
temp.b = this->b + p.b;
return temp;
}
int a;
int b;
};
int main() {
person p1;
p1.a = 10;
p1.b = 10;
person p2;
p2.a = 10;
p2.b = 10;
person p3 = p1 + p2;
cout << p3.a << " " << p3.b << endl;
return 0;
}
总结:对于内置的数据类型的表达式的运算符是不可能改变的;不要滥用运算符重载。
5.2 左移运算符重载
作用:可以输出自定义数据类型。
#include<iostream>
#include<string>
using namespace std;
class person{
public:
int a;
int b;
};
void operator<<(ostream &cout, person& p) {
cout << p.a << " " << p.b << endl;
}
int main() {
person p1;
p1.a = 10;
p1.b = 10;
cout << p1;
return 0;
}
链式思想实现:
#include<iostream>
#include<string>
using namespace std;
class person{
public:
int a;
int b;
};
ostream &operator<<(ostream &cout, person& p) {
cout << p.a << " " << p.b;
return cout;
}
int main() {
person p1;
p1.a = 10;
p1.b = 10;
cout << p1 << endl;
return 0;
}
5.3 递增运算符重载
作用:通过重载递增运算符,实现自己的整型数据。
#include<iostream>
#include<string>
using namespace std;
class person{
public:
person& operator++() {
a++;
return *this;
}
person operator++(int) {
person p = *this;
a++;
return p;
}
int a;
};
ostream &operator<<(ostream &cout, person& p) {
cout << ++p.a << endl;
cout << p.a++ << endl;
cout << p.a << endl;
return cout;
}
int main() {
person p1;
p1.a = 10;
cout << p1 << endl;
return 0;
}
5.4 递减运算符重载
#include<iostream>
#include<string>
using namespace std;
class person{
public:
person& operator--() {
a--;
return *this;
}
person operator--(int) {
person p = *this;
a--;
return p;
}
int a;
};
ostream &operator<<(ostream &cout, person& p) {
cout << --p.a << endl;
cout << p.a-- << endl;
cout << p.a << endl;
return cout;
}
int main() {
person p1;
p1.a = 10;
cout << p1 << endl;
return 0;
}
5.5 赋值运算符重载
C++编译器至少给一个类添加4个函数: 1)默认构造函数:无参,函数体为空 2)默认析构函数:无参,函数体为空 3)默认拷贝函数,对属性进行值拷 4)赋值运算符operator=,对属性进行值拷贝 如果类中有属性指向堆区,做赋值操作时也会出现深浅拷贝问题。
#include<iostream>
#include<string>
using namespace std;
class person{
public:
person(int a){
this->a = new int(a);
}
person& operator=(person &p) {
if (a != NULL) {
delete a;
a = NULL;
}
a = new int(*p.a);
return *this;
}
~person() {
if (a != NULL) {
delete a;
a = NULL;
}
}
int *a;
};
int main() {
person p1(20);
person p2(22);
p2 = p1;
cout << *p1.a << endl;
cout << *p2.a << endl;
return 0;
}
5.6 关系运算符重载
作用:重载关系运算符,可以让两个自定义类型的对象进行对比操作。
#include<iostream>
#include<string>
using namespace std;
class person{
public:
person(int a,string b){
this->a = a;
this->b = b;
}
bool operator==(person& p) {
if (this->a == p.a && this->b == p.b)
return true;
return false;
}
bool operator!= (person & p) {
if (this->a == p.a && this->b == p.b)
return false;
return true;
}
int a;
string b;
};
int main() {
person p1(18, "zhinen");
person p2(18, "zhinen");
if (p1 == p2) {
cout << "==" << endl;
}
else {
cout << "!=" << endl;
}
if (p1 != p2) {
cout << "!=" << endl;
}
else {
cout << "==" << endl;
}
return 0;
}
5.7 函数调用运算符重载
函数调用运算符()也可以重载,由于重载后使用的方式非常像函数的调用,因此称为仿函数。仿函数没有固定的写法,非常灵活。
#include<iostream>
#include<string>
using namespace std;
class person{
public:
void operator()(string str) {
cout << str << endl;
}
};
class person1 {
public:
int operator()(int a,int b) {
return a + b;
}
};
int main() {
person p1;
p1("zhinen");
person1 p2;
cout << p2(10, 20) << endl;
return 0;
}
6. 继承
继承是面向对象的三大特性之一。有些类与类之间存在特殊的关系,继承的好处:减少重复的代码。
6.1 继承的基本语法
语法:class 子类 : 继承方式 父类 子类也成为派生类;父类也成为基类。 派生类中的成员,包含两大部分:一类是从基类继承过来的,一类是自己增加的成员,从基类继承过来的表现其共性,而新增的成员体现了其个性。
6.2 继承方式
继承的方式一共有三种: 1)公共继承 2)保护继承 3)私有继承
#include<iostream>
#include<string>
using namespace std;
class A {
public:
int a;
protected:
int b;
private:
int c;
};
class B:public A {
public:
void fun() {
a = 10;
b = 10;
}
};
class C :protected A {
public:
void fun() {
a = 10;
b = 10;
}
};
class D :private A {
public:
void fun() {
a = 10;
b = 10;
}
};
class F :public D {
public:
void fun() {
}
};
int main() {
B p;
p.a = 100;
C p2;
D p3;
return 0;
}
6.3 继承中的对象模型
#include<iostream>
#include<string>
using namespace std;
class A {
public:
int a;
protected:
int b;
private:
int c;
};
class B:public A {
public:
int d;
};
int main() {
cout << sizeof(B) << endl;
return 0;
}
6.4 继承中构造和析构顺序
子类继承父类后,当子类创建子类对象,也会调用父类的构造函数。
#include<iostream>
#include<string>
using namespace std;
class A {
public:
A() {
cout << "A的构造函数" << endl;
}
~A() {
cout << "A的析构函数" << endl;
}
};
class B:public A {
public:
B() {
cout << "B的构造函数" << endl;
}
~B() {
cout << "B的析构函数" << endl;
}
};
class C :public B {
public:
C() {
cout << "C的构造函数" << endl;
}
~C() {
cout << "C的析构函数" << endl;
}
};
int main() {
C p;
return 0;
}
6.5 继承同名成员处理方式
当子类和父类出现同名的成员: 1)访问子类同名成员,直接访问即可 2)访问父类同名成员,需要加作用域
#include<iostream>
#include<string>
using namespace std;
class A {
public:
A() {
a = 100;
}
void fun() {
cout << "A" << endl;
}
int a;
};
class B:public A {
public:
B() {
a = 200;
}
void fun() {
cout << "B" << endl;
}
int a;
};
int main() {
B p;
cout << p.a << endl << p.A::a << endl;
p.fun();
p.A::fun();
return 0;
}
6.6 继承同名静态成员处理方式
静态成员和非静态成员出现同名,处理方式一致: 1)访问子类同名成员,直接访问即可 2)访问父类同名成员,需要加作用域
#include<iostream>
#include<string>
using namespace std;
class A {
public:
static void fun() {
cout << "A" << endl;
}
static int a;
};
int A::a = 100;
class B:public A {
public:
static void fun() {
cout << "B" << endl;
}
static int a;
};
int B::a = 200;
int main() {
B p;
cout << p.a << endl << p.A::a << endl;
cout << B::a << endl << B::A::a << endl;
p.fun();
p.A::fun();
B::fun();
B::A::fun();
return 0;
}
6.7 多继承语法
C++允许一个类继承多个类,但实际开发中不建议用多继承。 语法:class 子类 : 继承方式 父类1 , 继承方式 父类2,… 多继承可能会引发父类中有同名成员出现,需要加作用域区分。
#include<iostream>
#include<string>
using namespace std;
class A {
public:
A() {
a = 100;
}
int a;
};
class B {
public:
B() {
a = 200;
}
int a;
};
class C :public A,public B{
public:
C() {
c = 300;
d = 400;
}
int c;
int d;
};
int main() {
cout << sizeof(C) << endl;
C p;
cout << p.A::a << endl << p.B::a << endl;
return 0;
}
6.8 菱形继承
概念:两个派生类继承同一个基类,又有某个类同时继承这两个派生类,这种继承称为菱形继承,或者钻石继承。
#include<iostream>
#include<string>
using namespace std;
class A {
public:
int a;
};
class B:public A {
public:
};
class C :public A{
public:
};
class D :public B, public C {
};
int main() {
D p;
p.B::a = 100;
p.C::a = 200;
return 0;
}
利用虚继承解决菱形继承问题。
#include<iostream>
#include<string>
using namespace std;
class A {
public:
int a;
};
class B:virtual public A {
public:
};
class C :virtual public A{
public:
};
class D :public B, public C {
};
int main() {
C p;
p.a = 12;
cout << p.a << endl;
return 0;
}
7.多态
7.1 多态的基本概念
多态时C++面向对象三大特性之一。多态分为两类: 1)静态多态:函数重载和运算符重载属于静态多态,复用函数名 2)动态多态:派生类和虚函数实现运行时多态 静态多态和动态多态的区别: 1)静态多态的函数地址早绑定-编译阶段确定函数的地址 2)动态多态的函数地址晚绑定-运行阶段确定函数的地址
|