文章目录
- 类和对象
- 封装
- 封装的意义
- struct和class的区别
- 成员属性设置为私有
- 封装练习
- 设计立方体类
- 点和圆的关系
- 对象的初始化和清理
- 构造函数和析构函数
- 构造函数
- 析构函数
- 构造函数的分类及调用
- 拷贝构造函数的调用时机
- 构造函数调用规则
- 深拷贝与浅拷贝
- 浅拷贝
- 深拷贝
- 初始化列表
- 类对象作为类的成员
- 静态成员
- 静态成员变量
- 静态成员函数
- C++ 对象模型和this指针
- 成员变量和成员函数分开存储
- this指针
- `this` 指针具有以下特点:
- this指针用途
- 空指针访问成员函数
- const修饰成员函数
- 常函数
- 常对象
- 友元
- 使用全局函数做友元
- 类做友元
- 成员函数做友元
20240214
类和对象
-
C++面向对象三大特性:
封装、继承、多台
-
c++中万事皆对象
封装
封装的意义
- 将对象
#include <iostream>
using namespace std;
const double PI = 3.14;
//定义一个圆类
class Circle{
// 访问权限
public:
// 属性
// 半径
int m_r;
// 行为
// 获取圆的周长
double getPerimeter() {
return 2*PI*m_r;
}
};
int main(){
// 使用类初始化一个对象
Circle c;
// 对象属性赋值
c.m_r = 2;
// 调用对象方法获取圆周长
cout << "圆的周长:" << c.getPerimeter() << endl;
return 0;
}
-
控制访问权限
-
public 公共权限,成员在类内可以访问,类外也可以访问
-
protect 保护权限,成员在类内可以访问,类外不可以访问
-
private 私有权限,成员在类内可以访问,类外不可以访问
-
#include <iostream>
#include <string>
using namespace std;
class Person{
public:
string name;
protected:
int age;
private:
string phone;
public:
// 类内访问
void func(){
name="zs";
age = 12;
phone="12345";
}
};
int main(){
Person p;
// 类外访问
p.name = "ls";
// error: 'age' is a protected member of 'Person'
// p.age = 18;
// error: 'phone' is a private member of 'Person'
//p.phone = 20;
return 0;
}
struct和class的区别
-
唯一区别就是默认的访问权限不同
-
struct默认的访问全向为public权限
-
classs默认的访问全向为private权限
struct P1{
int age; // 默认为public权限
};
class P2 {
int age; // 默认为private权限
};
int main(){
// 实例化struct类对象
P1 p1;
// 实例化class类对象
P2 p2;
p1.age = 1;
// error: 'age' is a private member of 'P2'
// p2.age = 2;
return 0;
}
成员属性设置为私有
-
优点:
-
可以自己控制读写权限
-
对于写权限可以判断数据的有效性
-
#include <iostream>
#include <string>
using namespace std;
class Person{
public:
void setName(string name){
m_name=name;
}
string getName(){
return m_name;
}
void setAge(int age){
// 判断数据有效性
if (age > 100 || age < 0) {
return;
}
m_age = age;
}
int getAge(){
return m_age;
}
private:
string m_name; // 可读可写
int m_age; // 可读
string m_password; // 不可读不可写
};
int main(){
Person p1;
p1.setName("zs");
p1.getName();
p1.setAge(18);
p1.getAge();
return 0;
}
封装练习
设计立方体类
- 求出立方体的面积和体积
#include <iostream>
using namespace std;
class Cube {
public:
void setEdge(int edge){
m_edge = edge;
}
// 计算面积
int calcArea() {
return 6 * m_edge * m_edge;
}
// 计算体积
int calcVolume() {
return m_edge*m_edge*m_edge;
}
// 判断两个立方体是否相同
bool isSameCube(int edge) {
return m_edge=edge;
}
private:
int m_edge;
};
int main(){
Cube c1;
c1.setEdge(2);
cout << "c1立方体的面积是:" << c1.calcArea() << endl;
cout << "c1立方体的体积是:" << c1.calcVolume() << endl;
return 0;
}
点和圆的关系
-
设计一个圆类和点类,判断点和圆的关系
-
圆头文件编写
circle.hpp
#pragma once
#include "point.hpp"
class Circle{
public:
void setRadio(int r, int x, int y);
int getR();
Point getPoint();
private:
int m_r; // 半径
Point p; // 圆心
};
- 点头文件编写
point.hpp
#pragma once
class Point {
public:
void setX(int x);
void setY(int y);
int getX();
int getY();
private:
int m_x; // 横坐标
int m_y; // 纵坐标
};
- 源文件编写
point_and_circle.cpp
#include <iostream>
#include <cmath>
#include "circle.hpp"
#include "point.hpp"
using namespace std;
void Point::setX(int x) {
m_x = x;
}
void Point::setY(int y) {
m_y = y;
}
int Point::getX() {
return m_x;
}
int Point::getY() {
return m_y;
}
void Circle::setRadio(int r, int x, int y) {
m_r = r;
p.setX(x);
p.setY(y);
}
int Circle::getR() {
return m_r;
}
Point Circle::getPoint() {
return p;
}
void judgePointAndCircleRelation(Circle& c, Point& p) {
// 计算点到圆心的距离
int d = sqrt((p.getX()-c.getPoint().getX())^2+(p.getY()-c.getPoint().getY())^2);
int r = c.getR();
if (d == r){
cout << "点在圆上" << endl;
} else if (d > r) {
cout << "点在圆外" << endl;
} else {
cout << "点在圆外" << endl;
}
}
int main() {
Circle c;
Point p;
c.setRadio(2, 0 , 0);
p.setX(10);
p.setY(2);
judgePointAndCircleRelation(c, p);
return 0;
}
对象的初始化和清理
构造函数和析构函数
-
由编译器自动调用
-
如果不写构造函数和析构函数,编译器会自己提供空实现的构造函数和析构函数
构造函数
-
主要用于创建对象时,为对象成 员赋值
-
语法:类名() {}
-
构造函数没有返回值,也不用写void
-
函数名称和类名相同
-
构造函数可以有参数,因此可以发生重载
-
程序在调用对象时会自动调用构造函数,无需手动调用且只会调用一次
-
析构函数
-
主要用于对象销毁时执行一些清理工作
-
语法:~类名() {}
-
析构函数没有返回值,也不用写void
-
函数名称和类名相同,在名称前加上~
-
构造函数不可以有参数,因此也不能发生重载
-
程序在调用对象时会自动调用构造函数,无需手动调用且只会调用一次
-
#include <iostream>
using namespace std;
class Person {
public:
Person() {
cout << "construct" << endl;
}
~Person() {
cout << "deconstruct" << endl;
}
};
int main(){
Person p;
return 0;
}
构造函数的分类及调用
-
按参数分类:有参构造、无参构造
-
按类型分为:普通构造、拷贝构造
-
按调用方式
-
括号法
-
显示法
-
隐式转换法
-
#include <iostream>
using namespace std;
class Person{
public:
Person(){
cout << "无参构造" << endl;
}
Person(int age){
m_age=age;
cout << "有参构造" << endl;
}
Person(const Person& p){
m_age=p.m_age;
cout << "拷贝构造" << endl;
}
~Person(){
cout << "析构函数" << endl;
}
private:
int m_age;
};
void test() {
cout << "===括号法调用===" << endl;
// 无参构造调用
Person p;
// 调用默认构造函数时不要加()
// Person px() 编译器会将这行代码当成一个函数声明
// Person px();
// 有参构造调用
Person p1(3);
// 拷贝构
Person p2(p1);
cout << "===显示法调用===" << endl;
Person pp1 = Person(1);
Person pp2 = Person(pp1);
// Person(10) 匿名对象,当前行执行完系统会立即回收掉匿名对象
Person(10);
// 不要利用拷贝构造函数初始化匿名对象,编译器会认为Person(p) ==> Person p(p),造成p变量重定义
cout << "===隐式转换法===" << endl;
Person p4 =10; // 等价与Person p4=Person(10);
Person p5 = p4;
}
int main() {
test();
return 0;
}
拷贝构造函数的调用时机
-
使用一个已经创建完毕的对象来初始化另一个对象
-
值传递的方式给函数参数传值
-
以值方式返回局部对象
#include <iostream>
using namespace std;
class Person{
public:
Person(){
cout << "无参构造" << endl;
}
Person(int age){
m_age=age;
cout << "有参构造" << endl;
}
Person(const Person& p){
m_age=p.m_age;
cout << "拷贝构造" << endl;
}
~Person(){
cout << "析构函数" << endl;
}
private:
int m_age;
};
// 使用一个已经创建的对象来初始化另一个新对象
void test01() {
Person p(10);
Person p1(p);
}
// 值传递方式给函数参数传值
void func(Person p) {
}
void test02() {
Person p(10);
func(p);
}
// 以值方式返回局部对象
Person doWork() {
Person p(1);
return p;
}
void test03() {
Person p =doWork();
}
int main(){
test01();
test02();
test03();
return 0;
}
构造函数调用规则
-
默认情况下,c++编译器至少会给一个类添加3个函数
-
默认构造函数(无参函数体为空)
-
默认析构函数(无参函数体为空)
-
默认拷贝构造函数,对属性进行值拷贝
-
-
构造函数调用规则
- 如果自定义了有参构造函数,编译器不会在提供默认无参构造函数
#include <iostream> using namespace std; class Person{ public: Person(int age){ m_age=age; cout << "有参构造函数" << endl; } ~Person(){ cout << "默认析构函数" << endl; } int m_age; }; void test01(){ Person p; //no matching constructor for initialization of 'Person' } int main(){ test01(); return 0; }
- 但是会提供默认拷贝构造
#include <iostream> using namespace std; class Person{ public: Person(){ cout << "默认构造函数" << endl; } Person(int age){ m_age=age; cout << "有参构造函数" << endl; } ~Person(){ cout << "默认析构函数" << endl; } int m_age; }; void test01(){ Person p; p.m_age=18; Person p1(p); //调用默认拷贝构造 cout << p1.m_age << endl; } int main(){ test01(); return 0; }
- 如果自定义了拷贝构造函数,编译器不会在提供其它构造函数
#include <iostream>
using namespace std;
class Person{
public:
Person(const Person& p){
m_age=p.m_age;
cout << "拷贝构造函数" << endl;
}
~Person(){
cout << "默认析构函数" << endl;
}
int m_age;
};
void test01(){
//no matching constructor for initialization of 'Person'
Person p;
Person p1(18);
}
int main(){
test01();
return 0;
}
深拷贝与浅拷贝
- 如果类中有属性需要在对上开辟空间,一定要自己实现拷贝构造函数
浅拷贝
-
简单的赋值拷贝操作
-
浅拷贝存在问题:堆区的内存重复释放
#include <iostream>
using namespace std;
class Person{
public:
Person(){
cout << "默认构造" << endl;
}
Person(int age, int height){
m_age = age;
// 使用new关键字在堆上开辟
m_height=new int(height);
cout << "赋值拷贝构造" << endl;
}
~Person(){
if (m_height != NULL) {
delete m_height;
}
cout << "析构函数" << endl;
}
int m_age;
int *m_height;
};
void test(){
Person p(18, 170);
// 调用默认拷贝构造函数,使用的是浅拷贝的方式,析构释放堆上的内存会出错
Person p1(p);
}
int main(){
test();
return 0;
}
深拷贝
-
在堆区重新申请空间,进行拷贝操作
-
解决深拷贝,重复释放对上内存导致的panic
#include <iostream>
using namespace std;
class Person{
public:
Person(){
cout << "默认构造" << endl;
}
Person(int age, int height){
m_age = age;
// 使用new关键字在堆上开辟
m_height=new int(height);
cout << "赋值拷贝构造" << endl;
}
Person (const Person& p) {
m_age=p.m_age;
m_height = new int(*p.m_height);
}
~Person(){
if (m_height != NULL) {
delete m_height;
}
cout << "析构函数" << endl;
}
int m_age;
int *m_height;
};
void test1() {
Person p(18, 170);
// 调用自定义实现的深拷贝构造函数
Person p1(p);
}
int main(){
test1();
return 0;
}
初始化列表
-
C++提供初始化列表语法,用来初始化 属性
-
语法:
构造函数():属性1(值1),属性2(值2)...
#include <iostream>
#include <string>
using namespace std;
class Person{
public:
// 写死了函数必须按这种方式传递
Person():m_name("zs"),m_age(10){
cout << "无参初始化列表" << endl;
}
// 按照参数方式传入
Person(int age, string name):m_age(age),m_name(name) {
cout << "有参初始化列表" << endl;
}
~Person(){
cout << "析构函数" << endl;
}
int m_age;
string m_name;
};
int main(){
Person p;
cout << "姓名:" << p.m_name << " 年龄:" << p.m_age << endl;
Person p1(18, "ls");
cout << "姓名:" << p1.m_name << " 年龄:" << p1.m_age << endl;
return 0;
}
类对象作为类的成员
-
C++中的成员可以是另一个类的对象,我们称该成员为对象成员
-
构造的顺序:先调用成员的构造函数,在调用本类的构造函数,析构与构造的顺序相反
#include <iostream>
#include <string>
using namespace std;
class Phone{
public:
Phone(string name):pname(name){
cout << "Phone构造函数" << endl;
}
~Phone(){
cout << "Phone析构函数" << endl;
}
string pname;
};
class Person{
public:
Person(string name, string pname):m_name(name),m_phone(pname){
cout << "Person构造函数" << endl;
}
~Person() {
cout << "Person析构函数" << endl;
}
string m_name;
Phone m_phone;
};
void test(){
Person p("zs", "iphone");
}
int main(){
test();
return 0;
}
静态成员
-
静态成员就是在成员变量和成员函数前面加上static关键字
-
静态成员也是有访问权限的
静态成员变量
-
所有对象共享同一份数据
-
在编译阶段分配内存
-
类内声明,类外初始化
-
访问方式:通过对象访问;通过类名访问
#include <iostream>
using namespace std;
class Person{
public:
// 类内声明
static int age;
private:
// 静态成员也是有访问权限的
static int a;
};
// 类外初始化,Person::告诉编译器这是这个作用域下的变量
int Person::age=100;
int Person::a = 12;
void test(){
Person p;
cout << p.age << endl;
Person p1;
p1.age=18;
cout << p.age << endl;
}
void test01(){
// 静态成员变量不属于某个对象,所有对象都共享同一份数据,因此静态成员变量有两种访问方式
// 方式一
Person p;
cout << "通过对象访问:" << p.age << endl;
// 方式二
cout << "通过类名访问:" << Person::age << endl;
}
void test02(){
// 静态成员也是有访问权限的
// error: 'a' is a private member of 'Person'
// cout << "访问静态私有变量访问不到" << Person::a << endl;
}
int main(){
test();
test01();
test02();
return 0;
}
静态成员函数
-
所有对象共享同一个函数
-
静态成员函数只能访问静态成员变量
#include <iostream>
using namespace std;
class Person{
public:
// 静态成员函数
static void func(){
// 静态成员函数只能访问静态变量
m_A = 1;
// 不能区分非静态变量是属于那个对象的成员
// error: invalid use of member 'm_B' in static member function
// m_B = 2;
cout << "static void func() 调用" << endl;
}
static int m_A;
int m_B;
};
int Person::m_A=1;
// 静态成员函数调用方式
void test(){
// 使用对象调用
Person p;
p.func();
// 使用类名调用
Person::func();
}
int main(){
test();
return 0;
}
C++ 对象模型和this指针
成员变量和成员函数分开存储
-
空类占用内存大小为1,用来占位
-
非静态成员变量属于类上的对象
-
静态成员变量、非静态成员函数(非静态成员函数也是只有一份函数实例)、静态成员函数都不属于类上的对象
#include <iostream>
using namespace std;
class A {};
class B {
// 非静态成员变量属于类上的对象
int a;
};
class C {
// 非静态成员变量属于类上的对象
int a;
// 静态成员变量不属于类的对象上
static int b;
};
class D {
// 非静态成员变量属于类上的对象
int a;
// 静态成员变量不属于类的对象上
static int b;
void func(){}
};
class E {
// 非静态成员变量属于类上的对象
int a;
// 静态成员变量不属于类的对象上
static int b;
void func(){}
static void func1(){}
};
void test(){
A a;
// 空对象占用空间大小:1
// C++编译器会给每个空对象也分配一个字节空间,是为了区分空对象占内存的位置
// 每个空对象也应该有一个独一无二的内存地址
cout << "空类的大小是:" << sizeof(a) << endl;
}
void test1(){
B b;
cout << "只有一个int类型非静态成员变量的类的大小是:" << sizeof(b) << endl;
}
void test2(){
C c;
cout << "一个int类型非静态成员变量和一个int类型的静态成员变量的类的大小是:" << sizeof(c) << endl;
}
void test3(){
D d;
// 普通成员函数不属于类上的对象
cout << sizeof(d) << endl;
}
void test4() {
E e;
// 静态成员函数不属于类上的对象
cout << sizeof(e) << endl;
}
int main(){
test();
test1();
test2();
test3();
test4();
return 0;
}
this指针
-
this指针指向被调用成员函数所属的对象
-
this指针是隐含每一个非静态成员函数的一种指针
-
this指针不需要定义,直接使用即可
this
指针具有以下特点:
-
this
指针是一个指针,它存储对象的地址。 -
this
指针是一个常量指针,不能修改它所指向的地址。 -
this
指针在非静态成员函数内部是可用的,它允许你通过this->
或(*this).
访问对象的成员变量和成员函数。 -
this
指针的类型是指向类类型的常量指针。例如,如果对象属于Person
类,则this
的类型是Person* const
。
this指针用途
-
当形参和成员变量同名时可以使用this指针来区分
-
在类的非静态成员函数中返回对象本身,
return *this;
#include <iostream>
using namespace std;
class Person{
public:
Person(int age){
// 使用this指针解决命名冲突
this->age=age;
}
// 返回对象本身时需要使用引用类型
// 如果返回的不是引用,那么外层调用位置在赋值时就会调用拷贝构造生成新的临时对象
Person& PersonAddAge(Person& p) {
this->age += p.age;
return *this;
}
int age;
};
void test(){
Person p(18);
cout << p.age << endl;
}
void test1(){
Person p1(10);
Person p2(10);
// 链式编程思想
p1.PersonAddAge(p2).PersonAddAge(p2).PersonAddAge(p2).PersonAddAge(p2);
cout << p1.age << endl;
}
int main(){
test();
test1();
return 0;
}
空指针访问成员函数
- 空指针是可以正常的访问成员函数的
#include <iostream>
using namespace std;
class Person{
public:
void showClassName() {
cout << "this is Person class" << endl;
}
void showPersonAge() {
// 增加代码的健壮性
if(this == NULL){
return;
}
// 默认类中的成员属性都会有一个默认的this指针,即this->age
cout << age << endl;
}
int age;
};
void test(){
Person * p = NULL;
p->showClassName();
// p->showPersonAge() 空指针奔溃,因为内部使用的是this->age,而this是一个NULL
p->showPersonAge();
}
int main(){
test();
return 0;
}
const修饰成员函数
常函数
-
成员函数后加上const之后,我们称这个函数为常函数
-
常函数内不可以修改成员属性
-
成员属性声明时加上mutable后,在常函数中依然可以修改
常对象
-
声明对象前加const称该对象为常对象
-
常对象只能调用常函数
#include <iostream>
using namespace std;
class Person{
public:
Person(){}
// this指针相当于Person* const this,this指向的对象不能更改
// 在函数后面加上const相当于 const Person* const this,this指向的对象和值都不能改
void showPerson() const {
// age = 10;
// a有mutable修饰所以可以更改
a = 1;
}
void func(){}
int age;
mutable int a;
};
// 常函数测试
void test(){
Person p;
p.showPerson();
}
// 常对象测试
void test1(){
// 在对象前加const,变为常对象
const Person p;
// p.age =1;
// a是mutable修饰的变量,在常对象下也可以修改
p.a = 1;
// 常对象只能调用常函数
p.showPerson();
// 常对象不能调用普通函数,因为在常对象限制了只能修改mutable修饰的变量,而在普通函数中可以修改任意变量
// p.func();
}
int main(){
test();
test1();
return 0;
}
友元
- 友元的目的就是让一个类或者函数访问另一个类中的私有成员
使用全局函数做友元
#include <iostream>
#include <string>
using namespace std;
class Building{
// 声明GoodGay为Building的友元函数,这样在GoodGay函数中可以访问Building的私有成员变量
friend void GoodGay(Building& building);
public:
Building(){
sittingRoom = "sittingRoom";
bedingRoom = "bedingRoom";
}
public:
string sittingRoom;
private:
string bedingRoom;
};
// 全局函数想访问类的私有成员
void GoodGay(Building& building){
cout << "Good Gay 正在访问:" << building.sittingRoom << endl;
cout << "Good Gay 正在访问:" << building.bedingRoom << endl;
}
void test(){
Building b;
GoodGay(b);
}
int main(){
test();
return 0;
}
类做友元
- 让友元类可以访问另一个类中的所有成员函数
#include <iostream>
#include <string>
using namespace std;
class Building;
class GoodGay {
public:
GoodGay();
// visit()函数访问Building中的属性
void visit();
Building* b;
};
class Building{
// 声明GoodGay类是Building的友元类
friend class GoodGay;
public:
Building();
public:
string sittingRoom;
private:
string bedingRoom;
};
// 类外实现成员函数
GoodGay::GoodGay(){
// 构造函数中创建一个Building的对象
b=new Building;
}
void GoodGay::visit(){
cout << "GoodGay class正在访问: " << b->sittingRoom << endl;
cout << "GoodGay class正在访问: " << b->bedingRoom << endl;
}
Building::Building(){
sittingRoom="sittingRoom";
bedingRoom="bedingRoom";
}
// 测试
void test(){
GoodGay gg;
gg.visit();
}
int main(){
test();
return 0;
}
成员函数做友元
- 让类中的某些成员函数可以访问另一个类中的私有成员变量
#include <iostream>
#include <string>
using namespace std;
class Building;
class GoodGay {
public:
GoodGay();
// visit()函数访问Building中的私有属性
void visit();
// visit1()函数不可以访问Building中的私有属性
void visit1();
Building* b;
};
class Building{
// 声明GoodGay类下的visit函数作为Building类的友元函数
friend void GoodGay::visit();
public:
Building();
public:
string sittingRoom;
private:
string bedingRoom;
};
// 类外实现成员函数
GoodGay::GoodGay(){
// 构造函数中创建一个Building的对象
b=new Building;
}
void GoodGay::visit(){
cout << "GoodGay visit正在访问: " << b->sittingRoom << endl;
cout << "GoodGay visit正在访问: " << b->bedingRoom << endl;
}
void GoodGay::visit1(){
cout << "GoodGay visit1正在访问: " << b->sittingRoom << endl;
}
Building::Building(){
sittingRoom="sittingRoom";
bedingRoom="bedingRoom";
}
void test(){
GoodGay gg;
gg.visit();
gg.visit1();
}
int main(){
test();
return 0;
}