前言:课还没上完,但是可以把后面的部分内容提前放出来,保证内容的完整性,同时以便预习和下次课的复习
三、类和对象
C++面向对象三大特性:封装、继承、多态
C++认为万事万物都皆为对象,对象上有其属性和行为
具有相同性质的对象,都可以抽象为一类
(一)封装
0.结构体:
结构体变量:
调用方法:
stu.num1 / stu.num2;
结构体指针:
指针的调用方法:
定义结构体类型*p
1.p->num1 / p->num2;
2.(*p).num1 / (*p).num2;
1.封装
封装意义一:
类中的属性和行为都代表成员
属性也称为成员属性或者成员变量
行为也称为成员函数或者成员方法
// 类中的属性和行为都成为成员
// 属性 也成为成员属性or成员变量
// 行为 也成为成员函数or成员方法
//class代表一个类,后面就是类名称
class Circle
{
//访问权限
//公共权限
public:
//属性
int m_r;
//行为
//获得园的周长
double calculateZC()
{
return 2 * PI * m_r;
}
};
int main()
{
Circle cl;
//给圆对象的属性进行赋值
cl.m_r = 10;
cout << "圆的周长为:" << cl.calculateZC() << endl;
system("pause");
return 0;
}
封装意义二:
公共权限 public 成员类内可以访问,类外也可以访问
保护权限 protected 成员类内可以访问,类外不可以访问 儿子也可以访问父亲中的保护内容
私有权限 private 成员类内可以访问,类外不可以访问 儿子不可以访问父亲中的私有内容
2.struct和class区别
区别:默认的访问权限不一样
struct 默认为共有
class 默认为私有
3.成员属性设置私有化
优点一:将所有成员属性设置为私有,可以自己控制读写权限
优点二:对于写权限,我们可以检测数据的有效性
可以控制为只写不读/只读不写/可读可写
我们可以在class类里将成员属性定义为private类型,并在public类型中定义函数接口来改变类内私有成员的属性,通常用set和show来分别表示给数据赋值和输出数据
(二)对象的初始化和清理
1.构造函数和析构函数
对象的初始化和清理是两个非常重要的安全问题
一个对象或者变量没有初始状态,对其使用后果是未知的
同样的使用完一个对象或变量,没有及时清理,也会造成一定的安全问题
构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用
析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理工作
构造函数语法:类名(){}
1.构造函数,没有返回值也不写void
2.函数名称与类名相同
3.构造函数里可以有参数,因此可以发生重载
4.程序在调用对象时会自动调用构造,无须手动调用,而且只会调用一次
析构函数语法:~类名(){}
1.析构函数,没有返回值也不写void
2.函数名称与类名相同,在名称前面加上~
3.构造函数里不可以有参数,因此不可以发生重载
4.程序在对象销毁前会自动调用析构,无须手动调用,而且只会调用一次
#include <iostream>
#include <string.h>
#include <math.h>
#include <malloc.h>
using namespace std;
#include "swap.h"
class Person
{
public:
Person()
{
cout << "Person的构造函数的调用" << endl;
}
~Person()
{
cout << "Person的析构函数的调用" << endl;
}
};
//构造和析构都是必须有的实现,如果我们自己不写,编译器会提供一个空实现的构造和析构
void test01()
{
Person p;//在栈上的数据,test01执行完毕后,释放这个对象
}
int main()
{
test01();
system("pause");
return 0;
}
2.构造函数的分类及调用
两种分类方式:
按参数分为:有参构造和无参构造
按类型分为:普通构造和拷贝构造
三种调用方式:
括号法
Person p1;//默认构造函数
***调用默认构造函数时不要加(),因为编译器会认为是一个函数的声明,而不会认为在创建对象
Person p2(10);//有参构造函数
Person p3(p2);//拷贝构造函数
显示法
隐式转换法
拷贝构造函数---拷贝出一样的数据
不能改变原对象,且要以引用的方式传入
Person(const Person &p)
原因:因为如果我们传值当形参的话,形参是实参的一份临时拷贝,则又会调用拷贝构造函数,进入无限递归下去,报错。
#include <iostream>
#include <string.h>
#include <math.h>
#include <malloc.h>
using namespace std;
#include "swap.h"
class Person
{
public:
//普通构造
Person()
{
cout << "Person的无参构造函数的调用" << endl;
//也叫默认构造
}
Person(int a)
{
age = a;
cout << "Person的有参构造函数的调用" << endl;
}
//拷贝构造函数---拷贝出一样的数据
//不能改变原对象,且要以引用的方式传入
Person(const Person &p)
{
//将传入的人身上的所有属性拷贝到当前对象身上
age = p.age;
cout << "Person的拷贝构造函数的调用" << endl;
}
~Person()
{
cout << "Person的析构函数的调用" << endl;
}
int age;
};
//构造和析构都是必须有的实现,如果我们自己不写,编译器会提供一个空实现的构造和析构
void test01()
{
//调用
//1.括号法
//Person p1;//默认构造函数
//Person p2(10);//有参构造函数
//Person p3(p2);//拷贝构造函数
//***注意事项1
//调用默认构造函数时不要加()
//cout << "p2的年龄为:" << p2.age << endl;
//cout << "p3的年龄为:" << p3.age << endl;
//2.显示法
Person p1;
Person p2 = Person(10);//有参构造
Person p3 = Person(p2);//拷贝构造
Person(10);//匿名对象 特点:当前行执行结束后,系统会立即回收掉匿名对象
//***注意事项2
// 不要利用拷贝构造函数 初始化匿名对象
//编译器会认为 Person(p3)等价于Person p3;对象声明
//3.隐式转换法
Person p4 = 10;//相当于写了Person p4 = Person(10)
}
int main()
{
test01();
system("pause");
return 0;
}
3.拷贝构造函数调用时机
有三种情况:
使用一个已经创建完毕的对象,来初始化一个新对象
值传递的方式给函数参数传值
以值方式返回局部对象
#include <iostream>
using namespace std;
class Person
{
public:
Person()
{
cout << "Person的默认构造函数的调用" << endl;
}
Person(int age)
{
cout << "Person的有参构造函数调用" << endl;
m_Age = age;
}
Person(const Person& p)
{
cout << "Person的拷贝函数的调用" << endl;
m_Age = p.m_Age;
}
~Person()
{
cout << "Person的析构函数的调用" << endl;
}
int m_Age;
};
//1.使用一个已经创建完毕的对象,来初始化一个新对象
void test01()
{
Person p1(20);
Person p2(p1);
}
//2.值传递的方式给函数参数传值
void doWork(Person p)
{
}
void test02()
{
Person p;
doWork(p);
}
//3.以值方式返回局部对象
Person doWork2()
{
Person p1;
return p1;
}
void test03()
{
Person p = doWork2();
}
int main()
{
//test01();
//test02();
test03();
system("pause");
return 0;
}
4.构造函数调用规则
默认情况下,C++编译器至少给一个类添加三个函数
1.默认构造函数(无参,函数体为空)
2.默认析构函数(无参,函数体为空)
3.默认拷贝函数,对属性进行值拷贝
构造函数调用规则如下:
如果用户定义有参构造函数,C++不再提供默认无参构造,但是会提供默认拷贝构造
如果用户自己定义拷贝构造函数,C++不会再提供其他构造函数
**就是写了有参必须写无参,写了拷贝必须写有参无参
5.**深拷贝与浅拷贝
深浅拷贝是面试经典问题,也是常见的一个坑
浅拷贝:简单的赋值拷贝操作
深拷贝:在堆区重新申请空间,进行拷贝操作
析构函数代码,通常会被用作于:在堆区开辟数据做释放操作
正确写法:
但此代码会崩掉!
原因:堆区内存重复释放
默认的拷贝构造函数是浅拷贝!!
栈区先进后出,p1先进p2后进 p2先把堆区释放了,而p1会再释放一次---非法操作
浅拷贝带来的问题就是:堆区的内存重复释放
浅拷贝的问题要用深拷贝来进行解决
重新在堆区开辟空间则可以解决问题
自己实现拷贝构造函数,解决浅拷贝带来的问题
总结:如果属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题!!!
6.初始化列表
作用:C++提供了初始化列表语法,用来初始化属性
语法:构造函数():属性1(值1),属性2(值2)......{}
更灵活的方法:
7.类对象作为类成员
C++类中的成员可以是另一个类的对象,我们称之为对象成员
当其他类对象作为本类成员,构造时先构造类对象,再构造自身,析构顺序与构造相反
(先有零件再有整体,先拆整体再拆零件)
8.静态成员
静态成员就是在成员变量和成员函数前加上关键字static,称为静态成员
静态成员分为:
1.静态成员变量
所有成员共享同一份数据
在编译阶段分配内存
类内声明,类外初始化操作(必须要有,否则会报错)
输出:100 200
静态成员变量不属于某个对象,所有对象都共享同一份数据
非静态成员变量只能创建一个变量再访问
而静态成员变量有两种访问方式
1.通过对象访问
Person p;
cout<<p.m_A<<endl;
2.通过类名进行访问
cout<<Person::m_A<<endl;
静态成员变量也是有访问权限的!
2.静态成员函数
所有对象共享
静态成员函数只能访问静态成员变量
有两种访问方式:
静态成员函数也是有访问权限的!