面向对象
- 1.面向对象编程(难点)
- 2.类和对象
- demo1:地主类的实现版本1
- demo2:地主类的实现版本2
- 3.访问修饰符
- demo3:外部修改成员变量不安全(版本3)
- demo4: 使用封装防止直接修改成员变量(版本3)
- demo5:进一步封装:设置/获取名字,修改积分(版本4)
- 4.构造函数与析构函数(重点)
- 4.1默认构造函数
- demo6: 使用构造函数进行成员变量的初始化
- 4.2 带参构造函数
- demo7:学生类构建--堆内存对象/栈内存对象
- 4.3 析构函数
- demo8:学生类析构函数演示
- 5.this指针(重点)
- demo9:学霸返回引用的使用
《老九学堂C++课程》《C++ primer》学习笔记。《老九学堂C++课程》详情请到B站搜索《老九零基础学编程C++入门》
-------------简单的事情重复做,重复的事情用心做,用心的事情坚持做(老九君)---------------
1.面向对象编程(难点)
oop: object oriented programming
何为面向对象:基于对象的概念,以对象为中心,以类和继承为构造机制,来认识、理解、刻画客观世界;涉及构建相应的软件系统(模拟现实)
1.对象–有数据和容许的操作组成的封装体,与客观实体有直接的对应关系(属性和方法的集合)
面向对象不是某一种语言的特性,而是一种编程思想。原来面向过程代码超过10W行就会难管理,原来有因为飞机控制程序中一个,写成.号造成的空难。
举个粒子:斗地主游戏的开发
面向过程:一步一步来,很多很多的过程函数:开始游戏-洗牌-发牌-显示手牌…-输出结果
面向对象:
1.游戏参与者,行为模式是相同的–玩家对象,相同的属性和行为;
2.进行游戏的场景–牌桌对象,负责现实游戏的界面及内容;
3.游戏规则系统–裁判对象,负责判定牌面、输赢;
小结:
1.面向过程编程,首先考虑要遵循的步骤,然后考虑如何表示这些数据
2.oop编程,首先会考虑数据,包括数据的表示和数据的使用
2.类和对象
面向对象的编程流程:
1.抽象:从具体食物抽取共同的本质特征,【处理复杂问题的技巧–简化、抽象】
地主对象:外表特征–胖,留两撇胡子,两颗大金牙;行为特点–先出牌,多摸三张牌
2.用类封装:将抽象转换为用户定义类型的工具,将数据表示和操作数据的方法组合成一个整体。类的实例成为对象(对象的集合就是类,还可以这么理解:类就是对象模版),类中的变量和函数称为成员。
地主类:
成员变量:名称、积分、手牌
成员函数:摸牌、出牌、产看积分
类的声明:使用class/struct 关键字声明,
两者的区别:使用class声明的类默认成员是私有的(private),struct声明的类默认成员是共有的(public)。
推荐使用class声明类;struct声明结构,只包含数据POD,老式数据
class 类名{};
struct 类名{};
头文件中声明类(.h/.cpp),专门有一个类名.cpp文件实现类。
demo1:地主类的实现版本1
–LandOwnerV1.cpp 文件中既声明又实现,包含main 函数的main.cpp文件中调用
// mian.cpp 文件
#include "LandOwnerV1.cpp"
int main(){// 类,对象实验LandOwnerV1 landOwner1; // 声明了一个LandOwner1类型的变量landowner1// 调用对象的成员方法, 不能直接使用对象的私有成员// landOwner1.cards[0] = 0; 'cards' is a private member of 'LandOwnerV1' ,直接报错landOwner1.TouchCard(100);return 0;
}
// LandOwnerV1.cpp 文件既声明又实现
#include <iostream>
using namespace std;
// .hpp 一般包含实现的内联函数,通常用于模版类这种声明与实现共存的情况
// 建议:只要不是纯模版,一律使用.h 作为头文件后缀, .cpp 作为函数的实现文件
// 地主类的声明、实现
class LandOwnerV1 {private:string name; // 名称long score; // 积分int cards[20]; // 手牌数组public :LandOwnerV1() {}; // 默认构造函数~LandOwnerV1() {}; // 默认析构函数void TouchCard(int CardCount){// 暂时省略函数实现cout << name << "摸了" << CardCount << "张牌" << endl;}void ShowScore(){cout << name << "当前的积分为:" << score << endl;}};
输出
摸了100张牌
demo2:地主类的实现版本2
–LandOwnerV2.h中声明,LandOwnerV2.cpp中实现,main.cpp文件中调用
成员函数没有调用成功
// mian.cpp 文件
#include "LandOwnerV2.h" // 关注.h 文件
using namespace std;
int main(){LandOwnerV2 landowner2;landowner2.name = "小明";//landowner2.TouchCard(20); // 这个方法实现不了cout << landowner2.name << endl;return 0;
}
// LandOwnerV2.h
#include <iostream>
using namespace std;
// .hpp 一般包含实现的内联函数,通常用于模版类这种声明与实现共存的情况
// 建议:只要不是纯模版,一律使用.h 作为头文件后缀, .cpp 作为函数的实现文件
// 地主类的声明class LandOwnerV2 {private:long score; // 积分int cards[20]; // 手牌数组public :string name; // 名称LandOwnerV2(); // 构造函数声明~LandOwnerV2(); // 析构函数声明 void TouchCard(int); // 声明摸牌函数void PlayCard(int); // 声明出牌函数void ShowScore(); // 声明产看积分函数
};
// LandOwnerV2.cpp
#include <iostream>
#include "LandOwnerV2.h"
using namespace std;LandOwnerV2::LandOwnerV2()
{//ctor
}void LandOwnerV2::TouchCard(int CardCount){cout << name << "摸了" << CardCount << "张牌" << endl;
}
void LandOwnerV2::ShowScore(){cout << name << "当前的积分为:" << score << endl;
}LandOwnerV2::~LandOwnerV2()
{//dtor
}
(xcode 太不友好了,写到类分文件时就无法编译,弃坑!转向CLION,友好很多。)
3.访问修饰符
public: 修饰的成员在任意地方都可以访问
private:修饰的成员只能在类中或者友元函数中访问, 私有属性可以习惯性在名字前面加一个_
protected:修饰的成员可以在类中函数、子类函数、友元函数中访问
(数据隐藏:不希望别人随意操作,对应的操作叫做封装)
在修饰关键字放在类定义的关键字中,加冒号,无修饰关键字默认为private
class 类名{
修饰符:成员类标;
};
demo3:外部修改成员变量不安全(版本3)
非私有成员,会被直接修改–修改地主积分
// mian.cpp 文件
#include <iostream>
#include "LandOwnerv3.h"
using namespace std;
int main(){// 访问修饰符的实验LandOwnerv3 landOwner3;landOwner3.name = "巴依老爷";// 修改地主积分landOwner3.score = 100;landOwner3.ShowScore(landOwner3.score);return 0;
}
// LandOwnerv3.h 文件
// Created by 陈莹莹 on 2021/1/28.
#include <iostream>
#ifndef HELLOWORLD_LANDOWNERV3_H
#define HELLOWORLD_LANDOWNERV3_H
using namespace std;
class LandOwnerv3 {int cards[20]; // 手牌数组
public :string name;long score; // 积分LandOwnerv3(); // 构造函数声明, 没有返回值~LandOwnerv3(); // 析构函数声明void TouchCard(int); // 声明摸牌函数void PlayCard(int); // 声明出牌函数void ShowScore(int); // 声明产看积分函数
};
#endif //HELLOWORLD_LANDOWNERV3_H
// LandOwnerv3.cpp 文件
// Created by 陈莹莹 on 2021/1/28.
// 用来演示封装的基本概念
#include <iostream>
#include "LandOwnerv3.h"
using namespace std;
LandOwnerv3::LandOwnerv3()
{//ctor
}
void LandOwnerv3::ShowScore(int score){cout << name << "当前的积分为:" << score << endl;
}LandOwnerv3::~LandOwnerv3()
{//dtor
}
输出:
巴依老爷当前的积分为:100
demo4: 使用封装防止直接修改成员变量(版本3)
但是如此操作使得大家可以随意修改这个积分,有一些不合理的积分就会出现。为了解决积分被赋值为不合理的情况,需要将成员变量score进行封装(使用方法来实现对成员的封装)
demo4:类封装概念,get/set方法
// mian.cpp 文件
#include <iostream>
#include "LandOwnerv3.h"
using namespace std;
int main(){// 访问修饰符的实验LandOwnerv3 landOwner3;landOwner3.name = "巴依老爷";// 修改地主积分landOwner3.SetScore(-100);landOwner3.ShowScore();return 0;
}
// LandOwnerv3.h 文件
// Created by 陈莹莹 on 2021/1/28.
#include <iostream>
#ifndef HELLOWORLD_LANDOWNERV3_H
#define HELLOWORLD_LANDOWNERV3_H
using namespace std;
class LandOwnerv3 {long score; // 积分int cards[20]; // 手牌数组
public :string name;LandOwnerv3(); // 构造函数声明, 没有返回值~LandOwnerv3(); // 析构函数声明void TouchCard(int); // 声明摸牌函数void PlayCard(int); // 声明出牌函数void ShowScore(); // 声明产看积分函数// 定义成内联函数即可void SetScore(long lScore){// 通过条件判断封装了score的赋值过程if(lScore < 0){score = 0;}else{score = lScore;}}
};
#endif //HELLOWORLD_LANDOWNERV3_H
// LandOwnerv3.cpp 文件
// Created by 陈莹莹 on 2021/1/28.
// 用来演示封装的基本概念
#include <iostream>
#include "LandOwnerv3.h"
using namespace std;
LandOwnerv3::LandOwnerv3()
{//ctor
}
void LandOwnerv3::ShowScore(){cout << name << "当前的积分为:" << score << endl;
}LandOwnerv3::~LandOwnerv3()
{//dtor
}
输出:
巴依老爷当前的积分为:0
demo5:进一步封装:设置/获取名字,修改积分(版本4)
// mian.cpp 文件
#include <iostream>
#include "LandOwnerv41.h"
using namespace std;
int main(){// 访问修饰符的实验LandOwnerv41 landOwner4;landOwner4.SetName("巴依老爷");cout << landOwner4.GetName() << endl;// 修改地主积分landOwner4.SetScore(-100);landOwner4.ShowScore();return 0;
}
// LandOwnerv41.h 文件
// Created by 陈莹莹 on 2021/1/29.
#ifndef HELLOWORLD_LANDOWNERV41_H
#define HELLOWORLD_LANDOWNERV41_H#include <iostream>
using namespace std;
class LandOwnerv41 {long score; // 积分int cards[20]; // 手牌数组string name;
public :LandOwnerv41(); // 构造函数声明, 没有返回值~LandOwnerv41(); // 析构函数声明void TouchCard(int); // 声明摸牌函数void PlayCard(int); // 声明出牌函数void ShowScore(); // 声明产看积分函数// 定义成内联函数即可void SetScore(long lScore){// 通过条件判断封装了score的赋值过程if(lScore < 0){score = 0;}else{score = lScore;}}string GetName(){return name;}void SetName(string lName){name = lName;}};
#endif //HELLOWORLD_LANDOWNERV41_H
// LandOwnerv41.cpp 文件
// Created by 陈莹莹 on 2021/1/29.
#include "LandOwnerv41.h"
#include <iostream>
using namespace std;
LandOwnerv41::LandOwnerv41()
{//ctor
}
void LandOwnerv41::ShowScore(){cout << name << "当前的积分为:" << score << endl;
}LandOwnerv41::~LandOwnerv41()
{//dtor
}
4.构造函数与析构函数(重点)
构造函数:与类同名,没有返回值
构造函数的作用:给编译器看的,编译器在对象被创建时,为对象分配内存空间,并自动调用构造函数以完成成员的初始化
构造函数的种类:无参数构造,一般构造(重载构造),拷贝构造。(本节主要介绍:无参构造,一般构造)
4.1默认构造函数
demo6: 使用构造函数进行成员变量的初始化
// mian.cpp 文件
//演示构造函数
#include "LandOwnerv41.h"
using namespace std;
int main(){LandOwnerv41 landOwnerv4; // 省略了默认构造LandOwnerv41 landOwnerv4(); // 标准写法,但是不调用默认构造函数了?--不输出内容了return 0;
}
// LandOwnerv41.h 文件
// 与demo5一致
// LandOwnerv41.cpp 文件
// Created by 陈莹莹 on 2021/1/29.
//
#include "LandOwnerv41.h"
#include <iostream>
#include <memory.h>
using namespace std;
LandOwnerv41::LandOwnerv41()
{cout << "LandOwnerV41的无参数构造函数(默认构造)被调用!" << endl;name = "默认地主";score = 0;//将用户的手牌数组初始化为0memset(cards, 0, sizeof(cards)/sizeof(cards[0]));cout << "初始化的结果如下:" << endl;cout << "名字:" << name << endl;cout << "积分:" << score << endl;cout << "手牌数组:" ;for(int i=0; i < sizeof(cards)/sizeof(cards[0]); i++){cout << cards[i] << "\t";}cout << endl;
}
void LandOwnerv41::ShowScore(){cout << name << "当前的积分为:" << score << endl;
}LandOwnerv41::~LandOwnerv41()
{//dtor
}
为啥手牌数组的初始化不是全为0?
LandOwnerV41的无参数构造函数(默认构造)被调用!
初始化的结果如下:
名字:默认地主
积分:0
手牌数组:0 0 0 0 0 0 0 0 -375207560 32766 -375207584 32766 0 1 0 0 0 0 0 0
默认构造显式写法:
LandOwnerV4() = default;
注意:
1.如果创建的类中未书写任何构造函数,系统会自动生成默认的无参构造函数(函数为空,什么都不做)
2.如果书写了构造函数,系统不会自动生成默认构造函数,如果希望有一个这样的无参数构造函数,需要自己显示书写LandOwnerV4() = default;
4.2 带参构造函数
语法:
类名::构造(类型1 参数1, 类型2 参数2, ....){// 相关初始化方法
}
demo7:学生类构建–堆内存对象/栈内存对象
1.栈内存中的对象(类似于函数声明),
具体对象由系统创建并释放,不用担心内存泄露。
声明周期只在声明区域的大括号内。
栈内存的优势存取速度快(仅次于寄存器),缺点栈内存中的数据大小生存期是确定的,缺乏灵活性。
自定义类型名 对象名;
Student stu();
Student stu;
2.堆内存中的对象(需要new关键字)
p_stu1是指针,必须使用delete释放
使用灵活可以赋给全局变量,可以把对象作为函数的返回值
用好了
Student *p_stu1= new Student();
Student *p_stu2= new Student();
auto *p_stu3= new Student(); // auto 自动类型判断,不推荐使用,sizeof 会报错
类对象推荐放在堆内存中,即使用new来创建对象。
// mian.cpp 文件
#include <iostream>
#include "Student.h"
using namespace std;
int main(){// 演示构造函数,实质就是函数重载Student stu1;Student stu2("马化腾","普通家庭"); // 在栈内存中直接分配空间,速度块// 如果构造函数中只有一个参数,且单参数构造函数只有一个,可以直接使用赋值操作进行初始化// Student stu4 = 45stu2.showInfo();// 学生指针类型, 使用new 分配内存空间Student * stu5 = new Student("杰克马", "毁创阿里"); // 在堆内存中分配空间// 类指针访问方法要使用 ->stu5 ->showInfo();return 0;
}
输出结果:
默认构造函数
设置带参构造
普通家庭马化腾
设置带参构造
毁创阿里杰克马
// Student.h 文件
//
// Created by 陈莹莹 on 2021/2/2.
//
#ifndef HELLOWORLD_STUDENT_H
#define HELLOWORLD_STUDENT_H
#include <iostream>
using namespace std;class Student {
private:string m_name;string m_desc;int m_age;
public:Student(); // 默认构造函数Student(string, string);~Student();void showInfo();};
#endif //HELLOWORLD_STUDENT_H
// Student.cpp 文件
//
// Created by 陈莹莹 on 2021/2/2.
//
#include <iostream>
#include "Student.h"
using namespace std;Student::Student() {cout << "默认构造函数" << endl;
}
//Student::Student(string name, string desc){
// // 参数列表不同,函数重载
// m_name = name;
// m_desc = desc;
// cout << "设置带参构造" << endl;
//}
//初始化参数列表的写法,和上上面的带参数构造方法完全相同
Student::Student(string name, string desc):m_name(name), m_desc(desc){cout << "设置带参构造" << endl;
};void Student::showInfo() {cout << m_desc << m_name << endl;
}
Student::~Student() {}
4.3 析构函数
对象销毁时自动调用的特殊成员函数, 析构函数一般用来完成清理工作。
析构函数名称在类名前加一个~,析构函数没有参数,只能有一个
在栈内存中构造的对象,在栈区(mian函数)销毁时,类对象会被自动销毁。在堆内存中构建的类对象,需要手动释放。
在析构函数内部需要释放(delete)掉在构造函数中手动分配(new)的空间(对象内部的成员变量)。
1.析构函数用来释放对象使用的资源,并销毁对象的非static数据成员.
2.无论何时一个对象被销毁,都会自动调用析构函数(隐式析构)
demo8:学生类析构函数演示
类对象推荐放在堆内存中,即使用new来创建对象。
// mian.cpp 文件
#include <iostream>
#include "Student.h"
using namespace std;
int main(){// 演示构造函数,实质就是函数重载Student stu1;Student stu2("马化腾","普通家庭"); // 在栈内存中直接分配空间,速度块// 如果构造函数中只有一个参数,且单参数构造函数只有一个,可以直接使用赋值操作进行初始化// Student stu4 = 45stu2.showInfo();// 学生指针类型, 使用new 分配内存空间Student * stu5 = new Student("杰克马", "毁创阿里"); // 在堆内存中分配空间// 类指针访问方法要使用 ->stu5 ->showInfo();return 0;
}
输出结果:
默认构造函数
设置带参构造
普通家庭马化腾
设置带参构造
毁创阿里杰克马
杰克马被释放 # 杰克马为啥被释放了?
马化腾被释放
被释放
// Student.h 文件
// 与demo7一样
// Student.cpp 文件
//
// Created by 陈莹莹 on 2021/2/2.
//
#include <iostream>
#include "Student.h"
using namespace std;Student::Student() {cout << "默认构造函数" << endl;
}
//Student::Student(string name, string desc){
// // 参数列表不同,函数重载
// m_name = name;
// m_desc = desc;
// cout << "设置带参构造" << endl;
//}
//初始化参数列表的写法,和上上面的带参数构造方法完全相同
Student::Student(string name, string desc):m_name(name), m_desc(desc){cout << "设置带参构造" << endl;
};void Student::showInfo() {cout << m_desc << m_name << endl;
}
Student::~Student() {cout << m_name << "被释放" << endl;
}
5.this指针(重点)
(类似于于python中的self,在python中显式传递self参数)
每个成员函数(包括构造函数和析构函数)都有一个this指针。this指针指向调用对象,可以通过this关键字访问当前对象的成员。
- 访问成员变量:this -> 成员名;
- 访问成员函数:this -> 函数名();
this 在成员函数执行前就已经创建,在析构函数之后被销毁。
->this 指针的类型为*const,为右值;(可以赋值)
->this指针本身不占用大小,并不是对象的一部分,用sizeof 测不出来大小
->this指针作用域在成员函数内部;
->this指针是类成员函数的第一个默认隐含参数,编译器自动维护传递,编写者不能显式传递。
->在类非静态成员函数中才可以使用this指针,其他任何函数都不可以(static是先于类实例存在的,this还没有创建)
this 返回当前对象的引用,太难用了-- Student & GetSuperScholars(Student &);
demo9:学霸返回引用的使用
//mian.cpp
#include <iostream>
#include "Student.h"
using namespace std;
int main(){Student *ptr_stu1 = new Student("迪丽热巴","微胖女孩");ptr_stu1->AddScore(78.9);ptr_stu1->AddScore(77.9);ptr_stu1->AddScore(72.9);ptr_stu1->AddScore(79.9);ptr_stu1->AddScore(800);ptr_stu1->showInfo();Student stu2("刘强东","不爱美人");stu2.AddScore(78.9);stu2.AddScore(77.9);stu2.AddScore(72.9);stu2.AddScore(79.9);stu2.AddScore(90);stu2.showInfo();// Student scholar1 = stu2.GetSuperScholars(*ptr_stu1); 返回new出来的结果,接受的确实栈类型,矛盾,空间释放的时候会出问题
// Student scholar2 = ptr_stu1->GetSuperScholars(stu2);Student &scholar1 = stu2.GetSuperScholars(*ptr_stu1); //返回迪丽热巴的引用,Student &scholar2 = ptr_stu1->GetSuperScholars(stu2); //返回迪丽热巴的引用cout << "学霸是" << scholar1.GetName() << "\t" << scholar2.GetName() << endl;delete ptr_stu1;return 0;//scholar1 栈内存中定义的,main 结束后会自动释放//scholar2 栈内存中定义的,main 结束后会自动释放
}
//Student.h
//
// Created by 陈莹莹 on 2021/2/2.
//
#ifndef HELLOWORLD_STUDENT_H
#define HELLOWORLD_STUDENT_H
#include <iostream>
using namespace std;class Student {
private:string m_name;string m_desc;int m_age;float *scores; // 学生的分数数组,在某次构造的时候进行初始化int scoreCount; // 学生成绩的个数public:Student(); // 默认构造函数Student(string, string);~Student();void showInfo();void InitScores(); // 初始化学生的成绩数组,默认分配一个元素空间void AddScore(float score);// 返回值是引用时,是非常危险的。this可以直接修改属性,建议在函数声明处加一个const,限制在该函数内部不能通过this指针修改属性,太麻烦了,要该的地方太多了Student & GetSuperScholars(Student &); //返回学霸对象// 参数处加const 保证不对该参数进行修改。float GetTotal();string GetName() {return m_name;}};#endif //HELLOWORLD_STUDENT_H
//Student.cpp
//
// Created by 陈莹莹 on 2021/2/2.
//
#include <iostream>
#include "Student.h"
using namespace std;Student::Student() {cout << "默认构造函数" << endl;InitScores();
}
//Student::Student(string name, string desc){
// // 参数列表不同,函数重载
// m_name = name;
// m_desc = desc;
// cout << "设置带参构造" << endl;
//}
//初始化参数列表的写法,和上上面的带参数构造方法完全相同
Student::Student(string name, string desc):m_name(name), m_desc(desc){cout << "设置带参构造" << endl;InitScores();
};void Student::showInfo() {cout << m_desc << m_name << endl;for(int i = 0; i < scoreCount-1; i++){cout << this->scores[i] << "\t";}cout << endl;
}
// C的类方法写(我这么写编译不过)
//void MyShow(const Student* this){
// this->
//}
void Student::InitScores() {this -> scores = new float [1];this -> scoreCount = 1;
}
void Student::AddScore(float score) {this -> scores[this->scoreCount -1] = score;// 1. 创建一个新数组,分配scoreCount+1 个空间// 2. 复制愿数组中的内容到新数组中// 3. scoreCount++// 4. scores指向新数组float *newScores = new float [scoreCount + 1];float *oldScores = scores;memcpy(newScores, scores, sizeof(float) * scoreCount);scoreCount++;scores = newScores;delete oldScores; // 删除原来的指针
}
Student & Student::GetSuperScholars(Student &otherstu){// otherstu 要对比的另一学生对象// 返回总分比较大的那个学生对象// 分别计算两个学生的总分if(this->GetTotal() > otherstu.GetTotal()){return *this;}else{return otherstu;}}
float Student::GetTotal(){float sum = 0;for(int i = 0; i < scoreCount; i++){sum += scores[i];}return sum;}
Student::~Student() {cout << m_name << "被释放" << endl;delete this -> scores;}
输出
设置带参构造
微胖女孩迪丽热巴
78.9 77.9 72.9 79.9 800
设置带参构造
不爱美人刘强东
78.9 77.9 72.9 79.9 90
学霸是迪丽热巴 迪丽热巴
迪丽热巴被释放
刘强东被释放
小结:类就是自己定义的数据类型,对象就是变量