——我的梦想是面向对象编程
c++面向对象三大性质:封装,继承,多态
类和结构体体现了封装的思想
c++认为万物都为对象,其有自己的属性和行为
封装
为什么要封装
将属性和行为化为一个整体,并且可以加以控制
怎么封装
语法:class 类名 {访问权限: 属性/行为};
如 创建一个圆类
class circle
{
//访问权限
//公共权限
public:
//属性
int r; //圆的半径
//行为
//获取圆的周长
double calculateZC()
{
return 2 * PI /*圆周率*/* r;
}
};
只有类还不行,就跟只有人类没有具体的人一样,我们需要通过实例化操作创建对象,就好比在人类的大类下,创建“张三”这个人。
语法:(class) 类名 对象名;
在c++中,诸如class
struct
这类,在定义对象时可以省略。
通过实例化定义一个对象
circle c1;
我们可以这样访问类中的成员(属性,行为)等等
c1.r = 10;
cout << c1.calculateZC(); // 输出圆的面积
访问权限
在类中,我们可以设置成员的访问权限,是这个成员是否可以在类外被访问。
三种权限
public: 在类外类内都可以访问
protected: 在类外不可以访问,在类内可以访问
private: 在类外不可以访问,在类内可以访问
为什么protected 和 private 是一样的呢
他们的不同在类继承那里才能体现。
子类可以访问父类的protected成员但不能访问父类的private成员。 这里不细说
例:
class student
{
public: // 这些能从类外访问
void scanmessage()
{
cin >> number;
cin >> name;
}
void printmessage()
{
cout << number <<" "<< name << endl;
}
private: //这些不能从类外访问
int number;
char name[233];
};
什么是从类外访问呢
int main()
{
student a;
cout << a.number;
//number 的属性是private 不能从外部访问
//但是类中的printmessage函数可以访问number 这就是从类内访问。
}
class 和 struct 的区别
class
的默认访问权限是private
struct
的默认访问权限是public
将成员的属性设为私有
优点:
可以自己控制读写的权限
对于写可以检测数据的有效性
我们如果想访问一个成员可以在public中写访问他的接口,这样我们就可以控制一个成员是否可以被访问
class person
{
public: //我们虽然将一些属性设置为私有,但是我们可以在public中提供接口
//对这些属性进行操作
//设置姓名
void setname(string Name)
{
name = Name;
}
//获取姓名
string getname()
{
return name;
}
private:
string name;
这样我们不可以直接访问name ,但是我么可以调用setname getname这两个接口函数对其实现修改和查看。
构造函数和析构函数
创建对象时由系统自动调用,并且只调用一次
构造函数
特点:
没有返回值 不写void
函数名与类的名称相同
可以有参数 可以发生重载
创建对象时 构造函数自动调用,只调用一次
权限必须是public
class Person
{
public:
Person()
{
cout << "这是一个构造函数";
}
};
当我们创建一个对象时,Person()会被自动调用。
分类方式
按照参数分类
有参构造
构造函数带有参数的是有参构造
class Person
{
public:
Person(int a)
{
age = a;
cout << "Person的构造函数的调用(有参)\n";
}
};
调用时
Person a(3); //只需要后面带括号将参数传进即可
无参构造
就是最普通的构造
class Person
{
public:
Person()
{
cout << "Person的构造函数的调用(无参)\n";
}
};
但是调用时不能加括号
Person a;
如果加括号 如Person a();
编译器会认为是函数的声明
按照类型分类
拷贝构造
class Person
{
public:
Person(const Person& p)
{
age = p.age;
}
};
private:
int age;
拷贝构造就是将同类对象的属性拷贝到自己身上的构造
那为什么是 const Person& p
而不是 Person& p
或者是 const Person p
呢
无非就是const 和 引用两个问题。
先说 引用 如果不传引用的话 拷贝构造函数的形参是Person类型 编译器又会为p创建对象 又会调用拷贝构造函数 使得程序无线递归
如果是引用类型 传入的时候系统不会重新创建对象。
至于const 加不加都行 只是为了防止意外修改。
不加const的话 如果你有参构造函数只有一个参数的话 显式调用会出问题(玄学错误 我也不知道咋回事)
什么时候会用到拷贝构造
1 使用一个已经创建完毕的对象初始化一个新对象
就是最经典的调用
class Person
{
public:
Person(const Person& p)
{
cout << "Person的拷贝构造函数\n";
m_age = p.m_age;
}
int m_age;
};
void test01()
{
Person p1(20);
Person p2(p1);
cout << "p2的年龄为" << p2.m_age <<endl;
}
2 值传递的方式给函数参数传值
class Person
{
public:
Person(const Person& p)
{
cout << "Person的拷贝构造函数\n";
m_age = p.m_age;
}
int m_age;
};
void dowork(Person p)
{
//函数传值其实是Person p = p 拷贝构造函数隐式写法
}
void test02()
{
Person p;
dowork(p);
}
3 以值方式返回局部对象
Person dowork2()
{
Person p1;
return p1; //返回的是p1的值 编译器用p1的值创建了一个对象
}
class Person
{
public:
Person(const Person& p)
{
cout << "Person的拷贝构造函数\n";
m_age = p.m_age;
}
int m_age;
};
Person dowork2()
{
Person p1;
return p1; //返回的是p1的值 编译器用p1的值创建了一个对象
}
void test03()
{
Person p = dowork2(); //这里还是隐式写法 Person p = p1;
}
调用方式
普通调用(括号法)
Person p; //无参调用
Person p1(10); // 有参调用
Person p2(p1); // 拷贝调用
显式调用
我们首先要知道什么是匿名对象
Person(10)
Person(p1)
没有名字 但是实实在在是一个对象 这一行代码结束立马释放。
显式调用 就是把这个匿名对象加上一个名字
Person p = Person(10);
Person p1 = Person(p);
这个时候调用p.age 输出10
隐式调用
接上面显示调用 隐式调用换了种写法
Person p = 10;
person p1 = p;
总的来说还是括号构造清晰明了 但是其他两种还是要明白的
析构函数
在对象即将销毁前自动调用的函数
局部变量会在函数即将结束前销毁
全局变量会在程序即将结束前销毁
特点:
没有返回值 不写void
函数名和类名相同 名称前面加上~
没有参数 不可以重载
对象在销毁前 会自动调用析构函数 并且只调用一次
例:
class Person
{
public:
~Person()
{
cout << "这是析构函数的调用";
}
};
void ff()
{
Person a;
cout << "233";
}
你会发现 这是析构函数的调用
在 233
后输出 因为a在即将被释放时才调用析构函数
注意事项
构造函数 和 析构函数是系统强制写的 如果不写 系统会帮你写 但是里面是空实现。
系统同时也会写拷贝构造函数,对属性的值进行拷贝
默认规则
如果定义了有参构造 系统不再提供无参构造 但还提供拷贝构造
如果定义了拷贝构造 系统不再提供其他构造
class peo
{
public:
peo(int x)
{
age = x;
}
int getage()
{
return age;
}
private:
int age;
};
int main()
{
peo p1(10);
peo p2(p1);
std::cout << p2.getage();
}
在这段代码中 我们并没有为peo类写拷贝构造 但是我们调用了peo p2(p1)
这种拷贝构造函数 并且输出p2.getage()
是10
,说明系统自动帮我们完成了拷贝
我们在写构造函数的时候一定要注意
如果我们只提供有参构造 我们只能有参调用 因为系统不再提供无参构造
同样我们只提供拷贝构造 我们只能拷贝调用 因为系统不再提供其他构造
深拷贝和浅拷贝
我们先来看这样一个例子
class peo
{
public:
peo(int age)
{
m_age = age;
}
peo(const peo& p)
{
m_hei = p.m_hei;
age = p.age;
}
~peo()
{
delete m_hei;
m_hei = NULL;
}
int *m_hei , age;
};
int main()
{
peo p1(14);
peo p2(p1);
}
运行这段代码会怎么样呢 毫无悬念地崩溃了了 因为运行时编译器将p2 一模一样地拷贝给了p1包括m_hei
的指向
p1.m_hei
和 p2.m_hei
指向一模一样的地方 调用p1的析构函数时 这块空间已经被释放了 调用p2的析构函数这个空间又被释放了一次 造成程序挂掉。
我们只需要将m_hei = p.m_hei
改成m_hei = new int(*p.m_hei)
即可 每一次拷贝重新申请一个空间 这个就是深拷贝
浅拷贝(shallowCopy)只是增加了一个指针指向已存在的内存地址
深拷贝(deepCopy)是增加了一个指针并且申请了一个新的内存,使这个增加的指针指向这个新的内存
初始化列表
我们一般这样初始化类成员
class person
{
public:
//我们经常用的初始化方法
person(int a , int b , int c)
{
m_a = a;
m_b = b;
m_c = c;
}
};
还可以这样
格式 类名 (定义变量,): 成员变量名1(赋值), 成员变量名2(赋值)……
注意冒号,最后不加分号
class person
{
public:
person(int a , int b , int c) : m_a(a) , m_b(b) , m_c(c)
{
}
int m_a , m_b , m_c;
};
类组合(类对象作为其他类的成员)
毋庸置疑 手机是一个类,他有品牌等属性
所以我们可以定义一个手机类
class Phone
{
public:
string m_phone;
Phone()
{
cout << "手机类普通构造\n";
}
Phone(string name)
{
cout << "手机类有参构造\n";
m_phone = name;
}
~Phone()
{
cout << "手机的析构\n";
}
};
我们人是一类 我们每一个人都有手机
所以我们可以定义人类 手机类对象包含其中
class Person
{
public:
Person(string name1 , string name2) : m_name(name1) , p_name(name2)
{
cout << "人的构造\n";
}
Phone p_name;
string m_name;
~Person()
{
cout << "Person析构\n";
}
};
很多人一看这段代码其实就懵了,在Person类里创建Phone类p_name
对象时 并没有写任何参数
但是代码运行出来时 程序调用的是Phone类的有参构造,
其实调用的是Person的构造函数 然后利用隐式构造将name2 拷贝给了 p_name
相当于 p_name = name2(隐式写法)
这里奉上我个人的理解
如果Person的构造函数里对p_name进行了初始化 则这里不对其进行初始化
如果Person的构造函数里没对p_name进行初始化 则这里直接调用p_name的构造函数对其初始化
类组合的构造顺序
当其他类对象作为本类的成员 构造时先构造其他类的对象 再构造本类
析构 先析构本类 再析构其他类对象
析构 和 构造的顺序相反
静态成员
静态成员变量
特点:
1 类内声明 类外初始化 (必须有 否则无法访问)
2 所有对象共享同一份数据
3 编译阶段分配内存
首先说特点1
class person
{
public:
static int m_a;
private:
static int m_b;
};
//我们必须在类外初始化 指的是代码层面下类外
int person :: m_a = 100;
person ::
代表person下的成员
特点2 接上面代码
person a;
person b;
a.m_a = 200;
cout << b.m_a;
输出200 为什么不是100 因为m_a 是静态成员 对象a和b公用一个
静态成员变量的访问
两种方式 不过依然注意静态成员变量依然有访问权限问题 见上面变量m_b
通过对象访问
就是平常的访问
cout << a.m_a;
通过类名访问
因为m_a 只有一个 并不是只属于某个对象,所以我们可以通过类名访问
cout << person::m_a;
静态成员函数
特点
1 所有对象共享一个函数
2 只能访问静态成员变量
class Person
{
public:
static void func()
{
std :: cout << "static void func调用\n";
m_a = 3;
//m_b = 200; 静态函数无法访问非静态成员变量 无法区分是哪个对象的m_b
}
static int m_a;
int m_b;
private:
static void func2() //静态变量也有访问权限
{
std::cout << "233\n";
}
};
静态成员函数只能访问静态成员变量,也就是说func1()
只能访问m_a
不能访问m_b
因为m_a
不属于单独一个对象,而每个成员的m_b
都不一样 无法区分是哪个对象的m_b
静态成员函数的访问
对于特点1 使静态成员函数有两种访问方式
也要注意权限问题 见func2()
通过对象访问
person a;
a.func1();
通过类名访问
和静态成员变量相似
person :: func1();
this 指针
为了区分是谁调用的成员函数从而派生的指针
和 py 的self相似
看这个程序
你认为编译器能分清哪个是哪个age吗
答案是不能的
class person
{
person(int age)
{
age = age
}
int age;
};
所以就有了this指针
class person
{
public:
person(int age)
{
this -> age = age
}
int age;
};
perosn p(12);
对象p 调用了 构造函数 它的age被赋值为12
因为有了this指针 带有this指针的age被编译器认为是成员 不带this指针的被认为成传入的形参
this还可以返回对象本身
因为this是指向对象的指针
我们可以通过return *this
返回对象本身
class Person
{
public:
Person& personaddage(Person& p)
{
this -> age += p.age;
return *this;
}
int age;
};
int main()
{
Person p1(10);
Person p2(10);
p2.personaddage(p1).personaddage(p1);
}
p2调用函数后返回加了年龄的p2(引用)然后再调用 最后p2的年龄是40
这也是链式编程思想
this指针的本质
类名 *const this
这是一个指针常量 指向不可以修改 谁调用的成员函数 this指向谁
常函数 常对象
我们在类里定义函数时可以在函数后加const使其变成常函数 常函数不能修改动态成员
class person
{
void show() const
{
m_a = 3; //报错
}
int m_a;
};
这里涉及到this指针的问题
我们在访问成员的时候是通过this指针访问的
class person
{
void func()
{
f = 3;
//其实是 this -> f = 3;
}
int f;
};
this 指针是一个常量指针 指向不可变
person const* this
如果我们在函数后加了const
this 指针就成了这样
const person const* this 这样指向不能变
我们也不能通过这个指针修改这个地址的值
但 凡事总有例外
我们定义时可以在前面加个mutable关键字 就可以通过常函数修改动态成员了
class person
{
void show() const
{
m_a = 3; //报错
m_b = 4; //没问题
}
int m_a;
mutable int m_b;
};
int main()
{
const person p;
}
在对象前加const 变成常对象
常对象只能调用常函数 因为常函数不能修改属性 普通成员函数可以修改属性
但是我们依然可以通过常函数修改带有mutable 关键字的动态成员。
友元
有时我们确实想让外部访问类的私有成员 友元就排上用场了
全局函数做友元
我们现在有一个类
class Building
{
public:
Building()
{
m_sittingroom = "客厅";
m_bedroom = "卧室";
}
string m_sittingroom; //客厅
private:
string m_bedroom; //卧室
};
这个类有两个成员 一个是公共属性的客厅 另一个是私有属性的卧室
我们现在又有一个全局函数
void goodgay(Building* building)
{
cout << "好基友的全局函数正在访问 " << building->m_sittingroom <<endl;
cout << "好基友的全局函数正在访问 " << building->m_bedroom << endl;
}
这个函数里,第一句话没有问题 但是第二句话就出事了 因为卧室 是私有的
那咋办呢
这个时候,只要在类里面加上这样一句话
friend void goodgay(Building* building)
像这样:
class Building
{
friend void goodgay(Building* building);
public:
Building()
{
m_sittingroom = "客厅";
m_bedroom = "卧室";
}
string m_sittingroom; //客厅
private:
string m_bedroom; //卧室
};
这个时候/goodgay全局函数是Building类的友元 可以通过这个访问私有权限
类做友元
还是这个类
class Building
{
public:
Building();
std::string m_sittingroom; //客厅
private:
std::string m_bedroom; //卧室
};
Building::Building()
{
m_sittingroom = "客厅";
m_bedroom = "卧室";
}
这里提一嘴这种写法 成员函数并不一定要写在类内 写在类外用类名::
表明作用域是等价的写法
好嘞 回归正题
我们现在有一个类GoodGay
class GoodGay
{
public:
GoodGay();
void visit(); //参观函数访问building的属性
Building* building;
};
GoodGay::GoodGay()
{
//创建一个建筑物的对象
building = new Building;
}
void GoodGay::visit()
{
cout << "好基友类正在访问 " << building->m_sittingroom <<endl;
cout << "好基友类正在访问 " << building->m_bedroom << endl;
}
我们会发现 visit 函数里第二局还是不对
这时候我们照葫芦画瓢 在Building类里声明friend class GoodGay;
即可
类成员函数做友元
假设GoodGay类里有两个函数visit1
和visit2
两个成员函数 我只想让visit1
访问Building类的私有成员 这时候就不能让GoodGay做友元了 只能让他的成员函数visit1做友元
声明方式
friend void GoodGay::visit1(Building* building);