C++:类和对象

——我的梦想是面向对象编程

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_heip2.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类里有两个函数visit1visit2两个成员函数 我只想让visit1访问Building类的私有成员 这时候就不能让GoodGay做友元了 只能让他的成员函数visit1做友元
声明方式

friend void GoodGay::visit1(Building* building);
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值