第十一章 定义自己的数据类型
定义一个类就是定义一个新的数据类型。
面向对象编程的特点:
封装(特性一):定义一种数据类型为一个类,然后把和这种数据类型相关的数据和函数打包到这个类中
数据隐藏:在一般情况下,不允许访问对象的数据值。
注意:封装和数据隐藏在其他资料中经常放一块说表明,表示为"封装"这一个特性
继承(特性二):可以在一个类的基础上,续写另一个类,最开始的类叫基类,续写的类叫派生类,派生类有基类的所有属性和方法,并且可以增加自己独有的特性。
重写:在派生类中定义基类的函数
多态性:不同时刻有不同的形态。必须是基类指针调用虚函数,且派生类对该虚函数重写。
定义类
使用class关键字定义类,类名使用大写,类成员在花括号中,注意,右花括号后面必须有分号。
类的所有成员,默认私有。可以用访问修饰符关键字public,private,protected定义不同的成员。
class ClassName
{
private:
// 类的私有成员
public:
// 类的共有成员
protected:
// 类的受保护成员
};
注意:还可以使用关键字struct定义结构体,结构体默认成员是共有的。
struct StructName
{
private:
// 结构体的私有有成员
public:
// 结构体的共有成员
protected:
// 结构体的受保护成员
};
注意:如果没有给指针类型或基本类型的成员变量指定初始值,它们就会包含垃圾值。
#include <iostream>
#include <string>
class Student
{
private:
// 类的私有成员
std::string name;
int age;
public:
// 类的共有成员
void printInfo() {
std::cout << "name : " << name << std::endl;
std::cout << "age : " << age << std::endl;
}
protected:
// 类的受保护成员
};
int main()
{
Student s1;
s1.printInfo(); // 垃圾值:age : -858993460
}
构造函数
构造函数在定义类的新实例时调用。构造函数与类的名字同名。构造函数没有返回值,也没有返回类型。
#include <iostream>
#include <string>
class Student
{
private:
// 类的私有成员
std::string name;
int age;
public:
// 类的共有成员
Student(std::string nameValue, int ageValue) {
std::cout << "---创建一个新学生---" << std::endl;
name = nameValue;
age = ageValue;
}
void printInfo() {
std::cout << "name : " << name << std::endl;
std::cout << "age : " << age << std::endl;
}
protected:
// 类的受保护成员
};
int main()
{
Student s1{"XiaoHong", 12};
s1.printInfo();
}
默认构造函数
如果没有为类定义任何构造函数。编译器将生成默认构造函数。称为默认的默认构造函数。
可以使用default告诉编译器生成一个默认的构造函数
Student() {} // 不需要分号
Student() = default; // 需要分号
成员函数的定义,可以放在类定义的外部。但是需要用类名来限定
类定义内部的成员函数被隐式申明为内联函数
// student.h
#ifndef STUDENT_H
#define STUDENT_H
#endif
#include <string>
#include <iostream>
class Student
{
private:
// 类的私有成员
std::string name;
int age;
public:
// 类的共有成员
Student() = default;
Student(std::string nameValue, int ageValue);
void printInfo();
};
// student.cpp
#include "student.h"
Student::Student(std::string nameValue, int ageValue) {
name = nameValue;
age = ageValue;
}
void Student::printInfo() {
std::cout << "name : " << name << std::endl;
std::cout << "age : " << age << std::endl;
}
int main()
{
Student s1{"XiaoHong", 12};
s1.printInfo();
}
成员初始化列表
Student::Student(std::string nameValue, int ageValue):
// 成员初始化列表,更高效,
name{nameValue},
age{ageValue}
{}
类的构造函数只有一个,可能会有问题。编译器可以使用构造函数把其他类型的值转换成类对象
// student.h
#ifndef STUDENT_H
#define STUDENT_H
#endif
#include <string>
#include <iostream>
class Student
{
private:
// 类的私有成员
int age;
public:
// 类的共有成员
Student() = default;
Student(int num);
int doubleAge(); // 计算年龄的两倍
bool isLargeDoubleAge(Student student);
};
// student.cpp
#include "student.h"
Student::Student(int ageValue):
// 成员初始化列表,更高效,
age{ageValue}
{}
int Student::doubleAge() {
return age * 2;
}
bool Student::isLargeDoubleAge(Student student) {
if (doubleAge() > student.doubleAge()) {
std::cout << doubleAge() << " > " << student.doubleAge() << std::endl;
return true;
}
std::cout << doubleAge() << " <= " << student.doubleAge() << std::endl;
return false;
}
int main()
{
Student s1{12};
Student s2{13};
s1.isLargeDoubleAge(s2);
s1.isLargeDoubleAge(10); // 假设想要与10直接进行比较,但是实际上隐式转换成了age是10的对象,再与进行doubleAge()计算后的值比较
s1.isLargeDoubleAge(14);
}
可以对构造函数只用explicit来规避这样的问题,编译器就不会编译,直接报错。
C++中的explicit关键字只能用于修饰只有一个参数的类构造函数, 它的作用是表明该构造函数是显示的, 而非隐式的
explicit Student(int num);
委托构造函数
在构造函数的成员初始化列表中调用前一个构造函数的所有的值
// student.h
...
Student(int ageValue, int highValue);
Student(int value);
...
// student.cpp
...
Student::Student(int value) :
// 委托构造,是用上一个构造函数参数
Student{value, value / 2}
{}
...
副本构造函数
把类对象作为参数的构造函数
Student(const Student &student); // 一定要用引用,不然会无限递归调用构造函数,编译器出错
...
Student::Student(const Student &student) :
// 副本构造+委托构造
Student{student.age , student.high}
{}
访问私有类成员
可以增加public成员函数,直接返回私有成员变量。这些成员通常直接定义在类中,默认为内联函数。
this指针
在执行任何成员函数时,该成员函数自动包含一个隐藏的指针,称为this指针,包含调用该成员函数对象的地址。
方法链
Student* setAge(int ageValue) {
if (age > 0) {
age = ageValue;
}
return this;
}
Student* setHigh(int highValue) {
if (high > 0) {
high = highValue;
}
return this;
}
s4.setAge(18)->setHigh(180);
const成员函数
如果定义了const对象,调用成员函数printInfo,哪怕printInfo并没有修改成员变量的值,但是,编译器并不知道,还是会报错。
void Student::printInfo() {
std::cout << age << std::endl;
std::cout << high << std::endl;
}
const Student s1{12, 180};
s1.printInfo(); // 报错,编译不过
这时需要给不修改成员变量的函数增加用const定义。当然无法对set这种实际修改了成员变量值得函数定义为const,编译器会报错,这个叫"cosnt 正确性"
void printInfo() const;
将对象转换为非常量
const_cast<Type*>()
const_cast<Type&>()
关键字mutable
即使是const,其const成员也可以修改
如果不加mutable会报错
mutable int num;
void setNum(int numValue) const{
if (numValue > 0) {
num = numValue;
}
return;
}
友元函数
不想让外界访问对象的内部状态,但允许选定少数相关函数访问。
一般当函数需要访问两个不同对象的内部时,才需要把该函数声明为两个类的友元函数。
关键字friend
友元类
在一个类中直接声明一个友元类
class Student
{
private:
// 类的私有成员
int age;
int high;
mutable int num;
public:
// 类的共有成员
friend class Teacher;
类的数组对象
前4个拷贝构造,后两个默认构造
Student ss[6]{ s1,s2,s3,s4 };
类对象的大小
成员变量大小的总和
sizeof()
类的静态成员
独立于类类型的任何对象
初始化时一般声明为inline
static inline int count{ 0 };
如果不想声明为inline,可以在内外初始化
// student.h
class Student
{
private:
// 类的私有成员
int age;
int high;
mutable int num;
static int count;
// student.cpp
int Student::count {0};
静态常量
static inline const int subNo{ 111 };
静态成员函数
static void printSome() {
std::cout << "hello" << std::endl;
}
析构函数
默认析构函数
类的析构函数没有返回值和返回类型
~ClassName();
~Student() = default;
定义在类外部
但是需要在类中先声明
~Student();
Student::~Student() {}
使用指针作为类成员
RAII(资源获取即使化)
应该尽可能地使用std::make_unique<>()和std::make_shared<>()
实现一个Truckload
// main.cpp
#include "truckload.h"
inline unsigned random(size_t count) {
return 1 + static_cast<unsigned>(std::rand() / (RAND_MAX / count + 1));
}
inline SharedBox randomBox() {
const size_t dimLimit{ 99 };
return std::make_shared<Box>(random(dimLimit), random(dimLimit), random(dimLimit));
}
int main() {
std::srand(static_cast<unsigned>(std::time(nullptr)));
Truckload load1;
const size_t boxCount{ 12 };
for (size_t i{}; i < boxCount; ++i) {
load1.addBox(randomBox());
}
std::cout << "The first list:\n";
load1.listBoxes();
Truckload copy{ load1 };
std::cout << "The copied truckload:\n";
copy.listBoxes();
SharedBox largestBox{ load1.getFirstBox() };
SharedBox nextBox{ load1.getNextBox() };
while (nextBox) {
if (nextBox->compare(*largestBox) > 0) {
largestBox = nextBox;
}
nextBox = load1.getNextBox();
}
std::cout << "\nThe largest box in the first list is ";
largestBox->listBox();
std::cout << std::endl;
load1.removeBox(largestBox);
std::cout << "\nAfter deleting the largest box,the list contains:\n";
load1.listBoxes();
const size_t nBoxes{ 20 };
std::vector<SharedBox> boxes;
for (size_t i{}; i < nBoxes; ++i) {
boxes.push_back(randomBox());
}
Truckload load2{ boxes };
std::cout << "\nThe second list:\n";
load2.listBoxes();
auto smallestBox = load2.getFirstBox();
for (auto nextBox = load2.getNextBox(); nextBox; nextBox = load2.getNextBox()) {
if (nextBox->compare(*smallestBox) < 0) {
smallestBox = nextBox;
}
}
std::cout << "\nThe smallest box in the second list is";
smallestBox->listBox();
std::cout << std::endl;
}
// truckload.cpp
#include "truckload.h"
Truckload::Truckload(const std::vector<SharedBox>& boxes) {
for (const auto& pBox : boxes) {
addBox(pBox);
}
}
Truckload::Truckload(const Truckload& src) {
for (Package* package{ src.pHead }; package;package = package->getNext()) {
addBox(package->getBox());
}
}
void Truckload::addBox(SharedBox pBox) {
auto pPackage = new Package{ pBox };
if (pTail) {
pTail->setNext(pPackage);
}
else {
pHead = pPackage;
}
pTail = pPackage;
}
void Truckload::listBoxes() const
{
const size_t boxesPerLine = 5;
size_t count{};
Package* currentPackage{ pHead };
while (currentPackage)
{
currentPackage->getBox()->listBox();
if (!(++count % boxesPerLine)) {
std::cout << std::endl;
}
currentPackage = currentPackage->getNext();
}
if (count % boxesPerLine) {
std::cout << std::endl;
}
}
SharedBox Truckload::getFirstBox() {
pCurrent = pHead;
return pCurrent ? pCurrent->getBox() : nullptr;
}
SharedBox Truckload::getNextBox() {
if (!pCurrent) {
return getFirstBox();
}
pCurrent = pCurrent->getNext();
return pCurrent ? pCurrent->getBox() : nullptr;
}
bool Truckload::removeBox(SharedBox boxToRemove) {
Package* previous{ nullptr };
Package* current{ pHead };
while (current) {
if (current->getBox() == boxToRemove) {
if (previous) {
previous->setNext(current->getNext());
}
else {
pHead = current->getNext();
}
current->setNext(nullptr);
delete current;
return true;
}
previous = current;
current = current->getNext();
}
return false;
}
// truckload.h
#ifndef TRUCKLOAD_H
#define TRUCKLOAD_H
#include <vector>
#include "package.h"
#include "box.h"
class Truckload
{
private:
Package* pHead{};
Package* pTail{};
Package* pCurrent{};
public:
Truckload() = default;
Truckload(SharedBox pBox) {
pHead = pTail = new Package{pBox};
}
Truckload(const std::vector<SharedBox>& boxes);
Truckload(const Truckload& src);
~Truckload() { delete pHead; }
SharedBox getFirstBox();
SharedBox getNextBox();
void addBox(SharedBox pBox);
bool removeBox(SharedBox pBox);
void listBoxes() const;
};
#endif
// package.h
#ifndef PACKAGE_H
#define PACKAGE_H
#include "box.h"
using SharedBox = std::shared_ptr<Box>;
class Package
{
private:
SharedBox pBox;
Package* pNext;
public:
Package(SharedBox pb):
pBox{pb},
pNext{ nullptr}
{}
~Package() {
delete pNext;
}
SharedBox getBox() const {
return pBox;
}
Package* getNext() {
return pNext;
}
void setNext(Package* pPackage) {
pNext = pPackage;
}
};
#endif
// box.h
#ifndef BOX_H
#define BOX_H
#include <iostream>
#include <iomanip>
class Box
{
private:
double length{ 1.0 };
double width{ 1.0 };
double height{ 1.0 };
public:
Box(double lengthValue, double widthValue, double heightValue):
length{ lengthValue },
width{ widthValue },
height{ heightValue }
{}
Box() = default;
double volume() const {
return length * width * height;
}
int compare(const Box& box) const {
if (volume() < box.volume()) {
return -1;
}
if (volume() == box.volume()) {
return 0;
}
return 1;
}
void listBox() const {
std::cout << "Box(" << std::setw(2) << length << ','
<< std::setw(2) << width << ','
<< std::setw(2) << height << ')';
}
};
#endif
// 结果
The first list:
Box(79,63,68)Box(28,66, 1)Box(83, 3,95)Box(53,80,68)Box(91,47,36)
Box(25,99,11)Box(34,73,29)Box(99,66, 7)Box(92,50,85)Box(48,96,87)
Box(99,72,22)Box( 4,65,59)
The copied truckload:
Box(79,63,68)Box(28,66, 1)Box(83, 3,95)Box(53,80,68)Box(91,47,36)
Box(25,99,11)Box(34,73,29)Box(99,66, 7)Box(92,50,85)Box(48,96,87)
Box(99,72,22)Box( 4,65,59)
The largest box in the first list is Box(48,96,87)
After deleting the largest box,the list contains:
Box(79,63,68)Box(28,66, 1)Box(83, 3,95)Box(53,80,68)Box(91,47,36)
Box(25,99,11)Box(34,73,29)Box(99,66, 7)Box(92,50,85)Box(99,72,22)
Box( 4,65,59)
The second list:
Box(34,94,24)Box(33,97,72)Box(66,52,56)Box(21,75,74)Box(36, 1, 9)
Box(59,80,31)Box(64,78,91)Box(68,56,36)Box(47,23,46)Box(67, 4, 4)
Box(40,97,18)Box(44,37,97)Box(17,97,25)Box(55,83,42)Box(13,17,47)
Box(14,35,34)Box(94,97,40)Box(40,22,26)Box(85,27,13)Box(69,16,54)
The smallest box in the second list isBox(36, 1, 9)
模仿上面写一个相似的链表
// hero.h
#ifndef HERO_H
#define HERO_H
#include <string>
#include <iostream>
#include <iomanip>
class Hero {
private:
std::string name;
int strength;
int agility;
int intelligence;
public:
Hero(std::string nameVal, int strengthVal, int agilityVal, int intelligenceVal):
name { nameVal },
strength{ strengthVal },
agility{ agilityVal },
intelligence{ intelligenceVal }
{}
Hero() = default;
int totalAttribute() const{
return strength + agility + intelligence;
}
int compare(const Hero& hero) {
if (totalAttribute() > hero.totalAttribute()) {
return 1;
}
if (totalAttribute() < hero.totalAttribute()) {
return -1;
}
return 0;
}
void listHero() {
std::cout << "name:" << name << "\n"
<< "strength:" << strength << " "
<< "agility:" << agility << " "
<< "intelligence:" << intelligence << std::endl;
}
};
#endif // !HERO_H
// package.h
#ifndef PACKAGE_H
#define PACKAGE_H
#include "hero.h"
using SharedHero = std::shared_ptr<Hero>;
class Package
{
private:
SharedHero pHero;
Package* pNext;
public:
Package(SharedHero pHeroVal):
pHero{pHeroVal},
pNext{nullptr}
{}
Package() = default;
~Package() {}
SharedHero getHero() {
return pHero;
}
Package* getNext() {
return pNext;
}
void setNext(Package* pPackage) {
pNext = pPackage;
}
};
#endif // PACKAGE_H
// team.h
#ifndef TEAM_H
#define TEAM_H
#include "package.h"
#include <vector>
class Team
{
private:
Package* pHead{};
Package* pTail{};
Package* pCurrent{};
public:
Team(SharedHero pHero) {
pHead = pTail = new Package{ pHero };
}
Team(std::vector<SharedHero> hears);
Team(const Team& team);
Team() = default;
void listBoxes();
SharedHero getFirstHero();
SharedHero getNextHero();
void addHero(SharedHero pHero);
bool removeHero(SharedHero pHero);
};
#endif
// team.cpp
#include "team.h"
Team::Team(std::vector<SharedHero> heros) {
for (size_t i = 0; i < heros.size(); ++i) {
addHero(heros[i]);
}
}
Team::Team(const Team& team) {
for (Package* pPackage = team.pHead; pPackage;pPackage = pPackage->getNext()) {
addHero(pPackage->getHero());
}
}
void Team::listBoxes() {
Package* currentPackage = pHead;
while (currentPackage) {
currentPackage->getHero()->listHero();
currentPackage = currentPackage->getNext();
}
}
SharedHero Team::getFirstHero() {
pCurrent = pHead;
return pCurrent ? pCurrent->getHero() : nullptr;
}
SharedHero Team::getNextHero() {
if (!pCurrent) {
return getFirstHero();
}
pCurrent = pCurrent->getNext();
return pCurrent ? pCurrent->getHero() : nullptr;
}
void Team::addHero(SharedHero pHero) {
auto pPackage = new Package{pHero};
if (pTail) {
pTail->setNext(pPackage);
}
else {
pHead = pPackage;
}
pTail = pPackage;
}
bool Team::removeHero(SharedHero pMoveHero) {
Package* current = pHead;
Package* previous = nullptr;
while (current) {
if (current->getHero() == pMoveHero) {
if (previous) {
previous->setNext(current->getNext());
}
else {
pHead = current->getNext();
}
std::cout << "remove success!" << std::endl;
return true;
}
previous = current;
current = current->getNext();
}
std::cout << "error!have no want to remove hero!" << std::endl;
return false;
}
// main.cpp
#include "hero.h"
#include "package.h"
#include "team.h"
int main() {
std::vector<SharedHero> heroList{};
Hero hero1;
Hero hero2;
Hero hero3;
hero1 = Hero("Ember Spirit", 22, 22, 20);
hero2 = Hero("Storm Spirit", 21, 22, 23);
hero3 = Hero("Puck", 17, 22, 23);
SharedHero pHero1 = std::make_shared<Hero>(hero1);
SharedHero pHero2 = std::make_shared<Hero>(hero2);
SharedHero pHero3 = std::make_shared<Hero>(hero3);
heroList.push_back(pHero1);
heroList.push_back(pHero2);
Team team1(heroList);
Team team2(team1);
team1.getFirstHero()->listHero();
SharedHero nHero = team1.getNextHero();
while (nHero) {
nHero->listHero();
nHero = team1.getNextHero();
}
team1.removeHero(pHero1);
team1.removeHero(pHero3);
std::cout << "after remove!!!" << std::endl;
team1.getFirstHero()->listHero();
SharedHero nHero2 = team1.getNextHero();
while (nHero2) {
nHero2->listHero();
nHero2 = team1.getNextHero();
}
std::cout << "team2:" << std::endl;
team2.getFirstHero()->listHero();
SharedHero nHero3 = team2.getNextHero();
while (nHero3) {
nHero3->listHero();
nHero3 = team2.getNextHero();
}
}
// res
/*
name:Ember Spirit
strength:22 agility:22 intelligence:20
name:Storm Spirit
strength:21 agility:22 intelligence:23
remove success!
error!have no want to remove hero!
after remove!!!
name:Storm Spirit
strength:21 agility:22 intelligence:23
team2:
name:Ember Spirit
strength:22 agility:22 intelligence:20
name:Storm Spirit
strength:21 agility:22 intelligence:23
*/