黑马程序员课程学习笔记
内存分区
分四区:代码区、全局区、栈区、堆区
代码区
程序运行前就存在,存放二进制文件,共享的,只读
全局区
- 程序运行前就存在,存放全局变量,静态变量和常量(其中常量包括字符串常量、全局常量)
- 程序运行结束后,由操作系统释放
- 注意,局部变量加const修饰为局部常量,局部常量和局部变量都不在全局区
- 此外,string str=“abc”;str和"abc"地址不一样,str是局部变量,"abc"是字符串常量
#include <iostream>
using namespace std;
/*全局区,存放:全局变量,静态变量,常量,
程序运行结束后,由操作系统释放*/
//函数体外部定义全局变量
int g_c = 30;
int g_d = 40;
//全局常量
const int c_g_a = 40;
const int c_g_b = 40;
int main() {
//函数体内部定义局部变量
int a = 10;//局部变量
int b = 10;//局部变量
cout << "局部变量a地址:" << (int)&a << endl;
cout << "局部变量b地址:" << (int)&b << endl << endl;
//打印全局变量
cout << "全局变量g_c地址" << (int)&g_c << endl;
cout << "全局变量g_d地址" << (int)&g_d << endl<<endl;
//静态变量
static int s_e = 10;
static int s_f = 10;
cout << "静态全局变量s_e地址" << (int)&s_e << endl ;
cout << "静态全局变量s_f地址" << (int)&s_f << endl << endl;
//常量
//字符串常量
string str = "hello world" ;
cout << "字符串常量地址" << (int)&"hello world" << endl;
//这里如果用string定义,就是局部变量了,单个的字符串就是常量
cout << "局部变量str地址" << (int)&str << endl << endl;
//全局常量
//局部常量
const int c_l_a = 1;
const int c_l_b = 1;
cout << "全局常量c_g_a地址" << (int)&c_g_a << endl;
cout << "全局常量c_g_b地址" << (int)&c_g_b << endl;
cout << "局部常量c_l_a地址" << (int)&c_l_a << endl;
cout << "局部常量c_l_b地址" << (int)&c_l_b << endl;
return 0;
}
从上述地址可以看到,圈出的三个地址很近,离其他的很远,这三个都不在全局区
栈区
- 存放局部变量和形参
- 栈区的数据由编译器管理开辟和释放,先进后出
- 不要返回局部变量的地址
#include <iostream>
using namespace std;
/*栈区,存放局部变量和形参
* 栈区的数据由编译器管理开辟和释放
* 不要返回局部变量的地址*/
int* fun(int b) {
b = 100;
int a = 10;//这里的a和b都在栈区
return &a;
}
int main() {
int* p = fun(1);
cout << *p << endl;//第一次打印正确是编译器做了保留
cout << *p << endl;//第二次不再保留,打印失败,
return 0;
}
堆区 new
- 由程序员分配和释放,若程序员未释放,程序结束时,由系统回收
- 利用new在堆区开辟内存
- 利用delete释放堆区数据
- int * arr=new int[10];在堆区开辟一个长度为10的数组,new int(10),是开辟一个整型数据
- 开辟的数据用什么接收?new返回的是该数据类型的指针,所以要用指针来接收
- 利用delete[] 数组名释放堆区数组
#include <iostream>
using namespace std;
/*堆区
由程序员分配和释放,若程序员未释放,程序结束时,由系统回收
利用new在堆区开辟内存
利用delete释放*/
int*fun(){
//int a = 10;
//括号里面是初始化值,可以是字符型,字符串型以及整数型
int* a = new int(123);
//int *a = new int(10);
return a;
}
void fun2() {
//中括号表示一组数据,10表示数组有10个元素,()表示一个数据}
int *arr = new int[10];
for (int i = 0; i < 10; i++){
arr[i] = rand() % 50;
}
for (int i = 0; i <10; i++){
cout << arr[i] << " ";
}
//释放堆区数据
//释放数组时要加[]
delete[] arr;
}
int main() {
int* p = fun();
cout << "*p=" << *p << endl;
cout << "*p=" << *p << endl;
delete p;
//cout << "*p=" << *p << endl;//内存已被释放,不能再访问
fun2();
return 0;
}
引用
基本用法
- 作用:给变量起别名
- 定义:数据类型 &别名 = 原名;
- 注意:引用必须初始化,初始化之后不可再修改
#include <iostream>
using namespace std;
//引用:给变量起别名
//数据类型 &别名=原名
//注意:引用必须初始化
//初始化之后不可以再改变
int main() {
int a = 10;
int c= 30;
cout << "a=" << a << endl;
int& b = a;//引用之后,操作的是同一块内存
cout << "b=" << b << endl;
cout << "a=" << a << endl;
b = 20;
b=c;//这样写是把c的值赋予b,而不是改变引用
cout << "b=" << b << endl;
cout << "a=" << a << endl;
return 0;
}
引用作为函数参数
- 三种函数传参方式:值传递,地址传递,引用
- 地址传递和引用均可以修饰实参,但是引用可能操作更简单一点
#include <iostream>
using namespace std;
void test01(int a, int b);//值传递
void test02(int* a, int* b);//地址传递
void test03(int& a, int& b);//引用
//地址传递和引用均可以修饰实参,即操作的是一块内存
int main() {
int a = 10, b = 30;
cout << "交换前:" << endl;
cout << "a=" << a << ",b=" << b << endl << endl;;
test01(a, b);
cout << "a=" << a << ",b=" << b << endl;
a = 10;
b = 30;
test02(&a, &b);
cout << "a=" << a << ",b=" << b << endl;
a = 10;
b = 30;
test03(a, b);
cout << "a=" << a << ",b=" << b << endl;
return 0;
}
void test01(int a, int b) {
cout << "值传递:" << endl;
int temp = a;//把a的值给temp
a = b;
b = temp;
}
void test02(int* a, int* b) {
cout << "\n地址传递:" << endl;
int temp = *a;//a是地址,a解引用后的值给temp
*a = *b;
*b = temp;
}
void test03(int& aa, int& bb) {
cout << "\n引用:" << endl;
int temp = aa;//aa是a的引用,就是a换了一个名字,操作的是一块内存
aa = bb;
bb = temp;
}
引用作为函数返回值
1.不要返回局部变量的引用
2.引用的函数调用可以作为左值
#include <iostream>
using namespace std;
int& test01();
int& test02();
int main() {
int r1 = test01();
cout << "r1=" << r1 << endl;//这里接收的是r1
cout << "r1=" << r1 << endl;
int & r2 = test01();
cout << "r2=" << r2 << endl;
cout << "r2=" << r2 << endl;//这里就有问题了,
int& r3 = test02();
cout << "r3=" << r3 << endl;
cout << "r3=" << r3 << endl;
cout << "r3=" << r3 << endl;
test02() = 100;//函数调用可以作为左值
//test02()返回的是a的引用,因此可以作为左值
cout << "r3=" << r3 << endl;
cout << "r3=" << r3 << endl;
return 0;
}
int& test01() {
int a = 10;//局部变量,栈区
return a;//用引用的方式返回a,是不合法的
}
int& test02() {
static int b = 20;//静态变量,全局区,程序结束后由系统释放
return b;
}
引用的本质
- 指针常量:*+const,指针的指向不可以修改
- 引用的本质是指针常量,故初始化之后就不能修改指向,但是值可以改。
- int &a=b;等价于int * const a = &b;
常量引用
在函数传参时,地址传递和引用的形参都能修饰实参,如果不想实参别修改,可以价格const,前面的指针常量就有这个用处,这里用const修饰引用也是一样的。
void test(const int&a){}
此外
const int& a = 10;
//是合法的,编译器做了如下工作,
//int temp = 10;
//const int& a = temp;
类和对象
对象那个的三大特性:封装、继承、多态
封装
将属性和行为写到一起,来表现事物
语法:class 类名{ 访问权限 属性 \ 行为};
类的案例1:一个圆类
设计一个圆类,求圆的周长和面积
#include <iostream>
using namespace std;
const double PI = 3.1415926;
//设计一个圆类,求圆的周长
class Circle{
public://访问权限
//属性
double m_r;
//行为
double calculateC() {
return 2 * PI * m_r;
}
double calculateS() {
return PI * m_r* m_r;
}
};
int main() {
//通过圆类,创建具体的圆对象,即实例化
Circle yuan;
cout << "请输入圆的半径" << endl;
cin >> yuan.m_r;
cout << "半径:" << yuan.m_r
<< ",周长:" << yuan.calculateC()
<< ",面积:" << yuan.calculateS()<< endl;
return 0;
}
类的案例2:学生类
设计一个学生类,包含姓名和学号,且能通过成员函数赋值
#include <iostream>
#include <string>
using namespace std;
//设计一个学生类,包含姓名和学号,且能赋值
class Student{
public://访问权限
//属性=>成员变量
string m_Name;
int m_ID;
//行为=>成员函数
void setValue() {
cout << "请输入学生姓名和学号" << endl;
cin >> m_Name>>m_ID;
}
void showValue() {
cout << "姓名:" << m_Name<<"\t学号:"<<m_ID<<endl;
}
void modifyValue(string name, int id) {
m_Name = name;
m_ID = id;
}
};
int main() {
//实例化学生对象
Student s1;
//这里主要展示两种赋值方法
//方法1
s1.setValue();
s1.showValue();
//方法2
s1.modifyValue("李四",2020225030);
s1.showValue();
return 0;
}
访问权限
访问权限共有三种:公有、保护、私有
类型 | 成员的访问权限 | 子类 |
---|---|---|
公有 | 类内可以访问,类外也能访问 | |
保护 | 类内可以访问,类外不可以访问 | 子类可以访问 |
私有 | 类内可以访问,类外不可以访问 | 子类不可以访问 |
#include <iostream>
using namespace std;
//类的访问权限共有三种:
//公共权限 public 成员 类内可以访问,类外也能访问
//保护权限 protected 成员 类内可以访问,类外不可以访问 子类可以访问
//私有权限 private 成员 类内可以访问,类外不可以访问 子类不可以访问
class Person {
//这里面的就是类内,包括成员变量和成员函数
public:
string m_Name;//姓名
protected:
string m_Car;//汽车
private:
int m_Password;//银行卡密码
public:
//类内,三个都可以访问
void func() {
m_Name = "张三";
m_Car = "拖拉机";
m_Password = 123123;
}
};
int main() {
Person p1;
p1.m_Name = "李四";//类外,只有公有权限下的姓名可以访问
p1.func();//虽然类外不能访问另外两种权限的变量,但是public下的函数提供了接口
return 0;
}
class 与strut区别
class默认访问权限是私有的,而class是公有的
#include<iostream>
using namespace std;
//class 默认公有
//struct 默认私有
class C1 {
int m_A;//默认私有
};
struct S1{
int m_B;//默认公有
};
int main() {
C1 c1;
S1 s1;
s1.m_B = 1;
//c1.m_A = 1;//m_A是私有,类外不可访问
return 0;
}
成员属性设为私有
成员属性设为私有,类外通过公有成员函数对私有成员变量进行读写操作,优势如下:
- 可以自己控制读写权限
- 对于写权限,可以检测写入数据的有效性
#include <iostream>
using namespace std;
/*成员属性私有的优势
1.可以自己控制读写权限
2.对于写权限,可以检测写入数据的有效性*/
class Person{
//姓名
string m_Name;
//年龄
int m_Age;
public://对外的接口,用以设置和获取成员变量值
void setName(string name) {
m_Name = name;
}
void setAge(int age) {
//这里可以判断输入数据是否有效
if (age < 0 || age>120) {
age = 0;
cout << "输入失败" << endl;
//也可以只要上面的age=0;错误输入,就赋值0,
return;//这里如果是无效数据,会return掉,相当于没赋值
}
m_Age = age;
}
void showPerson() {
cout << "姓名:" << m_Name << "\t年龄:" << m_Age << endl;
}
string getName() {
return m_Name;
}
int getAge() {
return m_Age;
}
};
int main() {
Person p;
p.setAge(200);
p.setName("张三");
//p.showPerson();
cout << p.getName() << "的年龄为" << p.getAge() << endl;
return 0;
}
案例:点类和圆类
设计一个点类和圆类,判断点和圆的关系。
要点:类的分文件编写
- Point.h
#pragma once//防止头文件重复包含
#include <iostream>
using namespace std;
//头文件的最上面要防止头文件重复包含
//头文件需要类、成员变量定义以及成员函数的声明即可
class Point {
double m_X, m_Y;//成员变量
public:
void setPoint(double x, double y);//成员函数声明
double getPoint_X();
double getPoint_Y();
};
- Point.cpp
#include "Point.h"
//源文件需要包含头文件
//只需要成员函数的实现即可,类和成员函数定义都不需要
//但还是不够,要在函数前加上作用域Point::,否则不认识成员函数
void Point::setPoint(double x, double y) {//源文件只需要成员函数实现
m_X = x;
m_Y = y;
}
double Point::getPoint_X() {
return m_X;
}
double Point::getPoint_Y() {
return m_Y;
}
接着是距离计算函数,用来计算两点之间的距离
- getDistance.h
#pragma once
#include "Point.h"
double getDistance(Point p1, Point p2);
- getDistance.cpp
#include "getDistance.h"
double getDistance(Point p1, Point p2) {
double x, y;
x = pow((p1.getPoint_X() - p2.getPoint_X()), 2);
y = pow((p1.getPoint_Y() - p2.getPoint_Y()), 2);
return sqrt(x + y);
}
- Circle.h
#pragma once
#include <iostream>
#include "Point.h"
#include "getDistance.h"
using namespace std;
class Circle {
Point m_Pc;
double m_Ra;
public:
void setPc(Point p);
void setRadius(double r);
Point getPc();
double getRa();
void Point2Circle(Point p);
};
- Circle.cpp
#include "circle.h"
//注意作用域circle::
void Circle::setPc(Point p) {
m_Pc.setPoint(p.getPoint_X(), p.getPoint_Y());
}
void Circle::setRadius(double r) {
m_Ra = r;
}
Point Circle::getPc() {
return m_Pc;
}
double Circle::getRa() {
return m_Ra;
}
void Circle::Point2Circle(Point p) {
int ref = getDistance(this->m_Pc, p);
cout << "点的坐标:(" << p.getPoint_X() << "," << p.getPoint_Y() << ")" << endl;
cout << "圆心坐标:(" << this->m_Pc.getPoint_X() << "," << this->m_Pc.getPoint_Y() << ")" << endl;
cout << "圆的半径:" << this->m_Ra << endl;
cout << "点到圆心的距离:" << ref << endl;
if (ref > this->m_Ra) {
cout << "点在圆外" << endl;
}
else if (ref == this->m_Ra) {
cout << "点在圆上" << endl;;
}
if (ref < this->m_Ra) {
cout << "点在圆内" << endl;;
}
}
- main函数
#include <iostream>
#include "Circle.h"//这里是分文件编写之后包含的圆类头文件
using namespace std;
int main() {
Point p1,p2;
p1.setPoint(0, 0);
p2.setPoint(20,0);
Circle c1;
c1.setPc(p2);
c1.setRadius(20);
c1.Point2Circle(p1);
return 0;
}
对象的初始化和清理
- 构造函数:创建对象时为对象的成员属性赋值,构造函数有编译器自动调用,无须手动调用
- 析构函数:对象销毁前,系统自动调用,执行一些清理工作
构造函数 | 析构函数 |
---|---|
没有返回值,不写void | 没有返回值,不写void |
函数名与类名相同 | 函数名与类名不同 |
调用对象时由程序自动调用,且只调用一次 | 对象销毁时由程序自动调用,且只调用一次 |
可以有参数,可重载 | 不可以有参数,不能重载 |
构造函数 类名(){}
无参构造函数,如果是空实现,未对成员变量初始化,会有如下报错。
解决方式:初始化成员变量
- 按照有无参数分为无参构造和有参构造,其中无参构造又称为默认构造
- 按类型可分为普通构造和拷贝构造
- 三种调用方法,推荐括号法
#include <iostream>
using namespace std;
/*构造函数*/
class Person {
public:
Person() {
cout << "Person无参构造函数调用" << endl;
}
Person(int a) {
this->m_Age = a;
cout << "Person有参构造函数调用" << endl;
}
Person(const Person &p) {
this->m_Age = p.m_Age;
cout << "Person拷贝构造函数调用" << endl;
}
~Person() {
cout << "Person析构函数调用" << endl;
}
int m_Age;
};
void test01() {
//调用:
//1.括号法
//Person p;//无参构造函数调用
//Person p2(20);//有参构造函数调用
//Person p3(p2);//拷贝构造函数,把p2的数据拷贝到p3
//cout << "p2年龄:" << p2.m_Age << endl;
//cout << "p3年龄:" << p3.m_Age << endl;
//注意事项1,调用无参构造不要加()
//Person p();// 编译器会以为是函数声明
//2.显示法
//Person p4;//无参构造
//Person p5 = Person(3);//有参构造,p5是对象的名
//Person p6 = Person(p5);//拷贝构造
//Person(55);//匿名对象,当前行结束后,就被释放
//注意事项2,不要用拷贝构造初始化匿名对象
//Person(P5);//编译器会以为是实例化了一个对象 Person p5;
//3.隐式构造法
Person p7 = 10; //相当于Person p7 = Person(10);
}
int main() {
test01();
system("pause");
return 0;
}
拷贝构造函数调用时机
拷贝函数调用时机
- 使用一个已经创造完毕的对象来初始化一个新对象
- 值传递的方式给函数参数传值
- 值方式返回局部对象
#include <iostream>
using namespace std;
/*拷贝函数调用时机:
1.使用一个已经创造完毕的对象来初始化一个新对象
2.值传递的方式给函数参数传值
3.值方式返回局部对象*/
class Person {
public:
Person() {
cout << "Person默认构造函数" << endl;
}
Person(int age) {
this->m_Age = age;
cout << "Person有参构造函数" << endl;
}
Person(const Person &p) {
this->m_Age = p.m_Age;
cout << "Person拷贝构造函数" << endl;
}
~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;
cout << (int)&p1 << endl;
return p1;
}
void test03() {
Person p = dowork2();
cout <<(int) &p << endl;//p1和p地址不同,说明不是一个东西,发生了拷贝
}
int main() {
//test01();
//test02();
test03();
system("pause");
return 0;
}
构造函数调用规则
- 默认情况下,编译器提供默认构造函数(无参,函数体为空)、默认析构函数(无参,函数体为空)和默认拷贝构造函数。
- 无参构造<有参构造<拷贝构造,如果用户定义了有参构造,则c++不提供无参构造,但还会提供拷贝构造;如果用户定义了拷贝构造,则编译器不提供构造函数。如果编译器不提供,自己又不去定义,就会报错。
#include <iostream>
using namespace std;
/*这是一个错误示范!*/
class Person {
public:
int m_Age;
//Person() {
// cout << "Person默认构造函数调用" << endl;
//}
Person(int age) {
this->m_Age = age;
cout << "Person有参构造函数调用" << endl;
}
~Person() {
cout << "Person默认析构函数调用" << endl;
}
};
void test01() {
Person p;//因为自己定义了有参构造,所以系统不再提供无参构造,在注释掉自己写的无参构造之后,会报错找不到可用的无参构造。
}
int main() {
test01();
system("pause");
return 0;
}
深拷贝与浅拷贝
- 浅拷贝:编译器提供的那种简单的等号赋值操作,存在堆区内存重复释放的缺点
- 深拷贝:在堆区重新申请一块内存空间,进行拷贝操作
因此,如果有属性在读取开辟,要自己提供深拷贝,防止堆区内存重复释放
#include <iostream>
using namespace std;
/*深拷贝与浅拷贝
浅拷贝:编译器提供的那种简单的等号赋值操作,存在堆区内存重复释放的缺点
深拷贝:在堆区重新申请一块内存空间,进行拷贝操作*/
class Person {
public:
int m_Age;//年龄
int *m_Height;//身高
Person() {
cout << "Person默认构造函数调用" << endl;
}
Person(int age,int height) {
this->m_Age = age;
//int *m_Height=new int(height);
this->m_Height = new int(height);
cout << "Person有参构造函数调用" << endl;
}
//这里如果没有提供拷贝构造函数,由编译器提供,则为浅拷贝
Person(const Person& p) {
this->m_Age = p.m_Age;
//this->m_Height = p.m_Height;//编译器默认的写法,是浅拷贝
//*p.m_Height,地址解引用得到身高数据
//在堆区开辟一块内存存储这个数据,返回的是一个指针
//再用this的m_Height去接收这个指针
//这样两个对象就不再指向一块地址
this->m_Height = new int(*p.m_Height);
}
~Person() {
//析构函数可以释放堆区开辟的数据
if (m_Height!=NULL){
delete m_Height;
m_Height = NULL;
}
cout << "Person默认析构函数调用" << endl;
}
};
void test01() {
Person p1(15,160);
//m_Height是一个指针,需要解引用
cout << "p1年龄:" << p1.m_Age << ",p1的身高:" << *p1.m_Height << endl;
Person p2(p1);
cout << "p2年龄:" << p2.m_Age << ",p2的身高:" << *p2.m_Height << endl;
}
int main() {
test01();
system("pause");
return 0;
}
初始化列表
就是给属性赋初值的一种方法,个人感觉传统的有参构造更好用
#include <iostream>
using namespace std;
/*初始化属性
构造函数():属性1(值1),属性2(值2)···{}*/
class Person {
public:
//传统初始化操作,利用有参构造赋初值
//Person(int a, int b, int c) {
// m_A = a;
// m_B = b;
// m_C = c;
//}
//初始化列表赋初值
Person(int a, int b, int c) :m_A(a), m_B(b), m_C(c) {}
//属性
int m_A;
int m_B;
int m_C;
};
void test01() {
Person p(10,20,30);
cout << "m_A:" << p.m_A << endl;
cout << "m_B:" << p.m_B << endl;
cout << "m_C:" << p.m_C << endl;
}
void test02() {
Person p(10, 20, 30);
cout << "m_A:" << p.m_A << endl;
cout << "m_B:" << p.m_B << endl;
cout << "m_C:" << p.m_C << endl;
}
int main() {
//test01();
test02();
system("pause");
return 0;
}
类作为成员对象,类中有类
结论:如果A类中由B类成员,则会先构造B,再构造A,析构时则相反
这里两个程序,主要展示了初始化成员列表以及构造函数调用法则。
- 初始化成员列表,没有用到Person默认构造
#include <iostream>
#include <string>
using namespace std;
//类对象作为类成员
//类A嵌套类B,先构造B,再构造A,析构时则相反。
class Phone {
public:
Phone() {
cout << "Phone默认构造" << endl;
}
Phone(string brand) {
cout << "phone有参构造" << endl;
m_Brand = brand;
}
~Phone() {
cout << "Phone析构函数调用" << endl;
}
//属性
string m_Brand;
};
class Person {
public:
//Person(string name, string brand) {
// cout << "Person有参构造函数调用" << endl;
// m_Phone = Phone(brand);
// m_Name = name;
//}
~Person() {
cout << "Person析构函数调用" << endl;
}
Person(string name, string brand):m_Name(name), m_Phone(brand){
cout << "Person参数列表初始化" << endl;
//原理如下
//m_Phone = brand;//赋值的对应关系
//Phone m_Phone = brand;//隐式构造法
//Phone m_Phone = Phone(brand);//显示法
//Phone m_Phone(brand);//括号法
}
//属性
string m_Name;
Phone m_Phone;
};
void test01() {
Person p("张三", "诺基亚");
cout << p.m_Name << "拿着" << p.m_Phone.m_Brand << endl;
}
int main() {
test01();
system("pause");
return 0;
}
- 不用初始化成员列表也能写,只用到了Phone的默认构造
#include <iostream>
#include <string>
using namespace std;
//类对象作为类成员
class Phone {
public:
Phone() {
cout << "Phone默认构造" << endl;
}
Phone(string brand) {
cout << "phone有参构造" << endl;
m_Brand = brand;
}
~Phone() {
cout << "Phone析构函数调用" << endl;
}
//属性
string m_Brand;
};
class Person {
public:
Person(string name, string brand){
cout << "Person有参构造函数调用" << endl;
m_Phone = Phone(brand);
m_Name = name;
}
~Person() {
cout << "Person析构函数调用" << endl;
}
//Person(string name, string brand):m_Name(name), m_Phone(brand){
// //原理如下
// //m_Phone = brand;//赋值的对应关系
// //Phone m_Phone = brand;//隐式构造法
// //Phone m_Phone = Phone(brand);//显示法
// //Phone m_Phone(brand);//括号法
//}
//属性
string m_Name;
Phone m_Phone;
};
void test01() {
Person p("张三", "水果");
cout << p.m_Name << "拿着" << p.m_Phone.m_Brand << endl;
}
int main() {
test01();
system("pause");
return 0;
}
静态成员,static
包括静态成员变量和静态成员函数
静态成员变量
- 所有对象共享一份数据
- 在编译阶段分配内存,全局区
- 类内声明,类外初始化
- 依然受到访问权限约束
#include <iostream>
using namespace std;
/*静态成员变量
1.所有对象共享一份数据
2.在编译阶段分配内存,全局区
3.类内声明,类外初始化
4.依然有访问权限*/
class Person {
public:
//静态变量,类内声明,
static int m_A;
};
//类外初始化,去掉static,加上作用域Person::
int Person:: m_A=100;
void test01() {
Person p;
cout << "p.m_A=:" << p.m_A << endl;
Person p2;
p2.m_A = 200;//所有对象共享一份数据
cout << "p.m_A=:" << p.m_A << endl;
//也能通过作用域访问
cout << "m.A=" << Person::m_A << endl;
}
int main() {
test01();
system("pause");
return 0;
}
静态成员函数
成员函数前缀statice
- 所有对象共享同一个函数
- 静态成员函数只能访问静态成员变量
- 也受到访问权限限制
#include <iostream>
using namespace std;
//静态成员函数,成员函数前缀statice
//1.所有对象共享同一个函数
//2.静态成员函数只能访问静态成员变量
//3.也受到访问权限限制
class Person {
public:
static void fun() {
m_A = 100;//因为静态成员函数是共享的,而非静态属于特定的对象
//m_B = 200;//静态成员函数只能访问静态成员变量
cout << "static void fun()调用" << endl;
}
//类内声明
static int m_A;
int m_B;
};
//类外初始化
int Person::m_A = 100;
void test01() {
//静态成员函数两种访问方式
//1.对象访问
Person p;
p.fun();
//2.类名访问
Person::fun();
}
int main() {
test01();
system("pause");
return 0;
}
成员变量和成员函数分开存储
- 空的类,就是{}里面啥也不写,实例化的对象占空间大小为1
- 实例化对象所占空间大小只和非静态成员变量有关,本例占8,是因为两个int
#include <iostream>
using namespace std;
//成员函数和成员变量分开存储
//空的类,就是{}里面啥也不写,实例化的对象占空间大小为1
//实例化对象所占空间大小只和非静态成员变量有关,本例占8,是因为两个int
class Person {
public:
int m_A = 1;//非静态成员变量,属于类对象
int m_b = 1;//非静态成员变量,属于类对象
static int m_B;//静态成员变量,不属于类对象
void func1() {}//非静态成员函数,不属于类对象
static void func2() {}//静态成员函数,不属于类对象
};
int Person::m_B = 1;
void test01() {
Person p;
//空对象占用内存空间大小为1,且每个空对象内存空间是独一无二的
cout << "p占用内存空间:" << sizeof(p) << endl;
}
int main() {
test01();
system("pause");
return 0;
}
this指针
谁调用就指向谁
- 当形参变量和成员变量重名时,用this加以区分,最好还是做好命名规范,别给自己找麻烦
- 在类的非静态成员函数中返回对象本身,可用return *this,且函数返回值类型是类名&,也就是引用的方式
- 如果2中没有引用的方式返回,则发生了拷贝,返回的不是自身
#include <iostream>
using namespace std;
//1.解决名称冲突,最好做好命名规范,方便区分
//2.返回对象本身
/*!!注意,例子里面有个Person &,简单理解就是,return * 返回的是一个自身一样的类,
加&,就是更新了原件的值,后面继续对原件操作,没有&就是复制了一份,后面就是对副本操作,而不是原件*/
class Person {
public:
Person(int age) {
//this指针用来区分成员变量和形参
this->age = age;
}
//这种写法,只能加一次
//void PersonAddAge(Person& p) {
// this->age += p.age;
//}
//返回的是自身,Person后面如果不加&,则会调用拷贝构造,返回的不再是自身
Person & PersonAddAge(Person& p) {
this->age += p.age;
return *this;//this是个指针,*this解引用
}
int age;
};
void test01() {
Person p1(1);
cout << "p1年龄:" << p1.age << endl;
Person p2(1);
cout << "p2年龄:" << p2.age << endl;
//把p1的年龄加到p2,加一次可以成功,但是多加几次就不行了
//如果p2.PersonAddAge(p1)返回的还是p2,就能一直加
p2.PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1);
cout << "p2年龄:" << p2.age << endl;
}
int main() {
test01();
system("pause");
return 0;
}
空指针访问成员函数
如果定义了一个类的空指针,用空指针调用成员函数可能出现报错。为了防止成员崩溃,应防止传入空指针。if (this == NULL) return;//防止传入的是空指针
#include <iostream>
using namespace std;
//防止传入空指针
class Person {
public:
void showClassName() {
cout << "Person类" << endl;
}
void showPersonAge() {
if (this == NULL) return;//防止传入的是空指针
cout << "age:" << this->m_Age << endl;
}
int m_Age;
};
void test01() {
Person* p = NULL;
p->showPersonAge();//传入的指针是NULL
p->showClassName();
}
int main() {
test01();
system("pause");
return 0;
}
const修饰成员函数,常函数
- 常函数 函数名() const {}
- 常函数中不能修改成员变量的值
- 如果要改值,在定义成员变量时,前缀关键字mutable
- 实例化对象时前缀const,常对象,只能调用常函数
- 同样的,常对象只能修改mutable修饰的成员变量
#include <iostream>
using namespace std;
//常函数 函数名() const {}
//常函数中不能修改成员变量的值
//如果要改值,在值定义时,前缀关键字mutable
class Person {
public:
//加cosnt防止属性被修改
//this的本质是指针常量,*+const,指向不能改,但是值能改
//相当于Person *const this,前缀const,值也不能改了
void showPerson()const{
//this = NULL;//指针指向不能修改
//this->m_A = 100;//加了const,指针指向的值也不能改了
this->m_B = 200;//该变量受到mutable作用可以改
}
void fun() {}//不是常函数
int m_A;
mutable int m_B;//特殊变量,即使在常函数中,该值也能修改
};
//常对象
void test01() {
const Person p;
//p.m_A = 100;//m_A值不可以修改
p.m_B = 20;
p.showPerson();//常对象只能调用常函数
//p.fun();//常对象不可调用非常函数
Person p2;
p2.fun();
p2.m_A = 100;
//P2.m_B = 20;//常量值依然不能改,其他不受限制
p2.showPerson();
}
int main() {
test01();
system("pause");
return 0;
}
友元
类里面的访问权限中,private作用下的成员变量是类外访问不到的。但是,如果是友元就可以给他访问权限,有以下两种情况:
全局函数作友元
你有一个建筑类,里面有客厅和卧室,普通客人只能进客厅,不能访问卧室。但是你可以让好朋友进去,好朋友全局函数就是你建筑类的友元。
- 在类里面声明全局函数,且前缀friend,不用加public作用域
#include <iostream>
using namespace std;
//类作友元、成员函数作友元、类外定义成员函数
class Building;//先声明有这样一个类
class Goodgay {
public:
//构造函数
Goodgay();
//加一个Goodgay的析构函数
~Goodgay();
void visit();//参观函数,访问Building中的属性
Building* m_building;
};
class Building {
//包含友元类
friend class Goodgay;
//如果只要赋予Goodgay中的某个成员函数访问权限,则
friend void Goodgay::visit();
//无参构造声明
Building();
//析构声明
~Building();
public:
string m_SettingRoom;
private:
string m_BedRoom;
};
//类外定义所有成员函数
Goodgay::Goodgay() {
cout << "Goodgay构造函数" << endl;
//new一个建筑物,返回指针,用成员变量m_building去接收
m_building = new Building;
}
//自己加了段析构函数
Goodgay::~Goodgay() {
cout << "Goodgay析构函数" << endl;
if (m_building != NULL) {
delete m_building;
m_building = NULL;
}
}
void Goodgay::visit() {
cout << "好基友类正在访问" << m_building->m_BedRoom << endl;
cout << "好基友类正在访问" << m_building->m_SettingRoom << endl;
}
//Building无参构造赋初值
Building::Building() {
cout << "Building构造函数" << endl;
m_BedRoom = "卧室";
m_SettingRoom = "客厅";
}
Building::~Building() {
cout << "Building析构函数" << endl;
}
//开始调用
void test01() {
//创建gg对象,会调用Goodgay的构造函数
//Goodgay的构造函数里面new了一个Building对象
//又会调用Building的构造函数,赋初值
Goodgay gg;
//gg对象调用visit成员函数
gg.visit();
}
int main() {
test01();
system("pause");
return 0;
}
- 根据运行结果显示,先构造了goodgay,再构造了building;释放时也是先释放的goodgay,再释放的building
- 栈的数据是先进后出的,这里开辟了堆区数据,所以产生了差异
类和成员函数作友元
- 让一个类可以访问另一个类中的私有属性
- 让一个类中的单个成员函数可以访问另一个类中私有属性
- 成员函数的特殊写法:类内声明,类外加作用域实现。
- 特别注意这种嵌套类,声明定义时候的先后顺序
#include <iostream>
using namespace std;
//类作友元、成员函数作友元、类外定义成员函数
class Building;//先声明有这样一个类
class Goodgay {
public:
//构造函数
Goodgay();
void visit();//参观函数,访问Building中的属性
Building* m_building;
};
class Building {
//包含友元类
friend class Goodgay;
//如果只要赋予Goodgay中的某个成员函数访问权限,则
friend void Goodgay::visit();
//无参构造声明
Building();
public:
string m_SettingRoom;
private:
string m_BedRoom;
};
//类外定义所有成员函数
Goodgay::Goodgay() {
//new一个建筑物,返回指针,用成员变量m_building去接收
m_building = new Building;
}
void Goodgay::visit() {
cout << "好基友类正在访问" << m_building->m_BedRoom << endl;
cout << "好基友类正在访问" << m_building->m_SettingRoom << endl;
}
//Building无参构造赋初值
Building::Building() {
m_BedRoom = "卧室";
m_SettingRoom = "客厅";
}
//开始调用
void test01() {
//创建gg对象,会调用Goodgay的构造函数
//Goodgay的构造函数里面new了一个Building对象
//又会调用Building的构造函数,赋初值
Goodgay gg;
//gg对象调用visit成员函数
gg.visit();
}
int main() {
test01();
system("pause");
return 0;
}
运算符重载
重新定义运算符,进行自定义的数据类型的运算
加号运算符重载
比如两个类相加,普通的"+"是无法实现的。可行的做法是,在类里面写一个成员函数,传入一个类,实现类间相加。现在编译器给这个成员函数取了一个名字,叫operator+,当然,而也可以通过全局函数实现加号运算符重载。
#include <iostream>
using namespace std;
//加号运算符重载
//1.通过成员函数
//2.通过全局函数
//实质就是定义了名为operator+的函数,调用时可以按函数来调用,
//也可以直接+
class Person {
public:
Person() {
//这是Person的无参构造,如果不提供,会找不到合适的默认构造
//如果只是声明,不写大括号也不行
}
Person(int a,int b) {
m_A = a;
m_B = b;
}
//成员函数实现加号运算符重载
Person operator+ (Person &p) {
Person temp;
temp.m_A = this->m_A + p.m_A;
temp.m_B = this->m_B + p.m_B;
return temp;
}
int m_A;
int m_B;
};
//全局函数实现运算符重载
//Person operator+(Person& p1, Person& p2) {
// Person temp;
// temp.m_A = p1.m_A + p2.m_A;
// temp.m_B = p1.m_B + p2.m_B;
// return temp;
//}
void test01() {
Person p1(10, 20);
Person p2(30, 40);
//成员函数加号运算符重载实质上是这样
//Person p3 = p1.operator+(p2);
//全局函数运算符重载实质
//Person p5 = operator+(p1, p2);
//以上两种方式选一个,同时写会冲突
//简写就直接加
Person p4 = p1 + p2;
cout << "p4.m_A=" << p4.m_A << endl;
cout << "p4.m_B=" << p4.m_B << endl;
//cout << "p5.m_A=" << p5.m_A << endl;
//cout << "p5.m_B=" << p5.m_B << endl;
}
int main() {
test01();
system("pause");
return 0;
}
左移运算符重载
- 左移运算符重载只能利用全局函数实现
- cout,右击定义,属于ostream,标准的输出流类
#include <iostream>
using namespace std;
//左移运算符重载 operator<<
//只能利用全局函数实现
class Person {
public:
int m_A;
int m_B;
};
//转到cout的定义,知其属于ostream,标准输出流类
//cout在全局中只能有一个,所以要引用
ostream& operator<<(ostream &cout,Person &p){
//这里如果是void不返回,加上endl会报错,所以要返回cout,
cout << "p.m_A=" << p.m_A <<"\tp.m_B="<<p.m_B<<endl;
return cout;//这样每次调用返回的都是cout
}
void test01() {
Person p;
p.m_A = 1;
p.m_B = 2;
cout << p << endl;
}
int main() {
test01();
system("pause");
return 0;
}
递增运算符重载
- 如果一个在类内,一个在内外,占位参数无法区分
- 前置递增返回的是引用,而后置递增是值,因为后置递增,如果对象被销毁,会出现非法访问的情况,只能返回值
- cout那里第二个形参不能加引用,否则后置递增报错,原因未知
#include <iostream>
using namespace std;
class MyInt {
public:
//构造函数,初始化成员变量为0
MyInt() {
m_Int = 0;
}
//前置递增
//引用,保证以后操作的都是一个数据,而不是拷贝的数据
MyInt& operator++() {
this->m_Int++;//先加1
return *this;//再返回自身
}
//后置递增
//加个占位参数,实现函数重载,区分前置和后置
MyInt operator++(int) {
//先返回结果,但不能直接return,先记录一下
MyInt temp = *this;
//后递增
this->m_Int++;
return temp;//再return存储的值
}
int m_Int;
};
//注意这里的第二个参数不是引用,如果引用,再后置递增出问题
ostream& operator<<(ostream& cout, MyInt num) {
cout << num.m_Int << endl;
return cout;
}
void test01() {
MyInt p;
p.m_Int = 0;
cout << p;
cout << ++p;
cout << p;
}
void test02() {
MyInt myint;
cout << myint;
//先运算再加1,这里的运算就是参与输出,直接把myint输出了
cout << (myint++);
cout << myint;
}
int main() {
test01();
test02();
system("pause");
return 0;
}
赋值运算符重载
堆区的数据手动开辟,手动释放。编译器提供的直接等号赋值操作,是浅拷贝,对于堆区数据利用浅拷贝,会出现堆区内存重复释放的问题,因此要用深拷贝,重新开辟一块空间,不再指向同一块内存。
- 两块堆区数据,再赋值前,先将被复制的那一块进行了释放操作。
- 连等操作要返回自身
#include <iostream>
using namespace std;
//赋值运算符重载,主要利用深拷贝
class Person {
public:
Person(int age) {
//把年龄数据开辟到堆区,用指针接收
m_Age = new int(age);
}
//如果返回的是void,则不能实现连等操作
//必须返回自身
Person& operator=(Person &p) {
//这种直接等于就是浅拷贝,
//this->m_Age = p.m_Age;
//先判断自己是否有堆区数据,如果有就先释放,再存入
if (m_Age!=NULL){
delete m_Age;
m_Age = NULL;
}
m_Age = new int(*p.m_Age);//深拷贝,两个m_Age不指向同一块内存
return *this;
}
~Person() {
//堆区数据手动开辟,手动释放
if (m_Age != NULL) {
delete m_Age;
m_Age = NULL;
}
}
int* m_Age;
};
void test01() {
Person p1(18);
cout << "p1的年龄:" << *p1.m_Age << endl;
Person p2(20);
cout << "p2的年龄:" << *p2.m_Age << endl;
p1 = p2;
cout << "p1的年龄:" << *p1.m_Age << endl;
cout << "p1的年龄:" << *p1.m_Age << endl;
Person p3(30);
p1 = p2 = p3;
cout << "p1的年龄:" << *p1.m_Age << endl;
cout << "p2的年龄:" << *p2.m_Age << endl;
cout << "p3的年龄:" << *p3.m_Age << endl;
}
int main() {
test01();
system("pause");
return 0;
}
函数调用运算符重载 仿函数
对()重载,使用起来像是函数调用,故名仿函数,仿函数使用灵活,可以通过对象调用,也可以通过类名加括号调用,这种调用方式也被称为匿名对象。
#include <iostream>
#include <string>
using namespace std;
//使用时非常像函数,因此也叫仿函数
//仿函数使用非常灵活,没有固定写法
class myAdd{
public:
//重载函数调用运算符
void operator()(int num1, int num2) {
cout << num1 + num2 << endl;
}
};
class myCout {
public:
void operator()(string str) {
cout << str << endl;
}
};
void test01() {
//实例化对象之后调用
myAdd myadd;
myadd(100, 100);
//匿名对象直接调用
myAdd()(20, 20);
}
void test02() {
myCout mycout;
mycout("hello world!");
}
int main() {
test02();
//test01();
system("pause");
return 0;
}
继承
继承的作用是可以省去很多重复代码
基本语法
- class 子类:继承方式 父类
- 子类也叫派生类,父类也叫基类
- 先构造父类,再构造子类;析构时顺序相反
下面有个案例,小白和小黑是兄弟,每天一起吃饭睡觉。但是小白喜欢唱歌,小黑喜欢跳舞。吃饭睡觉是两人共有的,作为父类。每个人有自己的喜好,独立完成,作为子类
#include <iostream>
using namespace std;
//继承可以节省很多重复代码的写法
//语法:class 子类:继承方式 父类
//子类 也叫派生类
//父类 也叫基类
//举个例子,小白和小黑是兄弟,每天一起吃饭睡觉。
//但是小白喜欢唱歌,小黑喜欢跳舞
//吃饭睡觉是两人共有的,作为父类
//每个人有自己的喜好,独立完成,作为子类
//先构造父类,再构造子类,析构时顺序相反
//创建一个父类
class Base {
public:
Base() {
cout << "父类构造" << endl;
}
~Base() {
cout << "父类析构" << endl;
}
void eat() {
cout << "吃饭" << endl;
}
void sleep() {
cout << "睡觉" << endl;
}
};
//创建一个小白类,以公有的方式继承父类中的类容
class Bai :public Base {
public:
Bai() {
cout << "子类构造" << endl;
}
~Bai() {
cout << "子类析构" << endl;
}
void sing() {
cout << "唱歌" << endl;
}
};
//创建一个小黑类,以公有的方式继承父类中的类容
class Hei :public Base {
public:
void dance() {
cout << "跳舞" << endl;
}
};
void test01() {
//实例化对象,调用
Bai bai;
bai.eat();
bai.sing();
bai.sleep();
//Hei hei;
//hei.eat();
//hei.dance();
//hei.sleep();
}
int main () {
test01();
system("pause");
return 0;
}
继承方式
如下表所示:横向为父类中属性的访问权限,纵向为子类的继承方式,表格中为子类继承后属性的访问权限。
继承方式 \父类 | 公有 | 保护 | 私有 |
---|---|---|---|
公有 | 公有 | 保护 | 无权访问 |
保护 | 保护 | 保护 | 无权访问 |
私有 | 私有 | 私有 | 无权访问 |
父类中的元素,有三种访问权限:公有、保护、私有
子类的继承方式也有三种:公有、保护、私有
- 父类中的私有内容,无论那种继承方式,子类都访问不到
- 公有继承,父类 公有和保护 子类 仍保持原访问权限
- 保护继承,父类 共有和保护 子类 全变成保护
- 私有继承、父类 公有和保护 子类 全变成私有
#include <iostream>
using namespace std;
//父类中的元素,有三种访问权限:公有、保护、私有
//子类的继承方式也有三种:公有、保护、私有
//父类中的私有内容,无论那种继承方式,子类都访问不到
//公有继承,父类 公有和保护 子类 仍保持原访问权限
//保护继承,父类 共有和保护 子类 全变成保护
//私有继承 父类 公有和保护 子类 全变成私有
class A {
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
class B1 :public A {
//不能直接赋值,得通过函数
void fun() {
m_A = 100;//公有
m_B = 100;//保护,类内可以访问
//m_C = 100;//无权访问
}
};
class B2 :protected A {
void fun() {
m_A = 100;//保护,类内可以访问
m_B = 100;//保护,类内可以访问
}
};
class B3 :private A {
void fun() {
m_A = 100;//私有,类内可以访问
m_B = 100;//私有,类内可以访问
}
};
class C :public B3 {
void fun() {
//m_A = 100;//无权访问
//m_B = 100;//无权访问
}
};
void test01() {
B1 b1;
b1.m_A = 100;//只能访问b1
B2 b2;
B3 b3;
}
int main() {
system("pause");
return 0;
}
继承中的对象模型
- 父类中所有非静态成员都被子类继承下来,
- 父类中的私有属性,子类访问不到,但是也被继承了下来
- 利用vs自带工具查看类
#include <iostream>
using namespace std;
//父类中所有非静态成员都被子类继承下来,
//父类中的私有属性,子类访问不到,但是也被继承了下来
//利用开发人员命令提示工具查看对象模型
//跳转盘符 E:
//跳转到文件路径 cd 文件路径
//查看 cl /d1 reportSingleClassLayout类名 "c27 继承中的对象模型.cpp"
//注意 第一个是字母l,第二个是数字1 报告单个类布局类名 加具体文件名,按Tab补齐
class A {
int m_A;
public:
int m_B;
protected:
int m_C;
};
class B :public A {
public:
int m_D;
};
void test01() {
B b;
cout << "b占用和内存:" << sizeof(b) << endl;
}
int main() {
test01();
system("pause");
return 0;
}
工具截图,类B占16字节,父类是A,继承了m_A、m_B、m_C,定义了m_D
同名成员
如果子类和父类中有同名成员,包括变量和函数
- 子类对象可以直接访问子类中同名成员
- 子类对象加父类作用域可以访问到父类同名成员
同名静态成员
至于非静态成员,既可以通过对象调用,也可以类名的方式访问。在通过类名的方式访问的时候,子类名::父类名::同名静态成员
多继承
一个子类可以继承多个父类,但是可能引起重名问题,需通过作用域加以区分。不推荐使用这种方式。
class 子类:继承方式 父类1,继承方式 父类2···{};
菱形继承与虚继承
一个父类派生两个子类,又有一个(孙子)类同时继承了这两个子类,就是菱形继承,也叫钻石继承。
- 如果两个子类有同名成员,可以通过作用域区分,但是也造成了无意义的资源浪费
- 利用虚继承可以解决1的问题,在继承方式和父类名之间加关键字virtual
#include <iostream>
using namespace std;
//虚继承实质是一个虚基类指针vbptr
//基类 动物类,虚继承之后就是虚基类
//
class Animal{
public:
int m_Age;
};
//在继承方式和父类名之间加关键字virtual
//子类1 🐏
class Sheep :public virtual Animal {};
//子类2 驼
class Tuo :public virtual Animal {};
//孙子类同时继承了两个子类
class SheepTuo :public Sheep, public Tuo {};
void test01() {
SheepTuo st;
//不采用虚继承,就继承了两份,且访问时要加作用域
//st.Sheep::m_Age = 8;
//st.Tuo::m_Age = 10;
//cout << "st.Sheep::m_Age:" << st.Sheep::m_Age << endl;
//cout << "st.Tuo::m_Age:" << st.Tuo::m_Age << endl;
//加了虚继承之后,就只剩一份数据了,不要作用域就可以访问
st.m_Age = 6;
cout << "st.m_Age:" << st.m_Age << endl;
}
int main() {
test01();
system("pause");
return 0;
}
不用虚继承
虚基类指针,通过指针指向数据
多态
多态是c++面向对象三大特性之一。
多态分为两类:
- 静态多态:函数重载和运算符重载,
- 动态多态:派生类和虚函数实现运行时多态
两类多态区别: - 静态多态:函数地址早绑定,编译阶段确定函数地址
- 动态多态:函数地址晚绑定,运行阶段确定函数地址
动态多态
动态多态条件:
- 有继承关系
- 子类重写父类虚函数,重写不是重载,重写是函数返回值类型 函数名 参数列表完全相同
一个案例:
父类是动物类,猫狗是子类,有同名函数。有一个函数,父类指针指向子类对象,传入猫狗,就运行猫狗的函数,而不是父类中的函数
#include <iostream>
using namespace std;
//父类是动物类,猫狗是子类,有同名函数
//一个函数,父类指针指向子类对象,传入猫狗,就运行猫狗的函数
class Animal {
public:
//加了virtual就是虚函数,地址晚绑定,传谁就是谁说话
virtual void speak() {
cout << "动物在说话" << endl;
}
};
class Cat:public Animal{
public:
//Cat 里面有个同名函数
//子类重写父类虚函数,函数返回值类型 函数名 参数列表完全相同
void speak() {
cout << "猫在说话" << endl;
}
};
class Dog :public Animal {
public:
//Dog 里面有个同名函数
void speak() {
cout << "狗在说话" << endl;
}
};
//执行说话的函数,要求传入动物
//Animal &animal=cat;
//父类引用指向子类对象,运行父子之间的类型转换
void doSpeak(Animal &animal) {
//父类中没有虚函数,就是地址早绑定,不管传猫传狗,最后都是调用的animal里的speak
animal.speak();
}
void test01() {
Cat cat;
//cat.speak();
//cat.Animal::speak();
doSpeak(cat);
Dog dog;
doSpeak(dog);
}
int main() {
test01();
system("pause");
return 0;
}
案例 多态计算机
普通的计算器写法和多态计算器写法
多态调用:父类指针或者引用指向子类对象
#include <iostream>
using namespace std;
//分别用普通写法和多态实现计算器
//开发中讲求开闭原则:对扩展开放,对修改关闭
//普通写法
class Calculator {
public:
int getResult(char oper) {
switch (oper)
{
case '+':
return m_Num1 + m_Num2;
break;
case '-':
return m_Num1 - m_Num2;
break;
case '*':
return m_Num1 * m_Num2;
break;
case '/':
return m_Num1 / m_Num2;
break;
default:
break;
}
}
//两个操作数
int m_Num1;
int m_Num2;
//操作符
char oper;
};
//2.多态写法
class Base_JiSuanQi {
public:
//多态不是这样写的,都不用传oper
//virtual void getResult(char oper) {
// return m_Num1 oper m_Num2;
//}
virtual int getResult() {
return 0;//先输出一个0
}
//两个操作数
int m_Num1;
int m_Num2;
};
//专门的加法类
class son_Add :public Base_JiSuanQi {
public:
//子类重写父类虚函数,关键字virtual可以不要
int getResult() {
return m_Num1+m_Num2;
}
};
//专门的减法类
class son_Sub :public Base_JiSuanQi {
public:
//子类重写父类虚函数,关键字virtual可以不要
int getResult() {
return m_Num1 - m_Num2;
}
};
//专门的乘法类
class son_Mul :public Base_JiSuanQi {
public:
//子类重写父类虚函数,关键字virtual可以不要
int getResult() {
return m_Num1 * m_Num2;
}
};
//专门的除法类
class son_Div :public Base_JiSuanQi {
public:
//子类重写父类虚函数,关键字virtual可以不要
int getResult() {
return m_Num1 / m_Num2;
}
};
void test01() {
cout << "**计算器普通写法**" << endl;
Calculator c;
c.m_Num1 = 10;
c.m_Num2 = 10;
cout << c.m_Num1 << "+" << c.m_Num2 << "=" << c.getResult('+') << endl;
cout << c.m_Num1 << "-" << c.m_Num2 << "=" << c.getResult('-') << endl;
cout << c.m_Num1 << "*" << c.m_Num2 << "=" << c.getResult('*') << endl;
cout << c.m_Num1 << "/" << c.m_Num2 << "=" << c.getResult('/') << endl;
}
void test02() {
cout << "**多态计算器**" << endl;
//多态使用条件:
//父类指针或引用指向子类对象
//引用的方式
/*son_Add son;
Base_JiSuanQi& js = son;
js.m_Num1 = 10;
js.m_Num2 = 10;
cout << js.m_Num1 << "+" << js.m_Num2 << "=" << js.getResult() << endl;*/
//父类指针指向子类对象
Base_JiSuanQi* js = new son_Add;
js->m_Num1 = 10;
js->m_Num2 = 10;
cout << js->m_Num1 << "+" << js->m_Num2 << "=" << js->getResult() << endl;
//用完销毁
delete js;
//再指向减法
js = new son_Sub;
//数据被清空,重新赋值
js->m_Num1 = 10;
js->m_Num2 = 10;
cout << js->m_Num1 << "-" << js->m_Num2 << "=" << js->getResult() << endl;
delete js;
//再指向乘法
js = new son_Mul;
//数据被清空,重新赋值
js->m_Num1 = 10;
js->m_Num2 = 10;
cout << js->m_Num1 << "*" << js->m_Num2 << "=" << js->getResult() << endl;
delete js;
//再指向除法
js = new son_Div;
//数据被清空,重新赋值
js->m_Num1 = 10;
js->m_Num2 = 10;
cout << js->m_Num1 << "/" << js->m_Num2 << "=" << js->getResult() << endl;
delete js;
}
int main() {
//test01();
test02();
system("pause");
return 0;
}
纯虚函数和多态类 虚析构与纯虚析构
因为父类中的虚函数实现毫无意义,基本用不到,主要调用的是子类中的内容。因此可以将虚函数写为纯虚函数。
纯虚函数语法:
virtual 返回值类型 函数名 (参数列表)= 0;
当函数中有了纯虚函数,这个类也叫抽象类
抽象类特点:
- 无法实例化对象
- 子类必须重写抽象类中的纯虚函数,否则也属于抽象类
虚析构与纯虚析构
虚析构稍微方便一点
- 如果子类有数据开辟到堆区,父类的指针无法释放子类对象,解决办法是在父类析构函数前缀virtual。
- 纯虚析构在父类中声明之后,类外要实现。且纯虚析构,类也是抽象类
- 二者只能有一个
#include <iostream>
using namespace std;
#include <string>
//因为父类中虚函数用不到,直接写成纯虚函数
class baseAnimal {
public:
baseAnimal() {
cout << "baseAnimal构造函数" << endl;
}
//虚析构,如果没有虚析构,不会执行~Cat
//virtual ~baseAnimal() {
// cout << "baseAnimal析构函数" << endl;
//}
//纯虚析构声明
virtual ~baseAnimal() = 0;
virtual void speak() = 0;
};
//纯虚析构也要实现,防止父类也有对象开辟到堆区
baseAnimal::~baseAnimal() {
cout << "baseAnimal纯虚析构函数" << endl;
}
class Cat :public baseAnimal {
public:
Cat(string name) {
cout << "Cat构造函数" << endl;
m_Name = new string(name);
}
~Cat() {
//因为Cat里面开辟了堆区数据,所以要手动释放
cout << "Cat析构函数" << endl;
if (m_Name != NULL) {
delete m_Name;
m_Name = NULL;
}
}
//Cat 里面有个同名函数
void speak() {
cout <<*m_Name<< "猫在说话" << endl;
}
string* m_Name;
};
class Dog :public baseAnimal {
public:
//Dog 里面有个同名函数
void speak() {
cout << "狗在说话" << endl;
}
};
void doSpeak(baseAnimal* animal) {
animal->speak();
delete animal;
}
void test01() {
doSpeak(new Cat("Tom"));
//Animal animal;//抽象类无法实例化对象,堆区栈区都不行
//new Animal;
//doSpeak(animal);
}
int main() {
test01();
system("pause");
return 0;
}
案例 制作饮品 父类指针或引用指向子类对象
主要展示了纯虚函数,以及父类指针或者引用指向子类对象的具体实现方式
#include <iostream>
using namespace std;
//一个制作饮品的案例
//父类指针或者引用指向子类对象
class baseDrink {
public:
//一系列纯虚函数
//烧水
virtual void boilWater() = 0;
//冲泡
virtual void brew() = 0;
//倒入杯中
virtual void pour() = 0;
//加入辅料
virtual void add() = 0;
//整合一下,就不用虚函数了
void makeDrink() {
boilWater();
brew();
pour();
add();
}
};
//泡茶
class Tea:public baseDrink {
public:
//重写纯虚函数
void boilWater() {
cout << "第一步:烧水" << endl;
}
void brew() {
cout << "第二步:冲泡茶叶" << endl;
}
void pour() {
cout << "第三步:倒入杯中" << endl;
}
void add() {
cout << "第四步:加点枸杞" << endl;
}
};
//泡咖啡
class Coffee :public baseDrink {
public:
void boilWater() {
cout << "第一步:烧水" << endl;
}
void brew() {
cout << "第二步:冲泡咖啡" << endl;
}
void pour() {
cout << "第三步:倒入杯中" << endl;
}
void add() {
cout << "第四步:加点牛奶糖" << endl;
}
};
//1.父类指针指向子类对象
void doMakeDrink(baseDrink* bd) {
bd->makeDrink();
delete bd;//调用完就删除堆区数据
}
//2.父类引用指向子类对象
void doMakeDrink(baseDrink& bd) {
bd.makeDrink();
}
void test01() {
//相当于:baseDrink* bd=new Tea;
doMakeDrink(new Tea);
cout << "----------------" << endl;
doMakeDrink(new Coffee);
cout << "----------------" << endl;
//相当于:baseDrink& bd=tea;
Tea tea;
doMakeDrink(tea);
cout << "----------------" << endl;
Coffee coffee;
doMakeDrink(coffee);
cout << "----------------" << endl;
}
int main() {
test01();
system("pause");
return 0;
}
案例 组装电脑
#include <iostream>
#include <string>
using namespace std;
//一台电脑有cpu,显卡 内存 三个零件
//创建三个零件对象,再分别创建不同厂商生成零件的子类对象
//组装电脑
/// <summary>
/// 注意,computer不是子类,里面new的对象,只要在其析构函数中释放即可,不需要父类虚析构函数。
/// </summary>
class CPU {
public:
//抽象计算函数
virtual void calculate() = 0;
};
class VideoCard{
public:
//抽象显示函数
virtual void display() = 0;
};
class Memory {
public:
//抽象存储函数
virtual void storage() = 0;
};
class Computer {
public:
Computer(CPU* cpu,VideoCard* vc,Memory* mem) {
m_cpu = cpu;
m_vc = vc;
m_mem = mem;
}
//提供工作的函数
void doWork() {
m_cpu->calculate();
m_vc->display();
m_mem->storage();
}
//三个零件的指针
CPU* m_cpu;
VideoCard* m_vc;
Memory* m_mem;
~Computer() {
if (m_cpu!=NULL)
{
delete m_cpu;
m_cpu = NULL;
}
if (m_vc != NULL)
{
delete m_vc;
m_vc = NULL;
}
if (m_mem != NULL)
{
delete m_mem;
m_mem = NULL;
}
}
};
//具体的零件厂商
class InterCpu :public CPU {
public:
void calculate() {
cout << "InterCpu正在计算" << endl;
}
};
class InterVideoCard :public VideoCard {
public:
void display() {
cout << "Inter显卡正在显示" << endl;
}
};
class InterMemory :public Memory {
public:
void storage() {
cout << "Inter内存正在存储" << endl;
}
};
class LenovoCpu :public CPU {
public:
void calculate() {
cout << "LenovoCpu正在计算" << endl;
}
};
class LenovoVideoCard :public VideoCard {
public:
void display() {
cout << "Lenovo显卡正在显示" << endl;
}
};
class LenovoMemory :public Memory {
public:
void storage() {
cout << "Lenovo内存正在存储" << endl;
}
};
void test01() {
//开始电脑组装
//第一台电脑零件
CPU* intercpu = new InterCpu;
VideoCard* intercard = new InterVideoCard;
Memory* intermem = new InterMemory;
//new第一台电脑,放在堆区
Computer* pc1 = new Computer(intercpu, intercard, intermem);
pc1->doWork();
delete pc1;
cout << "--------------------" << endl;
//创建第二台电脑
Computer* pc2 = new Computer(new LenovoCpu, new LenovoVideoCard, new LenovoMemory);
pc2->doWork();
delete pc2;
cout << "--------------------" << endl;
//创建第三台电脑
Computer* pc3 = new Computer(new InterCpu, new LenovoVideoCard, new LenovoMemory);
pc3->doWork();
delete pc3;
}
int main() {
test01();
system("pause");
return 0;
}