前言:从本部分开始,就进入了C++语言学习笔记核心篇中关于类与对象的相关知识,内容重要性高,其中包括面向对象编程的三大特征,以及一些重要的其他知识包括浅拷贝与深拷贝,this关键字等等。而,本部分的主要内是C++语言面向对象编程三大特征之一的“继承”的相关知识。
目录
1、面向对象简介
C++面向对象的三大特征为:封装、继承、多态
C++认为万事万物皆可为对象,对象上有其属性和行为
eg、
1、人可以作为对象,属性有姓名、年龄、身高、体重...,行为有走、跑、跳、吃饭、唱歌...
2、车也可以作为对象,属性有轮胎、方向盘、车灯...行为有载人、放音乐、放空调...
3、具有相同性质的对象,我们可以抽象地称之为类,人属于人类,车属于车类
2、封装
2.1、封装的意义
封装是C++面向对象的三大特征之一
封装的意义:
1、将属性和行为作为一个整体,表现生活中的事物
2、将属性和行为加以权限控制
2.1.1、封装意义一
在设计类的时候,属性和行为写在一起,表现事物
语法:class 类名{
访问权限: 属性 / 行为
};
//设计一个圆类,求圆的周长
//圆周率
const double PI = 3.14;
//圆的周长公式:2 * PI * 半径
//class 代表设计一个类,类后面紧跟着的就是类名称
class Circle
{
//访问权限
//公共权限
public:
//属性(常常用变量)
int m_r; //半径
//行为(常常用函数)
double calculateZC() //获取圆的周长
{
return 2 * PI * m_r;
}
};
int main()
{
//通过圆类 创建具体的圆(对象)
//实例化 (通过一个类 创建一个对象的过程)
Circle c1;
//给圆对象 的属性进行赋值
c1.m_r = 10;
cout << "圆的周长为:" << c1.calculateZC() << endl;
}
2、
//设计一个学生类,属性有姓名和学号,
//可以给姓名和学号赋值,可以显示学生的姓名和学号
//设计一个学生类
class student
{
public:
//属性
string m_name; //姓名
string m_id; //学号
//行为
//显示姓名和学号
void showStudent()
{
cout << "姓名: " << m_name << endl;
cout << "学号: " << m_id << endl;
}
//可以通过行为来给姓名赋值
void setname(string name)
{
m_name = name;
}
//给学号赋值
void setid(string id)
{
m_id = id;
}
};
int main()
{
student s1;
s1.setname("张三");
s1.setid("1");
s1.showStudent();
return 0;
}
TIPS: 1、类中的属性和行为 我们统称为 成员
2、属性 又称:成员属性 、成员变量
3、行为 又称:成员方法 、成员函数
2.1.2、封装意义二
类在设计时,可以把属性和行为放在不同的权限下,加以控制
访问权限有三种:
1、public 公共权限 //成员 类内可以访问 类外也可以访问
2、protected 保护权限 //成员 类内可以访问 类外不可以访问 儿子可以访问父亲的保护内容
3、private 私有权限 //成员 类内可以访问 类外不可以访问 儿子不能访问父亲的私有内容
2.1.3、struct 和 class 的区别
在C++中 struct 和 class 唯一的区别就在:默认的访问权限不同
区别:
1、struct 默认权限为 公共
2、class 默认权限为 私有 ··将成员属性设置为私有
优点1:将所有的成员属性设置为私有,可以自己控制读写权限
优点2:对于写权限,我们可以检查数据的有效性
class Person()
{
public:
//设置姓名
void setName(string name)
{
m_Name = name;
}
//获取姓名
void getName()
{
return m_Name;
}
//获取年龄
int getAge()
{
m_Age = 10;//初始化为10岁
return m_Age;
}
//设置年龄
void setAge(int age)
{
if(age<0 || age>150)
{
m_Age = 0;
cout << "年龄输入错误!" << endl;
return;
}
m_Age = age;
}
//设置情人
void setLover(string lover)
{
m_Lover = lover;
}
private:
//姓名 可读可写
string m_Name;
//年龄 可读可写 如果想修改(年龄范围必须是 0~150 之间)
int m_Age;
//情人 只写
string m_Lover;
};
2.2、对象初始化和清理
生活中我们买的电子产品都基本会有出厂设置,在某一天我们不用的时候也会删除一些自己的信息数据,保证安全
C++中的面向对象来源于生活,每个对象也都会有初始化设置以及对象销毁前的清理数据的设置
2.2.1、构造函数和析构函数的简介
对象的初始化和清理也是两个非常重要的安全问题:
一个对象获变量没有初始化状态,对其使用后果是未知
同样的使用完一个对象或变量,没有及时清理,也会造成一定的安全问题
# C++利用构造函数和析构函数解决上述问题,这两个函数将会被编译器自动调用,完成对象初始化和清理工作。
# 对象初始化和清理是编译器强制我们要做的事,因此如果我们不提供构造和析构,编译器会提供
# 编译器提供的构造函数和析构函数是空实现。
2.2.1.1、构造函数
主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无需动手调用
语法:类名(){ }
1、构造函数,没有返回值也不用写void
2、函数名称与类名相同
3、构造函数可以有参数,因此可以发生重载
4、程序在调用对象时会自动调用构造函数,无需动手调用,而且只会调用一次
2.2.1.2、析构函数
作用在于对象销毁前,系统自动调用,执行一些清理工作
语法:1、析构函数,没有返回值也不写void
2、函数名称与类名相同,在名称前要加上符号 ~
3、析构函数不可以有参数,因此不可以发生重载
4、程序在对象销毁前会自动调用析构,无需动手调用,而且只会调用一次
class Person
{
public:
//构造函数
Person()
{
cout << "调用Person构造函数!" << endl;
}
//析构函数
~Person()
{
cout << "调用Person析构函数!" << endl;
}
};
//构造和析构都是必须实现的,如果我们自己不提供,编译器会提供一个空实现的构造和析构
void test01()
{
Person p; //在栈上的数据,test01执行完毕后,释放这个对象
}
void main()
{
test01();
}
2.2.2、构造函数的分类及调用
两种分类方式:
按参数分类:有参数构造 和 无参数构造(无参构造又称为默认构造函数)
按类型分类:普通构造 和 拷贝构造
拷贝构造函数的写法:
函数名( const 用引用的方式写欲拷贝的类 )//Person( const Person &p)
{
//将传入的类身上的所有属性,拷贝到该类上 // age = p.age;
}
三种调用方式:
1、括号法
2、显示法
3、隐式转换法
void test01()
{
//1、括号法
Person p1;//默认构造函数调用
Person p2(10);//有参构造函数
Person p3(p2);//拷贝构造函数
cout << "p2的年龄: " << p2.age << endl;
//因为p3为拷贝构造函数,拷贝p2,所以两者输出的值会一样
cout << "p3的年龄: " << p3.age << endl;
/*TIPS:调用默认构造函数时,不要加(),否则会无反应;因为编译器会认为该代码是一个函数的声明,不会认为是在创建对象*/
//2、显示法
Person p1;
Person p2 = Person(10);//有参构造
Person p3 = Person(p2);//拷贝构造
/*TIPS:Ⅰ、等号右侧的 Person(10) 和 Person(p2) 是匿名对象;而等号的左侧就是它的名; 特点:若只有匿名对象,为给其名,则在当前行执行完后,系统会立即回收掉匿名对象*/
//Ⅱ、不要使用拷贝函数初始化匿名函数
Person(p3);//p3为拷贝函数
//编译器会人为 Person(p3) === Person p3; 编译器会认为是对象的声明,会导致重复定义而报错
//3、隐式转换法
Person p4 = 10; //相当于写了
Person p4 = Person(10); 有参构造
Person p4 = p5; //拷贝构造
}
··拷贝调用函数使用时机
C++中拷贝构造函数调用时机通常有三种情况
1、使用一个已经创建完毕的对象来初始化一个新对象
2、值传递的方式给函数参数传值
3、以值方式返回局部对象
class Person
{
public:
Person()
{
cout << "Person默认构造函数调用" << endl;
}
Person(int age)
{
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);
cout << "p2的年龄是: " << p2.m_Age << endl;
}
//2、值传递的方式给函数参数传值
void doWork(Person p) //实参会将 p 拷贝一个临时副本传到形参中;所以不会影响实参的值
{
}
void test02()
{
Person p;
doWork(p);
}
//3、以值方式返回局部对象
Person doWork2()
{
Person p1;
cout << (int*)&p1 << endl; //通过查看地址是否相同,可以知道 p1 和 p 是否是同一个
//返回的时候会根据 p1 创建一个新的对象,然后返回给 test03() ;即返回的是 p1 的拷贝函数
return p1;
}
void test03()
{
Person p = doWork2();
//通过查看地址是否相同,可以知道 p1 和 p 是否是同一个;结果是不同的,所以两者是不同的对象
cout << (int*)&p << endl;
}
void main()
{
test01();
}
2.2.3、构造函数的调用规则
···默认情况下,C++编译器给每一个类添加至少3个函数
1、默认构造函数(无参,函数体为空)
2、默认析构函数(无参,函数体为空)
3、默认拷贝构造函数,对属性进行值拷贝
2.2.3.1、构造函数调用规则
1、如果用户定义有参构造函数,C++不再提供默认无参构造函数,但会提供默认拷贝函数
2、如果用户定义拷贝构造函数,C++不会再提供其他构造函数
2.2.4、深拷贝与浅拷贝
TIPS:深拷贝是面试经典问题,也是常见的一个坑
···浅拷贝:简单的赋值拷贝操作
···深拷贝:在堆区重新申请空间,进行拷贝操作
class Person
{
public:
//无参(默认)构造函数
Person()
{
cout << "Person 的默认构造函数调用!" << endl;
}
//有参构造函数
Person(int age, int height)
{
m_Age = age;
m_Height = new int(height);
cout << "Person 的有参构造函数调用!" << endl;
}
//自己实现拷贝构造函数,解决浅拷贝带来的问题
Person(const Person &p)
{
cout <<"Person 拷贝构造函数调用!" << endl;
m_Age = p.m_Age;
//m_Height = p.m_Height;编译器默认实现该行代码
//深拷贝操作:利用深拷贝在堆区创建新内存
m_Height = new int(*p.m_Height);
}
~Person()
{
//析构代码,将堆区开辟的数据做释放操作
if(m_Height != NULL)
{
delete m_Height;
m_Height = NULL; //防止出现野指针
}
cout << "Person 的析构函数调用!" << endl;
}
int m_Age; //年龄
int *m_Height; //身高
};
void test01()
{
Person p1(18);
cout << "p1 的年龄为:" << p1.m_Age << " 身高为: " << p1.m_Height << endl;
//会报错。原因:若利用编译器提供的拷贝构造函数,会做浅拷贝操作,会出现浅拷贝的问题
Person p2(p1);
//而浅拷贝带来的问题:堆区的内存重复释放
//解决浅拷贝的问题:利用深拷贝进行解决
}
void main()
{
test01();
}
TIPS:若属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题
尾声:以上是我学习是做的笔记,可供大家参考学习,若有不准确的地方,欢迎指出!