7.1 内联函数
内联(inline)函数:是C++为提高程序运行速度所做的一项改进;与常规函数的区别不在于编写方式,而在于被调用时的运行机制不同;编译使用函数代码替换 函数调用。
使用建议:如果执行函数代码的时间比处理函数调用机制的时间长,则节省的时间将只占整个过程的很小一部分。如果代码执行时间很短,内联调用就可以节省大部分时间。
使用内联特性(选其一):
- 在函数声明前加关键字inline
- 在函数定义前加关键字inline
#include <iostream>
inline int pow(int, int);
int main()
{
}
//或者
inline int pow(int num1, int num2)
{
}
7.2 函数默认参数
在C++中,函数的形参列表中的形参是可以有默认值的。
语法:返回值类型 函数名 (参数 = 默认值){}
使用默认参数
void sample(int = 10, int = 30);
int main()
{
sample();
sample(123);
}
void sample(int a, int b)
{
cout << a + b << endl;
}
注:
- 默认值可以在函数声明或实现中给出,不能在这两个位置同时出现
- 如果某个位置已经有了默认参数,那么从这个位置往后,从左到右都必须有默认参数
7.3 函数占位参数
c++中函数形参列表里可以有占位参数,用来做占位,调用函数时必须填补该位置
语法:返回值类型 函数名 (数据类型){}
占位参数,也可以有默认参数
7.4 函数重载
函数重载满足条件:
- 同一个作用域下
- 函数名相同
- 函数参数类型不同或者个数不同或者顺序不同
void fun(int a, int = 10)
小练习:
对下列数组进行排序
int iNums[]{56, 54, 12, 89, 43};
float fNums[]{78.0f, 5.7f, 42.8f, 99.1f};
double dNums[]{78.9, 23.6, 77.8, 98.5, 33.3};
#include <iostream>
using namespace std;
void sorts(int[], int);
void sorts(float[], int);
void sorts(double[], int);
int main()
{
int iNums[]{56, 54, 12, 89, 43};
float fNums[]{78.0f, 5.7f, 42.8f, 99.1f};
double dNums[]{78.9, 23.6, 77.8, 98.5, 33.3};
sorts(iNums, sizeof(iNums) / sizeof(iNums[0]));
sorts(fNums, sizeof(fNums) / sizeof(fNums[0]));
sorts(dNums, sizeof(dNums) / sizeof(dNums[0]));
}
void sorts(int iNums[], int length)
{
int temp;
for(int i = 0; i < length; i++)
{
for(int j = 0; j < length - i - 1; j++)
{
if(iNums[j] < iNums[j +1])
{
temp = iNums[j];
iNums[j] = iNums[j + 1];
iNums[j + 1] = temp;
}
}
}
for(int i = 0; i < length; i++)
{
cout << iNums[i] << endl;
}
cout << endl;
}
void sorts(float fNums[], int length)
{
float temp;
for(int i = 0; i < length; i++)
{
for(int j = 0; j < length - i - 1; j++)
{
if(fNums[j] < fNums[j + 1])
{
temp = fNums[j];
fNums[j] = fNums[j + 1];
fNums[j + 1] = temp;
}
}
}
for(int i = 0; i < length; i++)
{
cout << fNums[i] << endl;
}
cout << endl;
}
void sorts(double dNums[], int length)
{
double temp;
for(int i = 0; i < length; i++)
{
for(int j = 0; j < length - i - 1; j++)
{
if(dNums[j] < dNums[j + 1])
{
temp = dNums[j];
dNums[j] = dNums[j + 1];
dNums[j + 1] = temp;
}
}
}
for(int i = 0; i < length; i++)
{
cout << dNums[i] << endl;
}
cout << endl;
}
7.4.1 函数重载注意事项
- 引用作为重载条件
void fun(int&)
{
cout << "fun(int&)调用" << endl;
}
void fun(const int&)
{
cout << "fun(const int&)调用" << endl;
}
int main()
{
int a = 10;
fun(a);
fun(10)
}
- 函数重载碰到函数默认参数
void func(int a, int b = 10)
{
cout << "func(int a, int b)的调用" << endl;
}
void func(int a)
{
cout << "func(int a)的调用" << endl;
}
int main()
{
func(10);//两个函数都能调用,编译器报错
}
7.5 函数模板
所谓函数模板,实际上就是建立一个通用函数,以支持多种不同的形参,避免重载函数的函数体重复设计。它的最大特点是把函数使用的数据类型作为参数。
- 函数定义时不指定具体的数据类型(使用虚拟类型代替)
- 函数被调用时编译器根据实参反推数据类型 - - 类型的参数化
模板头与函数声明 / 定义是不可分割的整体
template <typename 类型参数1, typename 类型参数2,...>
返回值类型 函数名(形参列表){
//在函数体中可以使用类型参数
}
其中:
① template是定义模板函数的关键字,
②typename(或class)是声明数据类型参数标识符的关键字,用以说明它后面的标识符是数据类型标识符,
③在以后定义的这个函数中,想要根据实参数据类型来确定数据类型的变量,都可以用数据类型参数标识符来说明,从而使这个变量可以适应不同的数据类型
函数模板的生成:函数模板的数据类型参数标识符实际上是一个类型形参,在使用函数模板时,要将这个形参实例化为确定的数据类型。将类型形参实例化的参数称为模板实参,用模板实参实例化的函数称为模板函数。模板函数的生成就是将函数模板的类型形参实例化的过程。
注:
- 函数模板允许使用多个类型参数,但在template定义部分的每个形参前必须有关键字typename或class
- 在template语句与函数模板定义语句<返回类型>之间不允许有别的语句。
- 模板函数类似于重载函数,但两者有很大区别:函数重载时,每个函数体内可以执行不同的动作,但同一个函数模板实例化后的模板函数都必须执行相同的动作。
//函数声明
template<typename T> void Swap(T&, T&);
//使用模板技术实现变量交换值
template<typename T> //模板头
void Swap(T &a, T &b)
{
T temp = a;
a = b;
b = temp;
}
案例:书写函数模板
#include <iostream>
using namespace std;
//书写函数模板
template<typename T> void Sort(T tArray[], int len);
template<typename T> void show(T tArray[], int len);
int main()
{
int iNums[]{56, 54, 12, 89, 43};
float fNums[]{78.0f, 5.7f, 42.8f, 99.1f};
double dNums[]{78.9, 23.6, 77.8, 98.5, 33.3};
Sort(iNums, sizeof(iNums) / sizeof(iNums[0]));
Sort(fNums, sizeof(fNums) / sizeof(fNums[0]));
Sort(dNums, sizeof(dNums) / sizeof(dNums[0]));
show(iNums, sizeof(iNums) / sizeof(iNums[0]));
show(fNums, sizeof(fNums) / sizeof(fNums[0]));
show(dNums, sizeof(dNums) / sizeof(dNums[0]));
}
template<typename T>
void Sort(T tArray[], int len)
{
T temp;
for(int i = 0; i < len; i++)
{
for(int j = 0; j < len - i - 1; j++)
{
if(tArray[j] < tArray[j +1])
{
temp = tArray[j];
tArray[j] = tArray[j + 1];
tArray[j + 1] = temp;
}
}
}
}
template<typename T>
void show(T tArray[], int len)
{
for(int i = 0; i < len; i++)
{
cout << tArray[i] << endl;
}
cout << endl;
}
8.1 面向对象
所谓面向对象就是基于对象概念,以对象为中心,以类和继承为构造机制,来认识、理解、刻画客观世界和设计、构建相应的软件系统(模拟现实)。
面向对象的三个基本特征:封装、继承和多态
- 对象是由数据和容许的操作组成的封装体,与客观实体有直接对应关系
- 面向对象不是某一种语言特性,而是一种编程思想
8.2 封装
封装的意义:
- 将属性和行为作为一个整体,表现生活中的事物
- 将属性和行为加以权限控制
意义一:
在设计类的时候,属性和行为写在一起,表现事物
语法:class 类名{ 访问权限:属性 / 行为};
意义二:
类在设计时,可以把属性和行为放在不同的权限下,加以控制
访问权限有3种:public(公共权限)、protected(保护权限)、private(私有权限)
8.3 类
8.3.1 C++中的类
类是一种将抽象转换为用户定义类型的工具,
将数据表示和操纵数据的方法组合成一个整体,
类的实例称为对象,对象的集合称为类,
类中的变量和函数称为成员。
8.3.1.1 类的声明和使用
类的声明:使用class / struct关键字声明类型
class 类名{};
struct 类名{};
注:
- class方式声明的类型与struct声明的类型仅仅是形式上不同
- 其唯一区别在于使用class声明的类型默认成员是私有的(private),而struct声明的类型默认成员是公有的(public)
8.3.1.2 类的成员函数
函数声明:
函数定义:
例:写一个地主类
#include <iostream>
using namespace std;
class LandOwnerV2
{
public:
LandOwnerV2(); //构造函数的声明
~LandOwnerV2(); //析构函数的声明
string name;
long score;
int cards[20];
void TouchCard(int); //声明摸牌方法
void ShowScore(); //声明显示积分的方法
protected:
private:
};
#include "LandOwnerV2.h"
LandOwnerV2::LandOwnerV2()
{
//ctor
}
//实现摸牌方法
void LandOwnerV2::TouchCard(int cardCount)
{
cout << name << "摸了" << cardCount << "张牌" << endl;
}
LandOwnerV2::~LandOwnerV2()
{
//dtor
}
#include "LandOwnerV2.h" //如果要使用类,那么必须包含类的头文件
int main()
{
LandOwnerV2 landOwner2;
landOwner2.name = "于谦";
landOwner2.TouchCard(20);
return 0;
}
8.3.1.3 访问修饰符
常见访问修饰符
public:修饰的成员在任意的地方都可以访问
private: 修饰的成员只能够在类中或者友元函数中可以访问
protected:修饰的成员可以在类中函数、子类函数及友元函数中访问
修饰成员:
将修饰关键字放置在类定义的大括号中,添加冒号。
calss 类名{
修饰符:
成员列表;
};
class LandOwner{
private:
string namae;
public:
void PlayCard();
};
//演示封装的基本概念
#include <iostream>
using namespace std;
class LandOwnerV3
{
public:
LandOwnerV3();
virtual ~LandOwnerV3();
string name;
long score; //解决积分被赋值为负数错误的方法:将成员变量score进行隐藏并封装
int cards[20];
void ShowScore();
//使用方法/函数实现对成员变量的封装(Get/Set方法)
void SetScore(long lScore){
if(lScore < 0){
//如果传入的积分是非法情况,那么积分默认为0
score = 0;
}else{
//积分为正数时,才进行赋值操作 - 这里就通过条件判断,封装了score的赋值过程
score = lScore;
}
}
protected:
private:
};
#include "LandOwnerV3.h"
LandOwnerV3::LandOwnerV3()
{
//ctor
}
void LandOwnerV3::ShowScore()
{
cout << name << "当前积分为:" << score << endl;
}
LandOwnerV3::~LandOwnerV3()
{
//dtor
}
#include "LandOwnerV3.h"
int main()
{
LandOwnerV3 landOwner3;
landOwner3.name = "巴依老爷";
//注:模拟为地主修改积分
landOwner3.SetScore(-100);
landOwner3.ShowScore();
return 0;
}
使用工具自动生成Get/Set
直接在创建新类文件的时候添加成员变量
class LandOwnerV5
{
public:
LandOwnerV5();
virtual ~LandOwnerV5();
long Getscore() { return score; }
void Setscore(long val)
{
if(val < 0) score = 0;
score = val;
}
string Getname() { return name; }
void Setname(string val) { name = val; }
protected:
private:
long score;
string name;
int cards[20];
};
8.3.1.4 构造函数
构造函数:以类名作为函数名,无返回值类型。主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用
语法:类名(){}
注:
- 构造函数可以有参数,因此可以发生重载
- 程序在调用对象时会自动调用构造,无须手动调用,而且只会调用一次
- 类的对象被创建时,编译器为对象分配内存空间并自动调用构造函数以完成成员的初始化
构造函数的种类:无参构造、一般构造(重载构造)、拷贝构造
8.3.1.5 无参构造
LandOwnerV5::LandOwnerV5()
{
cout << "LandOwner5的无参构造函数(默认构造)被调用!" << endl;
}
注:
- 如果创建的类中未书写任何构造函数,系统会自动生成无参构造函数(函数为空,什么都不做)
- 如果书写了构造函数,系统就不会自动生成默认构造
8.3.1.6 带参构造
类名::构造(类型1 参数1,类型2 参数2,…)
{
//相关初始化代码
}
Student::Student(string name, string desc)
{
name = name;
desc = desc;
}
例:
#include <iostream>
using namespace std;
class Student
{
public:
//构造函数的重载规则与普通函数重载相同
Student();
Student(int);
Student(string, string); //带参构造
~Student();
void ShowInfo();
string GetName() { return m_Name; }
void SetName(string val) { m_Name = val; }
string Getdesc() { return m_desc; }
void Setdesc(string val) { m_desc = val; }
int Getage() { return m_age; }
void Setage(int val) {
if(val < 0){
m_age = 18;
}else{
m_age = val;
}
}
protected:
private:
string m_Name;
string m_desc;
int m_age;
};
#include "Student.h"
Student::Student()
{
cout << "默认构造" << endl;
}
Student::Student(string name, string desc)
{
m_Name = name; //等价写法:SetName(name);
m_desc = desc;
cout << "调用带参构造:Student(string name, string desc)" << endl;
}
Student::Student(int age)
{
Setage(age);
cout << "调用带参构造:Student(int age)" << endl;
}
void Student::ShowInfo()
{
cout << m_Name << endl;
cout << m_desc << endl;
}
Student::~Student()
{
//dtor
}
#include "Student.h"
int main()
{
Student stu1; //在栈内存中直接分配空间
Student stu2("马化腾", "普通家庭");
Student stu3(45);
stu2.ShowInfo();
//实例化对象
Student* stu4 = new Student("杰克马", "悔创阿里");//在堆内存分配空间
stu4->ShowInfo();
return 0;
}
拷贝构造函数
class Person
{
public:
Person(){}
~Person(){}
//拷贝构造
Person(const Person &p){//将传入的对象的所有属性,拷贝到另一个对象
age = p.age;
}
int age;
}
int main()
{
//调用:
//1. 括号法:
Person p1(10);
person p2(p1);
//2. 显示法:
Person p3(10);//调用有参构造
Person p4 = Person(p3)//Person(10):匿名对象 特点:当执行结束后,系统会立即回收掉匿名对象
//隐式转换法:
Person p5 = 10;//有参构造
Person p6 = p5;//拷贝构造
}
拷贝构造使用场景:
- 使用一个已经创建完毕的对象来初始化一个新对象
- 值传递的方式给函数参数传值
- 以值方式返回局部对象
构造函数调用规则
默认情况下,c++编译器至少给一个类添加3个函数:
- 默认构造函数
- 默认析构函数
- 默认拷贝构造函数,对属性进行值拷贝
构造函数调用规则:
- 如果用户定义有参构造,系统不再提供无参构造,但是会提供默认拷贝
- 如果定义拷贝构造,系统不提供其他普通构造函数
8.3.1.7 深拷贝与浅拷贝
浅拷贝:简单的赋值拷贝操作
深拷贝:在堆区重新申请空间,进行拷贝操作
class Person
{
public:
Person(){
cout << "Person的默认构造函数" << endl;
}
Person(int _age, int height) {
age = _age;
m_height = new int(height);
cout << "Person的默认youcan构造函数" << endl;
}
~Person() {
if (m_height != NULL) {
delete m_height;
}
cout << "Person的默认析构函数" << endl;
}
//如果不利用深拷贝在堆区创建新内容,会导致浅拷贝带来的重复释放堆区的问题
Person(const Person& p)
{
cout << "拷贝构造函数的调用" << endl;
age = p.age;
//m_height = p.m_height; //编译器默认实现的代码
m_height = new int(*p.m_height);
}
int age = 9;
int* m_height;
};
//值传递方式给函数参数传值
int main()
{
Person p1(18, 200);
Person p2(p1);
cout << p1.age << endl;
cout << "p1.age = " << p1.age << endl;
cout << "p1.m_height = " << *p1.m_height << endl;
cout << "p2.age = " << p2.age << endl;
cout << "p2.m_height = " << *p2.m_height << endl;
}
注:如果属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题
8.3.1.8 析构函数
析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理工作。析构函数的名称是在类名前加上~。(析构函数没有参数,只能有一个)
语法:~类名(){}
注:
- 析构函数不能有参数,不能重载
- 程序在对象销毁前会自动调用析构,无须手动调用,而且只会调用一次
class Student
private:
double * scores;
public:
Student(int len)
{
//使用new分配资源
scores = new double[len];
}
~Student()
{
delete scores;//释放资源
}
}
注:
3. 析构函数用来释放对象使用的资源,并销毁对象的非static数据成员
4. 无论何时一个对象被销毁,都会自动调用其析构函数(隐式析构)
8.3.1.9 初始化列表
作用:初始化属性
语法:构造函数() : 属性1(值1), 属性2(值2)…{}
class Person
{
public:
//初始化列表初始化属性
Person(int a, int b, int c) : m_a(a), m_b(b), m_c(c) {};
int m_a, m_b, m_c;
};
//值传递方式给函数参数传值
int main()
{
Person p(10, 20, 30);
cout << p.m_a << p.m_b << p.m_c << endl;
}
8.3.1.10 使用类创建对象
第一种实例化方式:
栈内存中创建:类似声明变量
自定义类型 对象名[([参数列表])];
Student stu(); Student stu;
注:
- stu对象由系统创建并释放,不用担心会出现内存泄漏
- 生命周期只在声明区域的大括号内
- 栈内存的优势是存取速度比较快(仅次于寄存器),缺点是存在占内存的数据大小与生存期必须是确定的,缺乏灵活性
第二种实例化方式:
在堆内存中创建:需要new关键字
Student* p_stu1 = new Student();
Student* p_stu2 = new Student;
auto* p_stu3 = new Student();
注:
- p_stu1是指针,必须使用delete释放
- 使用灵活(可以赋值给全局变量,可以把对象作为函数的返回值返回)
8.3.1.11 类对象作为类成员
当类中成员是其他对象时,我们称该成员为对象成员
当其它类对象作为本类成员,构造时先构造类对象,在构造自身;析构的顺序与构造相反
class Phone {//手机类
public:
Phone(string pName) {
m_Pname = pName;
cout << "Phone的构造调用" << endl;
}
~Phone() {
cout << "Phone的析构调用" << endl;
}
string m_Pname;//手机名称
};
class Person {//人类
public:
Person(string name, string pName) :m_Name(name), m_Phone(pName){
cout << "Person的构造调用" << endl;
}
~Person() {
cout << "Person的析构调用" << endl;
}
string m_Name;//人名
Phone m_Phone;
};
//当其它类对象作为本类成员,构造时先构造类对象,在构造自身
//析构的顺序与构造相反
int main()
{
Person p("张三", "华为");
cout << p.m_Name << '\t' << p.m_Phone.m_Pname << endl;
}
8.3.1.12 静态成员函数
静态成员函数只能访问静态成员变量
class Student{
public:
static void fun(){
m_a = 1;
//m_b = 2;//不可访问
}
static int m_a;//静态成员变量
int m_b;//非静态
}
int main(){
Student s;//通过对象访问
s.fun();
Student::fun();//通过类名访问
注:只有非静态成员变量才属于类的对象上
·
·
·
·
·
·
·
·
练习:
实现地主的摸牌,查看牌面的功能
#ifndef LANDOWNER_H
#define LANDOWNER_H
#include <iostream>
#include <vector>
using namespace std;
class LandOwner
{
public:
LandOwner();
LandOwner(string);
LandOwner(string, string, int, long);
~LandOwner();
string GetNickName() { return m_NickName; }
void SetNickName(string val) { m_NickName = val; }
string GetSex() { return m_Sex; }
void SetSex(string val) { m_Sex = val; }
int GetGold() { return m_Gold; }
void SetGold(int val) { m_Gold = val; }
long GetExp() { return m_Exp; }
void SetExp(long val) { m_Exp = val; }
void TouchCard(int); //摸牌
void ShowInfo();
void InitCards(); //初始化packCards,suplusCards,currCards
void ShowCards(const vector<int>&); //显示集合中的牌面
protected:
private:
string m_NickName;
string m_Sex;
int m_Gold;
long m_Exp;
vector<int> packCards; //默认的一幅牌,从1-54
vector<int> surplusCards; //摸牌后剩余的牌
vector<int> currCards; //当前玩家的手牌数组
bool isContains(int cardNum); //剩余牌集合中是否包含cardNum这个牌面
void deleteCard(vector<int>&, int); //删除集合中的数字
string getColor(int); //获得牌的花色
string getValue(int); //获得牌面
};
#endif // LANDOWNER_H
#include "LandOwner.h"
#include <iterator>
#include <algorithm>
#include <ctime>
#include <cstdlib>
LandOwner::LandOwner()
{
SetNickName("默认地主");
SetSex("保密");
SetExp(0);
SetGold(1000);
cout << "LandOwner()" << endl;
InitCards();
}
LandOwner::LandOwner(string nickName): m_NickName(nickName)
{
SetSex("保密");
SetExp(0);
SetGold(1000);
cout << "LandOwner(string nickName)" << endl;
InitCards();
}
LandOwner::LandOwner(string nickName, string sex, int gold, long exp): m_NickName(nickName), m_Sex(sex), m_Gold(gold), m_Exp(exp)
{
cout << "LandOwner(string nickName, string sex, int gold, long exp)" << endl;
//InitCards();
}
void LandOwner::ShowInfo()//显示地主的基本信息
{
cout << "昵称:" << GetNickName() << endl;
cout << "性别:" << GetSex() << endl;
cout << "金币:" << GetGold() << endl;
cout << "经验:" << GetExp() << endl;
}
void LandOwner::InitCards() //初始化packCards,suplusCards,currCards
{
//生成默认的一幅扑克牌
for(int i = 0; i < 54; i++)
{
packCards.push_back(i + 1);
surplusCards.push_back(packCards.at(i));
}
currCards.clear();
}
void LandOwner::ShowCards(const vector<int>& cards)
{
/*
for(int i = 0; i < cards.size(); i++)
{
cout << cards[i] << ",";
}
cout << endl;*/
//使用迭代器遍历
/*
for(vector<int>::const_iterator iter = cards.begin(); iter != cards.end(); iter++)
{
cout << *iter << ",";
}
cout << endl;*/
/* 使用C++11的类型推断
for(auto iter = cards.begin(); iter != cards.end(); iter++)
{
cout << *iter << ",";
}
cout << endl;*/
//for区间遍历 -C++11
for(auto card : cards){
cout << card << ":" << getColor(card) << "-" << getValue(card) << ", ";
}
cout << endl;
/*
//使用算法的方式,将容器的内容复制到cout绑定的迭代器中
//需要的导入两个头文件:#include <iterator> #include <algorithm>
copy(cards.cbegin(), cards.cend(), ostream_iterator<int>(cout, ","));
cout << endl;*/
}
void LandOwner::TouchCard(int cardCount)
{
srand(time(NULL)); //时间种子
//随机生成一张剩余集合中有的牌,添加到currCards集合中,从surplusCards中删除这张牌
for(int i = 0; i < cardCount; i++)
{
int randIndex = rand() % 54; //0-53之间的随机数字
//判断:随机生成的这张牌是否在剩余牌集合中
if(isContains(packCards[randIndex])){
currCards.push_back(packCards[randIndex]); //将摸的牌放入当前手牌数组
//在剩余牌集合中删除这张牌
deleteCard(surplusCards, packCards[randIndex]);
}else{
i--;//重新摸牌
}
}
cout << "<地主摸牌> - 当前手牌如下:" << endl;
ShowCards(currCards);
cout << "<地主摸牌后> - 剩余的牌面如下:" << endl;
ShowCards(surplusCards);
}
void LandOwner::deleteCard(vector<int>& cardVec, int card) //删除集合中的数字
{
/*第一种方法:
for(auto iter = cardVec.begin(); iter != cardVec.end(); ){
if(*iter == card){ //如果找到就删除元素
iter = cardVec.erase(card); //这里的返回值指向已删除元素的下一元素
}else{
++iter;//继续判断下一个元素是否相同
}
}*/
//第二种:使用算法删除
auto iter = find(cardVec.begin(), cardVec.end(), card);
if(iter != cardVec.end()){
cardVec.erase(iter);
}
}
bool LandOwner::isContains(int cardNum)
{
//使用算法查找
vector<int>::iterator iter = find(surplusCards.begin(), surplusCards.end(), cardNum);
//if(iter == surplusCards.end()){
// return false;
//}
//return true;
return iter != surplusCards.end();
}
string LandOwner::getColor(int card) //注意:card不是下标
{
if(card == 53) return "小王";
if(card == 54) return "大王";
string colors[] = {
"黑桃", "红心", "方块", "梅花"
};
return colors[(card - 1) / 13];
}
string LandOwner::getValue(int card){
if(card == 53) return "Black Joker";
if(card == 54) return "Red Joker";
string values[] = {
"A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"
};
return values[(card - 1) % 13];
}
LandOwner::~LandOwner(){
// cout << GetNickName() << "被释放" << endl;
}
#include "LandOwner.h"
int main()
{
LandOwner* ptr_LandOwner1 = new LandOwner();
LandOwner* ptr_LandOwner2 = new LandOwner("专业斗地主");
LandOwner* ptr_LandOwner3 = new LandOwner("周扒皮");
cout << endl;
ptr_LandOwner1->ShowInfo();
ptr_LandOwner2->ShowInfo();
ptr_LandOwner3->ShowInfo();
ptr_LandOwner2->TouchCard(20);
delete ptr_LandOwner1;
delete ptr_LandOwner2;
delete ptr_LandOwner3;
return 0;
}
·
·
·
·
·
·
·
·
·
8.4 const
一. const修饰成员变量:
const修饰指针变量时;
- 只有一个const时,如果const位于*的左侧:表示指针所指的数据是常量,不能通过该指针修改实际数据(指针本身是变量,可以指向其他内存单元)
- 只有一个const时,如果const位于*的右侧,表示指针本身是常量,不能指向其他内存单元,所指向的数据可以直接修改
- 如果有两个const位于*左右两侧,表示指针和指针所指向的数据都不可修改
二. const 修饰函数参数
- 传递来的参数num在函数体内不可改变,与修改变量时的性质一致。
- const修饰引用时,不能修改引用对象的任何成员 - 好处是可以保护传递的参数:不需要一个新的参数副本
- 使用const传递对象的引用时,可以起到不copy对象的目的(节省效率)
注:const成员函数不能调用非const成员函数
三. const修饰返回值
- 如果函数要返回局部对象,应该直接返回这个对象,而不要返回对象的引用
- 在可以返回对象也可以返回引用时,应该首选引用,效率高
- 强调:使用const修饰引用类型的一个常见的原因:提高效率
四. const修饰成员函数 - 说明函数不会修改成员变量的值
1.成员函数后加上const之后称这个函数为常函数
2.常函数内不可以修改成员属性;
3.成员属性声明时加关键字mutable后,在常函数中依然可以修改
常对象:
声明对象前加上const称该对象为常对象
常对象只能调用常函数
class Person {
public:
//this指针的本质是指针常量,指针的指向是不可以修改的
//在成员函数后面加const,修饰的是this指向,让指针指向的值也不可以修改
void showPerson()const {
this->m_grade = 100;
cout << m_grade << endl;
//m_age = 100;//不可修改
}
int m_age;
mutable int m_grade;//在常函数中加上mutable,可以修改这个值
};
int main()
{
Person p;
p.showPerson();
const Person p1;//在对象前加上const,变为常对象
//p1.m_age = 1;//不可以修改
p1.m_grade = 2;
}
代码:
#include <iostream>
using namespace std;
//1. const修饰成员变量
void ConstDemo1()
{
int num1 = 1024;
const int num2 = num1;
const int * ptr1_num1 = &num1;
int const * ptr2_num1 = &num1;
ptr1_num1 = &num2;//合法
//*ptr1_num1 = 1234;//不合法
//const修饰指针变量时;
//1. 只有一个const时,如果const位于*的左侧:表示指针所指的数据是常量,不能通过该指针修改实际数据
// 指针本身是变量,可以指向其他内存单元
//2. 只有一个const时,如果const位于*的右侧,表示指针本身是常量,不能指向其他内存单元,所指向的数据可以直接修改
int * const ptr3_num1 = &num1;
//ptr3_num1 = ptr2_um1;//不合法
//3. 如果有两个const位于*左右两侧,表示指针和指针所指向的数据都不可修改
cout << num1 << ptr1_num1 << num2;
}
//2. const 修饰函数参数
void ConstTest2(const int num)
{
//num = 12;//传递来的参数num在函数体内不可改变,与修改变量时的性质一致
}
class Computer{
public:
Computer(int core){this->m_core = core;}
void buy () const{}
void buy(int core)
{
}
//修改电脑的核心频率
void SetCore(int core){this->m_core = core;}
int GetCore() const {return m_core;}
private:
int m_core;//cpu的主频
};
void ConstTest3(const Computer & computer)
{
//const修饰引用时,不能修改引用对象的任何成员 - 好处是可以保护传递的参数:不需要一个新的参数副本
computer.buy(); //const成员函数不能调用非const成员函数
//使用const传递对象的引用时,可以起到不copy对象的目的(节省效率)
}
//3. const修饰返回值
//强调:使用const修饰引用类型的一个常见的原因:提高效率
const Computer & GetMax(const Computer & com1, const Computer & com2)
{
if(com1.GetCore() > com2.GetCore()){
return com1;
}else{
return com2;
}
}
//如果函数要返回局部对象,应该直接返回这个对象,而不要返回对象的引用
//在可以返回对象也可以返回引用时,应该首选引用,效率高
//4. const修饰函数 - 说明函数不会修改成员变量的值
class TestClass{
public:
int value;
void ModifyValue() const{
// value = 11;
}
};
8.5 运算符重载
为什么要重载运算符:在C++中已经给出的运算符(包括算数运算符和逻辑运算符)只是针对C++语言中已经给定的数据类型进行运算,假如我们想要对我们的自定义数据类型进行运算的话,则需要重载运算符,可以把重载运算符理解成对已有的运算符的一种重新定义。
运算符重载概念:对已有的运算符重新进行定义,赋予另一种功能,以适应不同的数据类型,它的目标是简化函数调用的方式:
- 把标准的函数使用方式,重新定义为自己认为的方式
- 所谓重载,就是赋予新的含义
- 运算符重载也是一个道理,同一个运算符可以有不同的功能
运算符重载的语法格式:
<返回类型> operator <运算符符号>(参数列表)
class Person {
public:
//1.成员函数重载+号
//本质调用:Person p3 = p1.operator+(p2);
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;
};
//2.全局函数重载+号
//本质调用:Person p3 = operator+(p1, p2);
//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;
//}
//函数重载:
Person operator+(Person& p, int num) {
Person temp;
temp.m_a = p.m_a + num;
temp.m_b = p.m_b + num;
return temp;
}
int main()
{
Person p1;
p1.m_a = 2;
p1.m_b = 3;
Person p2;
p2.m_a = 4;
p2.m_b = 1;
Person p3 = p1 + p2;
cout << p3.m_a << '\t' << p3.m_b << endl;
Person p4 = p1 + 10;
cout << p4.m_a << '\t' << p4.m_b << endl;
}
8.5.1 左移运算符重载
作用:可以输出自定义数据类型
class Person {
public:
Person(int a, int b);
friend ostream& operator << (ostream& out, Person& p);
private:
int m_A;
int m_B;
};
Person::Person(int a, int b) : m_A(a), m_B(b){}
ostream& operator << (ostream& out, Person& p) {
out << p.m_A << endl;
out << p.m_B << endl;
return out;
}
int main() {
Person p1(2, 3);
cout << p1 << endl;
}
8.5.2 递增运算符重载
作用:通过重载递增运算符,实现自己的整型数据
class MyInteger {
public:
MyInteger() {
m_Num = 0;
}
friend ostream& operator<< (ostream& out, const MyInteger& myint);
//重载前置++运算符
MyInteger& operator++() {
//先进行++运算
m_Num++;
//在将自身返回
return * this;
}
//重载后置++运算符
MyInteger operator++(int) {//占位参数,可用于区分前置和后置
//先记录当时的结果
MyInteger temp = *this;
//后递增
m_Num++;
//最后将记录的结果进行返回
return temp;
}
private:
int m_Num;
};
ostream& operator<< (ostream& out, const MyInteger& myint) {
out << myint.m_Num << endl;
return out;
}
int main() {
MyInteger myint1;
cout << ++myint1 << endl;
cout << myint1++ << endl;
}
8.5.3 重载赋值运算符
class Person {
public:
Person(int age) {
m_age = new int(age);
}
~Person() {
delete m_age;
}
Person& operator=(Person& p) {
//编译器提供的是浅拷贝
//m_age = p.m_age;
//应该先判断是否有属性在堆区,如果有则释放干净,然后在深拷贝
if (m_age != NULL) {
delete m_age;
m_age = NULL;
}
m_age = new int(*p.m_age);
return *this;
}
int *m_age;
};
int main() {
Person p1(18);
Person p2(200);
Person p3(10);
p2 = p1;//p1和p2分别释放一次,相当于执行了两次析构,重复释放,程序崩溃
//解决方案:利用深拷贝解决浅拷贝,重载赋值运算符
cout << *p1.m_age << endl;
p1 = p2 = p3;
cout << *p1.m_age << *p2.m_age << *p3.m_age << endl;
}
8.5.3 函数调用运算符重载
函数调用运算符() 可重载,由于重载之后使用的方式非常像函数的调用,因此称为仿函数,仿函数没有固定的写法,非常灵活
class MyPrint {
public:
//重载函数调用运算符
void operator()(string test) {
cout << test << endl;
}
};
//仿函数很灵活
class Add {
public:
int operator()(int num1, int num2) {
return num1 + num2;
}
};
int main() {
MyPrint myp;
myp("hello world");//跟函数调用很相似-仿函数
Add add;
int res = add(10, 10);
cout << res << endl;
//匿名函数对象
cout << Add()(100, 100) << endl;
}
·
·
·
·
可以重载的运算符列表如下:
不能重载的运算符:
.(点运算符)
*(成员指针访问运算符)
::(域运算符)
sizeof(长度运算符)
? :(三元运算符 / 条件运算符)
注:
1. 重载不能修改运算变量个数,比如:关系运算是二元运算,重载后也必须有两个变量参数运算
2. 重载不能修改运算符的优先级别,比如:" * " 和 " / "优先于 " + " 和 " - " ,重载后这个优先级不会被修改
3. 重载不修改运算顺序,比如:赋值运算是从右到左的,重载后不能改变
完善自定义包装类Integer的功能:
const Integer operator-(const Integer & other) const;
const Integer operator*(const Integer & other) const;
const Integer operator/(const Integer & other) const;
const Integer operator%(const Integer & other) const;
//重载赋值符号,需要使用const修饰函数
const Integer operator=(const Integer & other) ;
const Integer Integer::operator-(const Integer & other) const
{
return Integer(this->m_value - other.m_value);
}
const Integer Integer::operator*(const Integer & other) const
{
return Integer(this->m_value * other.m_value);
}
const Integer Integer::operator/(const Integer & other) const
{
return Integer(this->m_value / other.m_value);
}
const Integer Integer::operator%(const Integer & other) const
{
return Integer(this->m_value % other.m_value);
}
const Integer Integer::operator=(const Integer & other)
{
return Integer(this->m_value = other.m_value);
}
8.6 友元函数
在程序里,某些私有属性想让类外特殊的一些函数或者类进行访问,就需要用友元;友元的目的是让一个函数或类访问另一个类中的私有成员
友元的三种实现方法:
- 全局函数做友元
- 类做友元
- 成员函数做友元
8.6.1 全局函数做友元
class Building {
friend void good(Building* building);//可访问Building中的私有成员
public:
Building() {
m_sittingRoom = "客厅";
m_bedRoom = "卧室";
}
string m_sittingRoom;//客厅名称
private:
string m_bedRoom;//卧室名称
};
//全局函数
void good(Building *building)
{
cout << "访问:" << building->m_sittingRoom << endl;
cout << "访问:" << building->m_bedRoom << endl;
}
int main()
{
Building bui;
good(&bui);
}
8.6.2 类做友元
class Building {
public:
friend class GoodGay;//可访问本类中私有成员
Building();
string m_sittingRoom;//客厅名称
private:
string m_bedRoom;//卧室名称
};
class GoodGay {
public:
GoodGay();
void visit();//访问building中的属性
Building* building;
};
GoodGay::GoodGay()
{
//创建建筑物的对象
building = new Building;
}
Building::Building() {
m_sittingRoom = "客厅";
m_bedRoom = "卧室";
}
void GoodGay::visit()
{
cout << "访问" << building->m_sittingRoom << endl;
cout << "访问" << building->m_bedRoom << endl;
}
int main()
{
GoodGay good;
good.visit();
}
8.6.3 成员函数做友元
class Building;
class GoodGay {
public:
GoodGay();
void visit();//visit函数可访问building中的私有成员
void visit2();//普通函数,不可访问building中的私有成员
Building* building;
};
class Building {
//告诉编译器GoodGay类下的visit成员函数作为本类的好朋友,可以访问私有成员
friend void GoodGay::visit();
public:
Building();
string m_sittingRoom;//客厅名称
private:
string m_bedRoom;//卧室名称
};
GoodGay::GoodGay()
{
//创建建筑物的对象
building = new Building;
}
Building::Building() {
m_sittingRoom = "客厅";
m_bedRoom = "卧室";
}
void GoodGay::visit()
{
cout << "visit访问" << building->m_sittingRoom << endl;
cout << "visit访问" << building->m_bedRoom << endl;
}
void GoodGay::visit2()
{
cout << "visit2访问" << building->m_sittingRoom << endl;
//cout << "visit2访问" << building->m_bedRoom << endl;//不可访问
}
int main()
{
GoodGay good;
good.visit();
good.visit2();
}
案例:
解决方案:使用友元函数
friend const Integer operator+(const Ingeter & , const Ingeter &);
friend ostream & operator << (ostream &, const Ingeter &);
const Ingeter operator+(const Ingeter & leftNum, const Ingeter & rigthNum)
{
return Ingeter(leftNum.m_num + rigthNum.m_num);
}
ostream & operator << (ostream & out, const Ingeter & num)
{
out << num.m_num;
return out;
}
运算符声明成类成员还是声明独立友元函数准则:
- C++规定,赋值运算符 = 、数组下标运算符[]、函数调用运算符()、成员访问运算符->在重载时必须声明为类的成员函数;
- 流运算符<<、>>、类型转换运算符不能定义为类的成员函数,只能是友元函数;
- 一元运算符和复合赋值运算符重载时,一般声明类的成员函数;
- 二元运算符在运算符重载时,一般声明为友元函数;
运算符重载为类的友元函数的一般格式为:
friend [函数类型] operator <运算符>([参数表])
{
函数体
}
注:
- 对于很多运算符来说,可以选择使用成员函数或非成员函数来实现运算符重载,一般来说,非成员函数应该是友元函数,这样才能直接访问类的私有数据
- 在定义运算符时,必须选择其中的一种格式,而不能同时选择这两种格式,同时定义这两种格式将被视为二义性错误,导致编译出错
- 友元函数不需要使用域运算符
- 当运算符重载为类的友元函数时,没有隐含的this指针,所以操作数的个数没有变化,所有的操作数都必须通过函数的形参进行传递,函数的参数与操作数自左至右一一对应。