目录
C++面向对象的三大特性为:封装、继承、多态
C++认为万事万物皆为对象,对象上有其属性和行为。
具有相同性质的对象可抽象为类。
一、封装
1、封装的意义
(1)将属性和行为作为一个整体,表现生活中的事物。
语法:class 类名 { 访问权限:属性/行为 };
注意:类中的属性和行为统称为成员
属性:成员属性/成员变量
行为:成员函数/成员方法
举例1:设计一个圆类,求圆的周长
#include<iostream>
using namespace std;//命名空间
const double PI = 3.14;
class Circle
{
//访问权限
public:
//属性
int r;
//行为
double caclulate()
{
return 2 * PI * r;
}
};
int main()
{
//通过圆类创建具体的圆(对象)——实例化
Circle c1;
c1.r = 10;
cout << "圆的周长" << c1.caclulate() << endl;
system("pause");
return 0;
}
举例2:设计一个学生类,包含姓名和学号
#include<iostream>
using namespace std;//命名空间
#include<string.h>
class Student
{
//访问权限
public:
//属性
string name;
int id;
//行为
void show()
{
cout << "学生的姓名" << name << "学生的学号" << id << endl;
}
};
int main()
{
//实例化
Student s;
s.name = "张三";
s.id = 10;
s.show();
system("pause");
return 0;
}
(2)将属性和行为加以权限控制:
public公共权限:类内可以访问、类内可以访问
protected保护权限:类内可以访问、类外不可以访问、子类可以访问父类的保护内容
private私有权限:类内可以访问、类外不可以访问、子类不可以访问父类的私有内容
2、struct和class区别
唯一区别:默认的访问权限不同:struct默认权限为公共、class默认权限为私有
3、成员属性设置为私有
优点1:将所有成员属性设置为私有,可以自己控制读写权限
优点2:对于写权限,我们可以检测数据的有效性
举例:创建立方体类、设计属性、设计行为:获取立方体面积和体积、利用全局函数和成员函数判断两个立方体是否相等
#include<iostream>
using namespace std;//命名空间
#include<string.h>
class Cube
{
private:
int l;
int w;
int h;
public:
void setl(int m_l)
{
l = m_l;
}
int getl()
{
return l;
}
void setw(int m_w)
{
w = m_w;
}
int getw()
{
return w;
}
void seth(int m_h)
{
h = m_h;
}
int geth()
{
return h;
}
int gets()
{
return 2 * (l * w + l * h + h * w);
}
int getv()
{
return l * w * h;
}
bool issame(Cube& c)
{
if (l == c.getl() && h == c.geth() && w == c.getw())
{
return true;
}
return false;
}
};
//利用全局函数判断两个立方体是否相等
bool issame(Cube& c1, Cube& c2)
{
if (c1.getl() == c2.getl() && c1.geth() == c2.geth() && c1.getw() == c2.getw())
{
return true;
}
return false;
}
int main()
{
//实例化
Cube c1;
c1.seth(10);
c1.setl(10);
c1.setw(10);
cout << "c1的面积为" << c1.gets() << " c1的体积为" << c1.getv() << endl;
Cube c2;
c2.seth(10);
c2.setl(10);
c2.setw(10);
//利用全局函数
bool ret = issame(c1, c2);
if (ret)
{
cout << "c1和c2相等" << endl;
}
else
{
cout << "c1和c2不相等" << endl;
}
//利用成员函数
ret = c1.issame(c2);
if (ret)
{
cout << "c1和c2相等" << endl;
}
else
{
cout << "c1和c2不相等" << endl;
}
system("pause");
return 0;
}
二、对象的初始化和清理
1.构造函数和析构函数
(1)构造函数和析构函数会被编译器自动调用,完成对象初始化和清理工作,如果我们不提供构造和析构函数,编译器会提供(空实现)。
(2)构造函数:
作用:主要作用在于创建对象时为对象的成员属性赋值。
语法:类名(){ }
注意:没有返回值也不写void、函数名称与类名相同、构造函数可以有参数,因此可以发生重载、程序在调用对象时候会自动调用构造,无需手动调用,而且只会调用一次。
(3)析构函数:
作用:主要作用在于对象销毁前系统自动调用,执行一些清理工作。
语法:~类名(){ }
注意:析构函数,没有返回值也不写void、函数名称与类名相同,在名称前加~、析构函数不可以有参数,因此不可以发生重载、程序在对象销毁前会自动调用析构,无需手动调用,而且只会调用一次。
2.构造函数的分类及调用
(1)两种分类方式
按参数分为:有参构造和无参构造(默认构造)
按类型分类:普通构造和拷贝构造
(2)三种调用方式
括号法(默认构造函数调用)、显示法、隐式转换法
class Person
{
public:
Person()
{
cout << "无参构造函数" << endl;
}
Person(int a)
{
age = a;
cout << "有参构造函数" << endl;
}
Person(const Person& p)
{
cout << "拷贝构造函数" << endl;
}
~Person()
{
cout << "析构函数" << endl;
}
public:
int age;
};
void test01()
{
Person p;//注意调用无参构造函数时不能加括号,如果加了编译器认为这是一个函数声明
Person p1(10);//括号法
Person p2 = Person(10);//显式法
Person p3 = Person(p2);
Person p4 = 10;//隐式转换法:Person p4 = Person(10);
Person p5 = p4;
}
3.拷贝构造函数调用时机
(1)使用一个已经创建完毕的对象来初始化一个新对象
(2)值传递的方式给函数参数传值
(3)以值方式返回局部对象
class Person
{
public:
Person()
{
cout << "无参构造函数" << endl;
}
Person(int a)
{
age = a;
cout << "有参构造函数" << endl;
}
Person(const Person& p)
{
age = p.age;
cout << "拷贝构造函数" << endl;
}
~Person()
{
cout << "析构函数" << endl;
}
public:
int age;
};
//(1)使用一个已经创建完毕的对象来初始化一个新对象
void test01()
{
Person p1(20);
Person p2(p1);
cout << "p2的年龄为:" << p2.age << endl;
}
//(2)值传递的方式给函数参数传值
void dowork(Person p)
{
}
void test02()
{
Person p;
dowork(p);
}
//(3)以值方式返回局部对象
Person dowork1()
{
Person p;
return p;
}
void test03()
{
Person p = dowork1();
}
4.构造函数调用规则
默认情况下c++编译器至少给一个类添加3个函数
(1)默认构造函数(无参,函数体为空)
(2)默认析构函数(无参,函数体为空)
(3)默认拷贝构造函数,对属性进行值拷贝
构造函数调用规则如下:
如果用户定义有参构造函数,c++不再提供无参构造,但是会提供默认拷贝构造
如果用户定义拷贝构造函数,c++不会再提供其他构造函数
5.深拷贝与浅拷贝
(1)浅拷贝:简单的复制拷贝操作
(2)深拷贝:在堆区重新申请空间,进行拷贝操作
6.初始化列表
(1)作用:c++提供了初始化列表语法,用来初始化属性
(2)语法:构造函数():属性1(值1),属性2(值2)...{ }
Person(int a,int b,int c) :m_A(a), m_B(b), m_C(c)
{
}
7.类对象作为类成员
c++类中的成员可以是另一个类的对象,我们称该成员为对象成员。
当其它类对象作为本类成员,构造时先构造类对象,再构造自身,析构的顺序相反。
class A
{
}
class B
{
A a;
}
8.静态成员
静态成员就是在成员变量和成员函数前加上关键字static,称为静态成员,分为:
(1)静态成员变量:
所有对象共享同一份数据、在编译阶段分配内存、类内声明,类外初始化
class Person
{
public:
static int m_A;
};
int Person::m_A = 100;
void test1()
{
Person p;
cout << p.m_A << endl;
Person p2;
p2.m_A = 200;
cout << p.m_A << endl;//共享
}
void test2()
{
//静态成员变量不属于某个对象上,所有对象共享同一份数据
//因此静态成员变量有两种访问方式
//1.通过对象进行访问
Person p;
cout << p.m_A << endl;
//2.通过类名进行访问
cout << Person::m_A << endl;
}
int main()
{
test2();
system("pause");
return 0;
}
//注:静态成员变量也有访问权限:private类外无法访问
(2)静态成员函数:
所有对象共享同一个函数、静态成员函数只能访问静态成员变量
class Person
{
public:
static void func()
{
m_A = 100;
cout << "静态成员函数的调用" << endl;
}
static int m_A;
int m_B;
};
int Person::m_A=0;
void test1()
{
//1.通过对象进行访问
Person p;
p.func();
//2.通过类名进行访问
Person::func();
}
int main()
{
test1();
system("pause");
return 0;
}
三、C++对象模型和this指针
1.成员变量和成员函数分开存储
在c++中,类内的成员变量和成员函数分开存储,只有非静态成员变量才属于类的对象上(可以通过sizeof验证)(空对象占用内存:1)
2.this指针概念
(1)每一个非静态成员函数只会诞生一份函数实例,也就是说多个同类型对象会共用一块代码,那么问题是:这一块代码是如何分哪个对象调用自己的?
(2)c++通过提供特殊的对象指针,this指针,解决上述问题,this指针指向被调用的成员函数所属的对象
(3)this指针是隐含每一个非静态成员函数内的一种指针
(4)this指针不要定义,直接使用即可
(5)this指针的用途:
当形参和成员变量同名时,可以用this指针区分
在类的非静态成员函数中返回对象本身,可使用return*this
class Person
{
public:
Person (int age)
{
this->age = age;//this指针指向被调用的成员函数所属的对象:p
}
Person& PersonAddAge(Person &p)
{
this->age += p.age;
//this指向p2的指针,而*this指向的就是p2这个对象本体
return *this;
}
int age;
};
void test1()
{
Person p(18);
cout << "p年龄为:" << p.age << endl;
}
void test2()
{
Person p1(18);
Person p2(18);
//链式编程思想
p2.PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1);
cout << "p2年龄为:" << p2.age << endl;
}
int main()
{
test2();
system("pause");
return 0;
}
3.空指针访问成员函数
(1)c++中空指针是可以调用成员函数的,但是也要注意有没有用到this指针
(2)如果用到this指针,需要加以判断保证代码的健壮性
4.const修饰成员函数
(1)常函数:
成员函数后加const后我们称这个函数为常函数(如:void show() const )
常函数内不可以修改成员属性(修饰的是this指向,让指针指向的值也不可以修改)
特例:成员属性声明时加关键字mutable后,在常函数中依然可以修改
(2)常对象:
声明对象前加const称该对象为常对象(如:const Person p;)
常对象只能调用常函数
四、友元
1.基本概念
(1)目的:让一个函数或者类访问另一个类中私有成员
(2)关键字:friend
(3)三种实现:全局函数做友元、类做友元、成员函数做友元
2.全局函数做友元
#include<iostream>
using namespace std;
#include<string.h>
class Building
{
//test1全局函数是Building好朋友,可以访问Building中私有成员
friend void test1(Building* building);
public:
Building()
{
m_SittingRoom = "客厅";
m_Bedroom = "卧室";
}
public:
string m_SittingRoom;//客厅
private:
string m_Bedroom;
};
//全局函数
void test1(Building *building)
{
cout << "全局函数正在访问:" <<building->m_Bedroom << endl;
}
int main()
{
Building building;
test1(&building);
system("pause");
return 0;
}
3.类做友元
#include<iostream>
using namespace std;
#include<string.h>
class Building;
class Friend
{
public:
Friend();
void visit();//参观函数 访问Building中属性
Building *building;
};
class Building
{
//Friend类是本类的好朋友,可以访问本类中私有成员
friend class Friend;
public:
Building();
public:
string m_SittingRoom;//客厅
private:
string m_Bedroom;
};
//类外写成员函数
Building::Building()
{
m_SittingRoom = "客厅";
m_Bedroom = "卧室";
}
Friend::Friend()
{
//创建建筑物对象
building = new Building;//在堆区创建一个对象
}
void Friend::visit()
{
cout<< "Friend类正在访问:" << building->m_SittingRoom << endl;
cout<< "Friend类正在访问:" << building->m_Bedroom << endl;
}
void test1()
{
Friend f;
f.visit();
}
int main()
{
Building building;
test1();
system("pause");
return 0;
}
4.成员函数做友元
#include<iostream>
using namespace std;
#include<string.h>
class Building;
class Friend
{
public:
Friend();
void visit1();//参观函数 访问Building中私有成员
void visit2();//参观函数 不可以访问Building中私有成员
Building* building;
};
class Building
{
//Friend类下的visit1成员函数是本类的好朋友,可以访问本类中私有成员
friend void Friend::visit1();
public:
Building();
public:
string m_SittingRoom;//客厅
private:
string m_Bedroom;
};
//类外写成员函数
Building::Building()
{
m_SittingRoom = "客厅";
m_Bedroom = "卧室";
}
Friend::Friend()
{
//创建建筑物对象
building = new Building;//在堆区创建一个对象
}
void Friend::visit1()
{
cout << "Friend类正在访问:" << building->m_SittingRoom << endl;
cout << "Friend类正在访问:" << building->m_Bedroom << endl;
}
void Friend::visit2()
{
cout << "Friend类正在访问:" << building->m_SittingRoom << endl;
//cout << "Friend类正在访问:" << building->m_Bedroom << endl;
}
void test1()
{
Friend f;
f.visit1();
f.visit2();
}
int main()
{
Building building;
test1();
system("pause");
return 0;
}
五、运算符重载
1.基本概念
运算符重载概念:对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型。
2.加号运算符重载
作用:实现两个自定义数据类型相加的运算
#include<iostream>
using namespace std;
//通过自己写成员函数,实现两个对象相加属性后返回新的对象
class Person
{
public:
int m_a;
int m_b;
public:
Person() {};
Person(int a, int b)
{
this->m_a = a;
this->m_b = b;
}
Person operator+ (const Person& p);
};
//成员函数
Person Person::operator+(const Person& p)
{
Person temp;
temp.m_a = this->m_a + p.m_a;
temp.m_b = this->m_b + p.m_b;
return temp;
}
//全局函数
Person operator+(const Person &p1, const Person& p2)
{
Person temp;
temp.m_a = p1.m_a + p2.m_a;
temp.m_b = p1.m_b + p2.m_b;
return temp;
}
void test1()
{
Person p1(10,10);
Person p2(20,20);
Person p3 = p1 + p2;//Person p3 = p1.operator+(p2);
Person p4 = p1 + p2;//Person p4 =operator+(p1,p2);
cout << p3.m_a << endl;
cout << p3.m_b << endl;
cout << p4.m_a << endl;
cout << p4.m_b << endl;
}
int main()
{
test1();
system("pause");
return 0;
}
3.左移运算符重载
作用:可以输出自定义数据类型
#include<iostream>
using namespace std;
class Person
{
public:
//不会利用成员函数重载左移运算符,因为无法实现cout在左侧
int m_a;
int m_b;
};
ostream &operator<<(ostream &cout, Person &p)//cout<<p
{
cout << "m_a=" << p.m_a << " m_b=" << p.m_b;
return cout;//cout可 out也可
}
void test1()
{
Person p;
p.m_a = 10;
p.m_b = 10;
cout << p << endl;
}
int main()
{
test1();
system("pause");
return 0;
}
4.递增运算符重载
作用:通过重载递增运算符,实现自己的整型数据(前置递增返回引用,后置递增返回值)
5.赋值运算符重载
c++编译器至少给一个类添加四个函数
(1)默认构造函数(无参,函数体为空)
(2)默认析构函数(无参,函数体为空)
(3)默认拷贝构造函数,对属性进行值拷贝
(4)赋值运算符operator=,对属性进行值拷贝
如果类中有属性指向堆区,做赋值操作时也会出现深浅拷贝问题
6.关系运算符重载
作用:重载关系运算符,可以让两个自定义类型对象进行对比操作
7.函数调用运算符重载
函数调用运算符()也可以重载
由于重载后使用的方式非常像函数的调用,因此称为仿函数
仿函数没有固定写法,非常灵活