目录
1)面向过程(POP:Procedure Oriented programming)
2)面向对象(OOP:object-oriented programming)
1.面向对象与面向过程
面向对象与面向过程是两种编程思想(如果去解决一个问题)
1)面向过程(POP:Procedure Oriented programming)
基本概念
面向过程是 一种以过程为中心的编程思想,其解决问题的方式就是,把问题的解决过程划分为一个个步骤,通过函数实现这些步骤,最后依次调用这些函数即可。
例子:制作一个下棋游戏(五子棋)
使用面向过程思想制作这个游戏,我们总是把游戏的制作步骤划分成一步步,即先把main函数的步骤确定。
1)初始化棋盘
2)下棋
3)更新棋盘
4)判断输赢
5)游戏结束
再把上面每个过程通过函数实现
1)初始化棋盘 game_init();
2)下棋 play_chess();
3)更新棋盘 update_chessbroad();
4)判断输赢 judge();
5)游戏结束 game_over();
最后再依次调用这些函数
int main()
{
game_init();
while(1)
{
play_chess();
update_chessbroad();
if(juede() == 1)
{
break;
}
}
game_over();
}
面向过程编程简单的说其实就是,先确定main函数的内容,也就是说先明确第一步干啥,第二步干啥,然后再一步步完成这些操作,下一步的完成依赖于上一步的完成。
C语言能够完整的用代码体现出面向过程编程思想,所以我们称C语言是一门面向过程的编程语言。当然在上一节我们说过C++是在C语言的基础上开发出来的,所以C++也是支持面向过程的。(任何语言都需要支持面向过程)
2)面向对象(OOP:object-oriented programming)
基本概念:面对对象编程是也一种解决问题的方法,这种方法的特点是,在遇到问题的时候,先把问题分解成多个对象(整体划分为部分),分解问题的目的不是为了完成某个步骤,而是为了描述某个事物在解决问题步骤中的一些行为(即把特定的行为划分给指定的对象,完成这个行为需要这个指定的对象)。然后,各个对象之间通过相互协作解决最终的问题。//你把这个问题看作是一个团队,团队里面有非常多的人,每个人都能完成一些特定的事,这些人通过协同合作完成最终的任务
例子:制作一个下棋游戏(五子棋)
五子棋作为一个下棋游戏,首先,肯定需要一个棋盘,其次,需要棋手下棋,最后还需要一个公正的裁判。
所以五子棋游戏,可以划分为3个对象,且3个对象的基本行为也可以确定
1)棋盘
2)棋手(下棋)
3)裁判(判断游戏是否结束)
最后对象相互协作完成游戏
int main()
{
棋盘 -> 构建 + 初始化;
棋手 -> 构建 + 选子
裁判 -> 游戏开始
while(1)
{
棋手 -> 下棋
裁判 -> 判断游戏结束
}
}
对比一下面向对象例子 + 面向过程例子,其实可以发现这两种编程思想的区别其实就是,问题是一个人解决(面向过程)还是多个人解决(面向对象)的问题。
面向对象编程简单的说,就是把一个问题划分为若干部分,每个部分的内容交由某个实体完成,最后实体间通过相互协作完成任务(就好像一个公司划分为若干个部门,每个部门各司其职,各个部门相互合作维持公司的运转)。
面向对象编程有三个特点:封装、继承、多态,C++是一门支持面向对象编程的程序设计语言,在C++中实现了这三个操作,而像JAVA,C#等面向对象编程语言也必定有这些内容,只是在语法上由略微区别,那么接下来的学习就是从面向对象编程的3个特点开始,首先我们需要学习一下实现这3个特点的基础 => 类和对象。
2.类与对象
类:class
类是现实世界/思维世界中的实体在计算机中的体现,也就是说,你所想与所见都可以在计算机中用类去描述。
简单的说:
类和结构体是相似的,也是一种构造类型,可以构建新的类型去描述一个实体属性+行为
实体属性:用于描述实体信息的一些变量。
圆:圆心、半径
实体的行为:主要用于描述实体相关的函数。(能够从实体上得到什么,或者说能通过这个实体构建什么)
圆:周长、面积
对象:object
简单的说,对象就是类的实例
更简单点,对象就是个"变量",由类这个类型构建出的变量。
类的格式
类是一种构造类型,其格式和结构体差不多
格式:
class 类名
{
访问修饰符:
成员变量1;
成员变量2;
...
访问修饰符:
成员函数1;
成员变量;
};//在类的内容中,一般只有变量和函数。
变量:描述实体的属性
函数:描述实体的行为访问修饰符? 在这之前,我们先了解下面对对象的第一个特点"封装"
封装:对内访问数据,对外屏蔽数据(属性),但是对外提供接口(函数),在一个类的结构中,希望这个类所表示实体的属性是不能被外界对象随意的修改的。
struct stu
{
char *name;
int age;
float high;
int setage(int newage)
{
age = newage;
}
};
stu s;
s.age = 100;//而在封装的概念中,觉得类中的属性,不应该像这样能够被外界随意的修改=》对外屏蔽数据
s.high = 182.1;//可以随意的修改结构体内部的成员
s.setage(12); //我希望你在修改数据的时候,是通过这个函数去操作的 => 对外提供接口
对内访问数据 => 结构体/类内成员函数能够无视限制,直接操作成员变量而封装功能的具体实现 => 通过访问修饰符 =》3个关键字
修饰符 | 访问限制 |
---|---|
public(共有的) | 无限制,可以通过对象名直接访问数据(函数/接口) |
private(私有的) | 类内成员可访问,类外不可访问 |
protected(保护的) | 类内成员可访问,类外不可访问(继承属性) |
例子:2021/09/07写的
现在我需要使用类描述现实世界中的实体 => 打工仔
class work_people
{
private: //一般属性是私有的,所以使用private
char *name;
int age;
int price;//薪水
bool single_status; //单身
public://一般行为都是共有的,使用public
void work()
{
cout << "working" << endl;
}
void eat()
{
cout << "eatting" << endl;
}
char* get_name()
{
return name;//name和函数在同一个作用域内,可以直接使用
}
int get_price()
{
return price;
}
};
练习:
定义一个圆类,属性,圆心坐标,半径
行为:面积,周长
数据的初始化 => 写一个成员函数赋值即可
ps:类内函数默认是内联属性的=>除非你的构造函数就几行,否则我建议你写类外.
3.类的构造函数
在类对象创建时,我们需要给类成员进行初始化,而private属性成员是无法通过初始化列表去初始化的,解决这个问题的方式,类的构造函数.
类的构造函数的格式:
类的构造函数必须是类的成员函数 => 属于这个类才能访问类的私有成员
类的构造函数没有返回值类型 => 它的调用是由编译器优化的,由编译器写调用代码,不是由你手动调用的.
类的构造函数的函数名和类名是一样的.
class stu
{
stu()//类的构造函数
{
内容
}
}
构造函数的调用时机:
当我们使用一个类去构建一个新的对象的时候,就会主动调用构造函数,这可以为我们省略一行调用初始化函数的代码,也是为了防止某些粗心的程序员这样写(int a; a+=100 初值都没有,就去计算?)
比如:
stu a; //这条语句中包含了一个构造函数调用
构造函数是支持重载的,所以可以写不同的构造函数,支持类以不同的方式去初始化.
示例代码:
constructor.cpp
ps:如果没有对应类型的构造函数,而你在构造对象时去调用,那么编译出错
//constructor.cpp代码文件
#include<iostream>
using namespace std;
class A
{
private:
int m, n;
public:
A()//构造函数必须要是public的,否则对象会无法构建
{
cout << "A()" << endl;
}
A(int a, int b)
{
cout << "A(int,int)" << endl;
m = a, n = b;
}
};
int main()
{
A a;//对象调用时,会自动调用构造函数 你可以理解为A a;语句之后默认添加了 a.A();这句话
A b(1,2); //A a{1,2};
//A c{1,2}; //需要调用带参数构造函数时,需要使用{}包括参数列表
//A d(); //err 函数声明。。。二义性,编译不报错,但是运行会有问题
//A e{}; //ok
//A a; 在类中构造函数是不能省的,当然你省略了,那么编译器会自动生成一个无参的构造函数,内容默认是把成员的值赋0
//a.show(); //成员未赋初值,直接输出。。。
}
4.构造函数初始化列表
格式:
class 类名
{private:
public:
类名():成员的名字(值列表),成员的名字(值列表).. //成员如果是普通类型那么就是直接赋值
{//成员如果是类构建的对象,那么就去调用对应的构造函数
}
};
具体情况见car.cpp我们写一个圆类,圆有圆心,那么请构建一个圆心类(保存坐标就行),使得圆心类对象作为圆类的成员对象.具体情况见ex1.cpp
//ex1.cpp代码文件
#include<iostream>
using namespace std;
class point
{
private:
int x, y;
public:
point(int vx, int vy)
{
x = vx;
y = vy;
}
};
class circle
{
private: //一般属性是私有的,所以使用private
point center;
int r;
public://一般行为都是共有的,使用public
circle(int x, int y, int r) :center(x, y), r(r) {}
double get_area()
{
return r * r*3.14;
}
double get_permiter()
{
return 2 * r*3.14;
}
};
class A
{
public:
int a, b;
};
int main()
{
//point p = {1,2}; //在结构体中是可以使用初始化列表去初始化结构体对象的成员
//类可不可以这么玩呢?
//circle q = {{1,2},3};
///circle q{ 1,2,3 };
//A a = {1,2};//能不能使用初始化列表去初始化一个类对象,在目前来看,public属性是关键的
//circle p;
circle p(10,10,12);
cout << p.get_area() << endl;
cout << p.get_permiter() << endl;
}
//car.cpp代码文件
#include<iostream>
using namespace std;
class wheel //轮子类
{
private:
int r;
public:
//轮子的行为???暂不处理
wheel(int vr)
{
r = vr;
cout << "wheel" << endl;
}
};
class car //车类
{
private:
wheel w;//先假设只有一个轮子吧
//车灯,车架,方向盘,座椅类太多了,先避免下
public:
int x;
car(int vr) :w(vr), x(vr)
{
cout << "car" << endl;
//w.r = vr;//???语法上判断,觉得权限有问题,肯定有问题
}
};
int main()
{
car c(1);
cout << c.x << endl;
}
注意:使用初始化列表时,先完成构造的是成员类对象,当前类对象是后完成的。
问题1:如何在当前类中访问成员类的内容 => 通过接口
示例代码 => car_v2.cpp
问题2:相同类构建的两个对象能不能获取对方的私有成员信息呢
可以 => class_v2.cpp
private所表示的类内成员可访问,指的是类构造任意对象只要能够使用类内函数,就能访问类的内容。
//car_v2.cpp代码文件
#include<iostream>
using namespace std;
class wheel //轮子类
{
private:
int r;
public:
//轮子的行为???暂不处理
wheel(int vr)
{
r = vr;
cout << "wheel" << endl;
}
int get_r()
{
return r;
}
};
class car //车类
{
private:
wheel w;//先假设只有一个轮子吧
//车灯,车架,方向盘,座椅类太多了,先避免下
public:
car(int vr) :w(vr)
{
cout << "car" << endl;
//w.r = vr;//???语法上判断,觉得权限有问题,肯定有问题
}
void show()
{
//w.r; //不行 r是w的私有,
cout << w.get_r() << endl; //ok
}
};
int main()
{
car c(3);
c.show();
//c.w.r; //w就是个私有的,怎么玩哦
}
//class_v2.cpp代码文件
#include<iostream>
using namespace std;
class A
{
private:
int a,b;
//A c;//肯定不行
public:
A(int va,int vb)
{
a = va;
b = vb;
}
void info(A &b)//在一个类的内容,没有详细的声明,那么你只能使用类的引用/指针类型
{
cout << b.a << b.b << endl;
}
};
int main()
{
A a{1,2},b{2,3};
a.info(b);//info是class A类中函数,所以可以访问类的私有成员,因为private可被类内成员访问
b.info(a);
A c[3]={{1,2},{2,3},{3,4}};//类对象数组,其实和结构体数组一样
}
5.拷贝构造函数
在类对象构建时,我们不是总是使用常量值去给类对象初始化的,有时我们需要使用已存在类对象去初始化一个新创建的对象,这个时候,构造函数的参数必然会变成一个类的引用(因为类的名字不能表示类的地址),而像这种参数是当前类引用类型的构造函数 => 拷贝构造函数。
拷贝构造函数的格式:
class 类名
{
public:
类名(const 类名 &对象名){//const 引用指的是我不想修改这个形参的值,只是读取使用各种赋值操作 => 把已有类的内容复制到新类中。
}
};
示例代码:copy_constructor.cpp拷贝构造函数的调用情况:
小结:在使用一个已存在的对象初始化一个新的对象
实际场景:
1.函数的参数 => 实参给形参传参时会调用拷贝构造去初始化形参的值。
2.函数的返回值 => 编译器未优化的版本上,如果函数的返回值返回的是一个对象,那么隐藏的操作是,先把对象copy到一块临时内存中,如果函数的返回值有一个新对象接收,那么就再调用一次拷贝构造函数,没有接收就释放了。
3.在一个新对象创建的时候直接使用拷贝构造。
这是一个笔试点,请记住!!
如果我想避免拷贝构造函数调用(函数传参时) => 引用拷贝的两种情况: =>考点
浅拷贝:单存的赋值
A(const A& b)//因为引用不触发拷贝构造
{
m = b.m;
n = b.n;
cout << "A(A&)" <<endl;
}
深拷贝:如果我类中有个指针变量,那么。。。。
char *p = malloc(100);
char *q = p;
free(q);
free(p);
示例代码: array.cpp
ps:一般如果你不写拷贝构造函数,那么编译器会主动的帮你的加一个拷贝构造函数,但是这个拷贝构造函数是个浅拷贝的构造函数,如果你的内容有指针操作就会有问题了,一般成员对象有指针类型 =》 自己写拷贝构造,其他情况,可以暂时省略。
//copy_constructor.cpp代码文件
#include<iostream>
using namespace std;
class A
{
private:
int m,n;
public:
A(int a,int b)
{
m = a;
n = b;
cout << "A()" <<endl;
}
A(const A& b)//因为引用不触发拷贝构造
{
m = b.m;
n = b.n;
cout << "A(A&)" <<endl;
}
void show()
{
cout << m << n << endl;
}
};
A& func(A &a)//形参创建的新对象被实参所初始化
{
a.show();
return a;//return a返回一个对象,此时会先把这个对象拷贝到一块临时内存上
}
int main()
{
A a{1,2}; //正常的构造一个对象,使用构造函数
//A b{a}; //使用存在对象a去初始化新对象b
A b = a;//只有在定义一个新对象时,=表示拷贝
//A c;
//c = a; //不是拷贝,是赋值
//A c(a); //与上面是一样的效果,(){}上,只有无参才有区别
//A &b = func(a);//因为有对象接收返回值,所有直接调用拷贝,把内容复制到新对象上(编译器的优化)
//也就是说一个函数调用过程中会发生多次拷贝
}
//array.cpp代码文件
#include<iostream>
#include<cstdlib>
#include<cstring>
using namespace std;
class arr
{
private:
int *a; //用于保存数组数据的
int n; //用于保存数组元素个数的
int size; //需要保存数组最大元素个数
public:
arr(int vsize)
{
a = (int*)malloc(sizeof(int)*vsize);
n = 0;
size = vsize;
}
arr(const arr& b)
{//深拷贝
n = b.n;
size = b.size;
//a = b.a; //err ,可能导致double free
a = (int*)malloc(sizeof(int)*size);
memcpy(a,b.a,sizeof(int)*size);
}
~arr()//析构函数
{
free(a);
cout << "~A()" << endl;
}
};
/*
NAME
memcpy - copy memory area
SYNOPSIS
#include <string.h>
void *memcpy(void *dest, const void *src, size_t n);
拷贝内存函数
dest:目标地址
src:源地址
n:拷贝内容的长度,字节单位
把src地址中长度为n的数据拷贝到dest中
dest比src短,肯定就炸了
*/
int main()
{
arr a(10);
arr b(a);
//free(a.a); //err,访问不了
cout << "hehehe" << endl;
}
//规则: 先构造后析构,后构造的先析构 入栈操作
memcpy函数
/*
NAME
memcpy - copy memory area
SYNOPSIS
#include <string.h>
void *memcpy(void *dest, const void *src, size_t n);
拷贝内存函数
dest:目标地址
src:源地址
n:拷贝内容的长度,字节单位
把src地址中长度为n的数据拷贝到dest中
dest比src短,肯定就炸了
*/
6.析构函数
在一个类中,如果我们在对象的构造函数中给成员对象申请了空间,那么我们应该在这个对象生命消亡的时候去释放这段空间,什么时候这个对象会消亡呢? => 作用域范围过了就没了,这不就意味着在}前,我要写一堆释放空间代码 => 浪费时间。。。在类中,有一个函数叫析构函数,这个函数的特点是当对象消亡的时候,会自动调用这个函数,再因为这个函数是类中的,所以也不需要考虑访问修饰符的限制。。。,所以我们一般在析构函数中释放在构造函数中申请的空间
格式:
~类名(无参数) //析构函数一般是公用的,且在类内的
{
内容
}
示例代码:
array.cpp
作业:
1.在之前圆形类的基础上添加一个函数,判断两个圆是否相交。
2.设计一个矩形类,属性为左上角与右下角两个点的坐标,行为:求面积+周长。
3.设计一个树类,属性是树龄,行为是 grow=>是的树龄增加一定的值(自己设定),age => 输出树的年龄。
4.完善我的数组类,增加插入+排序操作
见homework1-4.cpp
//homework1.cpp代码文件
#include <iostream>
#include <math.h>
using namespace std;
/*
1.在之前圆形类的基础上添加一个函数,判断两个圆是否相交
*/
class point
{
private:
int x;
int y;
public:
point(int a,int b)
{
x = a;
y = b;
}
double point_distance(point &another)//两点之间的距离
{
int d_x = x - another.x;
int d_y = y - another.y;
double dis = sqrt(d_x*d_x + d_y*d_y);
return dis;
}
};
class circle
{
private:
point center;
int r;
public:
circle(int x,int y,int r):center(x,y),r(r)
{
}
int get_r()
{
return r;
}
bool intersect(circle &another)
{
int rr = r + another.r;//两个半径之和
double dis = center.point_distance(another.center);
//center.x => 我想以这种形式直接去访问类内的私有成员 => 友元(friend)
if(dis <= rr)
{//相交
return true;
}
else
{
return false;
}
}
};
int main()
{
circle p{10,10,12};
circle q{1,2,1};
if(p.intersect(q) == true)
{
cout << "相交" << endl;
}
else
{
cout << "不相交" << endl;
}
return 0;
}
//homework2.cpp代码文件
/*
2.设计一个矩形类,属性为左上角与右下角两个点的坐标,行为:求面积+周长
*/
#include<iostream>
using namespace std;
class rectangle
{
private:
int x1;
int y1; //第一个点坐标
int x2;
int y2; //第二个点坐标
public:
rectangle(int a, int b, int c, int d)
{
x1 = a;
y1 = b; // 第一个点赋值
x2 = c;
y2 = d; // 第二个点赋值
}
int S(rectangle r)
{
/*
cout<< r.x1 << endl;
cout<< r.x2 << endl;
cout<< r.y1 << endl;
cout<< r.y2 << endl;
*/
int k = (r.x2 - r.x1)*(r.y1 - r.y2);
/*
if( k <0)
{
k = -k;
}
*/
return k;
}
int D(rectangle r)
{
int Dc = 2 * (r.x2 - r.x1) + 2 * (r.y1 - r.y2);
return Dc;
}
};
int main()
{
rectangle s{ 1,5,6,2 };
//std::cout <<"sdada"<<std::endl;
//int S=s.S(s);
//int D=s.D(s);
std::cout << s.S(s) << std::endl;
std::cout << s.D(s) <<std:: endl;
}
//homework3.cpp代码文件
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
using std::cout;
using std::endl;
/*
3.设计一个树类,属性是树龄,
行为是 grow = > 是的树龄增加一定的值(自己设定),
age = > 输出树的年龄。*/
class tree
{
private:
int age;
int high;//当年龄到了,high增加
public:
tree(int h) :high(h) {}
void grow()
{
//当age到达一定值时,grow增加
if (high > 10)
{
age = sqrt(high) * 2;
if (high < 20)
{
age += sqrt(high);
}
if (high > 20)
{
age += sqrt(high - 2);
}
}
else
{
age = sqrt(high) * 3;
}
cout << age << endl;
}
int get_age()
{
return age;
}
};
int main()
{
tree p{ 22 };
p.grow();
}
//homework4.cpp代码文件
//4.完善我的数组类,增加插入+排序操作。
#include<iostream>
#include<cstdlib>
#include<cstring>
using namespace std;
class arr
{
private:
int *a; //用于保存数组数据的
int n; //用于保存数组元素个数的
int size; //需要保存数组最大元素个数
public:
arr(int vsize)
{
a = (int*)malloc(sizeof(int)*vsize);
n = 0;
size = vsize;
}
bool insert_arr(int x)
{
if (n < size)
{
a[n] = x;
n++;
return true;
}
else
{
return false;
}
}
void arr_sort()
{
if (n < 2)
{
return;
}
int m, i, j;
for (i = 0; i < n - 1; i++)
{
for (j = 0; j < n - i - 1; j++)
{
if (a[j] < a[j + 1])//按从大到小排序。
{
m = a[j];
a[j] = a[j + 1];
a[j + 1] = m;
}
}
}
}
void prin()
{
for (int i = 0; i < n; i++)
{
cout << a[i] << " ";
}
cout << endl;
}
arr(const arr& b)
{//深拷贝
n = b.n;
size = b.size;
//a = b.a; //err ,可能导致double free
a = (int*)malloc(sizeof(int)*size);
memcpy(a, b.a, sizeof(int)*size);
}
~arr()//析构函数
{
free(a);
cout << "~A()" << endl;
}
};
int main()
{
arr a(10);
int x;
while (1)
{
cin >> x;
if (x == 0)
{
//a.prin();
break;
}
a.insert_arr(x);
}
a.arr_sort();
a.prin();
}
7.new与delete
在C语言中,我们可以使用指针的方式去动态的创建一个数组,在C++中也同理,如果我们要动态的创建一个类对象数组,那么必然需要使用到malloc,但是使用malloc时,往往会出现如下的问题
1.如果我使用malloc为一个类对象分配了空间,那么按道理来说,给你分配了空间,就等同于你这个对象成功创建,此时应该要调用构造函数初始化类对象的内容 => malloc 没有这个机制.
见代码:
space.cpp
2.free也不会调用析构函数
为了解决这个问题,C++中创建了两个新的"运算符"去替代malloc与free申请/释放heap的功能。
new => malloc
delete => freenew使用方式:
类型 * 指针变量名 = new 类型[数组元素个数]{初始化列表};
//初始化列表为空,默认调用无参构造函数,不为空则调用对应参数个数构造函数
//如果数组元素个数不为空(值>1),那么初始化列表形式一般是这样{{第1个元素的初始值},{第二个},{第三个}}
例子:
int *a = new int; //单个元素
int *b = new int[3]; //3个元素调用无参构造函数 => int 不是类,默认赋值0
int *c = new int[3]{{1},{2},{3}}; //3个元素a[0] = 1,a[1] = 2,a[2] =3delete使用方式:
非数组释放: delete 变量名
数组释放: delete [ ]变量名
delete a;
delete []b;
delete []c;
//space.cpp代码文件
#include<iostream>
#include<cstdlib>
using namespace std;
class A
{
private:
int m;
public:
A()
{
m = 0;
cout << "A()" << endl;
}
A(int a, int b)
{
cout << "A(int ,int)" << endl;
}
~A()
{
cout << "~A()" << endl;
}
};
int main()
{
/*
A *a = (A*)malloc(sizeof(A));
//单独的定义一个指针变量,不会调用构造函数的,
//所以你也可以使用指针去避免函数调用时,
//使用拷贝构造函数的机制。
//得出结论,malloc之后并没有调用构造函数
free(a);//free时也不会有析构函数调用
*/
A *a = new A;//使用new会在分配空间成功之后调用构造函数,且new会自动匹配类型。
A *b = new A[3];//new失败了会返回空指针,在C++中空指针是nullptr,不是 NULL。
//A *c = new A[3]{{1,2},{2,3},{3,4}};
delete a;//调用析构函数
delete[]b;
}
8.this指针
一个类可以构建非常多的对象,这些对象中都有自己的成员变量,这些对象的成员函数明明都是一样的,为什么对象调用自己的函数的时候,一定能输出自己的值呢?
class A
{
private:
int a,b;
public:
A(int va,int vb):a(va),b(vb){}
void show()
{
cout << a << b << endl;
}
};
int main()
{
A a(1,2);
A b(2,3);
a.show(); //输出1 2
b.show(); //输出2 3 ,我们函数明明是一样的,凭啥输出不一样呢
}
这种同类对象调用相同函数结果不同 => 编译器的类函数修改,编译器会给类中的每一个非静态函数添加一个参数,这个参数是一个 类名 *const this(名字),也就是说,void show() => void show(A *const this),怎么验证这个东西呢?GDB看看20 a.show(); //输出1 2
(gdb) s
A::show (this=0x7fffffffe008) at this.cpp:13
13 cout << a << b << endl;
//this.cpp代码文件
//this.cpp代码文件
#include<iostream>
#include<cstdlib>
using namespace std;
class A
{
private:
int a, b;
public:
A(int va, int vb) :a(va), b(vb) {}
void show()
{
cout << a << b << endl;
}
/* 上面这个show在编译之后的内容如下
void show(A *const this)
{
cout << this->a << this->b << endl;
}
*/
};
int sum(int a, int b)
{
return a + b;
}
int main()
{
A x(1, 2);
A y(2, 3);
//sum(10, 12);
x.show(); //输出1 2 show(&x);
y.show(); //输出2 3 ,我们函数明明是一样的,凭啥输出不一样呢
}
9.类的其他形式的成员
除了普通成员对象和类成员对象/指针对象,类成员对象还可以是以下内容
1.引用
当类的成员对象是引用类型的时候,在对象构建时,需要使用构造函数初始化列表,初始化一个引用变量。
class A
{
private:
int &b;
public:
A(int va):b(va){}
void show(){cout << b << endl;}
};
//variable.cpp代码文件
#include<iostream>
#include<cstdlib>
using namespace std;
class A
{
private:
int &b;
const int a;
public:
A(int& va) :b(va), a(10) {} //引用必须使用初始化列表去操作
void show() { cout << b << a << endl; }
};
int main()
{
int b = 10;
A a(b);
a.show();
}
2.const
当类的成员对象是const类型的时候,在对象构建时,需要使用构造函数初始化列表,初始化一个常量。
3.static(考点)
当类的一个成员对象是const类型的时候,那么此时这个成员对象是属于这个类,而不是属于这个类构建出来的对象,这个static对象不会因为类构建类对象时拷贝一份到类对象空间中,而是所有的类对象共享这个static成员,概念如图
static成员对象的使用方式:
1.如果你在定义一个class类型时,声明了一个static成员对象,那么你必须定义这个对象才能去使用。
定义:
class A
{
private:
static int a;//静态对象声明
public:
A(){}
};
int A::a = 10;//这是静态成员定义
静态对象类型 作用域名字(类名)::静态成员名 = 初值;使用:
如果是public属性的:
A::a = 100;
A b;
b.a = 100;
且在类内可以直接访问
如果是私有的,那就玩不动
具体见代码:
static.cpp如果类没有构建对象,且静态成员是私有的,但是我想修改静态成员的值(在初始化操作之后),除非你能获取静态变量的地址,否则就玩不动,但是C++设计时,肯定会考虑到这种问题 =》 使用静态成员函数访问
写法: 函数声明时 + static关键字
使用: 和静态成员变量一致静态成员函数没有this指针,所以无法访问类内非静态成员。
小结:
使用 =》 声明写静态关键字
访问 =》 公有: 通过 类名::静态成员(变量/函数)
类对象.静态成员(变量/函数)
私有: 只能通过类内成员函数
静态函数的存在只为在无类对象时能够访问私有静态成员。以上内容最大的用途 => 单例设计模式。
单例模式 => 指在程序的运行过程,这个类只能构建一个对象/由这个类构建出来的对象最多只能存在一个,也就是说你只能在当前类对象不存在的时候去构建一个新对象。
这种操作怎么实现的呢:
1) 先把构造函数设为私有的
A a; =>这种方式直接构建会失败,因为无法访问私有的构造函数
2) 需要有一个静态成员变量表示你创建的这个类的对象是第一个。
3) 需要有一个静态成员函数访问那个静态成员变量,以构建对象。具体见代码: singleton.cpp
//static.cpp代码文件
//static.cpp代码文件
#include<iostream>
#include<cstdlib>
using namespace std;
class A
{
private:
static int m;//这里仅仅是个声明
int va;
public:
static int n;
A():va(100)
{
cout << "A()" << endl;
}
void show();
static void set()//静态成员要是共有的,不能是私有
{//这个函数无法转化为 static void set(A *const this)形式
//cout << va << endl;//静态函数不能访问类内对象
cout << "static func" << endl;
cout << m << endl;
m = 100;
cout << m << endl;
}//使用这种形式,你就是通过公有的静态成员函数去修改任意类型的静态成员变量的值
};
int A::n = 10;//类内静态成员的定义,平常的函数也可以这么操作
int A::m = 12;
void A::show()
{
cout << n << m << endl; //类内,无论是共有还是私有,照常使用
}//建议 =》 强行执行,所以类内函数,在类内只写声明,类外写定义
int main()
{
A::set();//这种操作使得我们不需要定义类对象就能使用这个函数
//A a;
//a.set();
//cout << a.m << endl;//私有无法访问
//cout << A::m << endl;//私有无法访问
}//只要类A定义完成,且静态变量定义完成,那么你就能使用类名::变量名的方式访问静态成员变量
Singleton单例模式
Singleton.cpp
//Singleton.cpp代码文件
#include<iostream>
using namespace std;
class Singleton
{
private:
Singleton();//将构造函数设为私有的,那么就无法直接创建对象了
static int num;//假设这个静态成员用于计数
//类内私有静态成员变量只能被类内成员访问
public:
Singleton(const Singleton&);
static Singleton Singleton_create();//一个共有的静态成员函数去访问num
void show()
{
cout << num << endl;
}
~Singleton();
};
int Singleton::num = 0;
Singleton::Singleton()
{
num += 1;//每构建一个对象,那么num的值+1
cout << "Singleton()" << endl;
}
Singleton::~Singleton()
{
cout << "~Singleton()" << endl;
}
Singleton::Singleton(const Singleton &a)
{
cout << "Singleton(Singleton &a)" << endl;
}
Singleton Singleton::Singleton_create() //静态虽然特殊,但也是类成员,所以能够调用构造函数
{
if (num == 0)//判断类对象是不是没创建过
{
Singleton s;//Singleton_create是类内成员,可以访问其他类内成员
return s;
}
// return ;
}
int main()
{
Singleton s = Singleton::Singleton_create();//接收new Singleton返回的地址
s.show();
//Singleton s1 = Singleton::Singleton_create();//nullptr
//Singleton *s2 = Singleton::Singleton_create();//nullptr
//Singleton::num = 1; //这样能被修改
//Singleton a;//构造函数私有,无法创建
}
Singleton_v1.cpp
//Singleton_v1.cpp代码文件
#include<iostream>
using namespace std;
class Singleton
{
private:
Singleton();//将构造函数设为私有的,那么就无法直接创建对象了
static int num;//假设这个静态成员用于计数
//类内私有静态成员变量只能被类内成员访问
public:
static Singleton *Singleton_create();//一个共有的静态成员函数去访问num
void show();
~Singleton();
};
int Singleton::num = 0;
Singleton::Singleton()
{
num += 1;//每构建一个对象,那么num的值+1
cout << "Singleton()" << endl;
}
Singleton::~Singleton()
{
num -= 1;
}
void Singleton::show()
{
cout << num << endl;//类内函数可以直接访问静态成员
}
Singleton * Singleton::Singleton_create() //静态虽然特殊,但也是类成员,所以能够调用构造函数
{
if (num == 0)//判断类对象是不是没创建过
{
Singleton *s = new Singleton;//Singleton_create是类内成员,可以访问其他类内成员
return s;
}
return nullptr;
}
int main()
{
Singleton *s = Singleton::Singleton_create();//接收new Singleton返回的地址
s->show();
Singleton *s1 = Singleton::Singleton_create();//nullptr
Singleton *s2 = Singleton::Singleton_create();//nullptr
//Singleton::num = 1; //这样能被修改
//Singleton a;//构造函数私有,无法创建
}
Singleton_v2.cpp
// Singleton_v2.cpp代码文件
#include<iostream>
using namespace std;
class Singleton
{
private:
Singleton();//将构造函数设为私有的,那么就无法直接创建对象了
static Singleton *s;
public:
static Singleton *Singleton_create();//一个共有的静态成员函数去访问num
void show();
~Singleton();
};
Singleton* Singleton::s = nullptr;
Singleton::Singleton()
{
cout << "Singleton()" << endl;
}
Singleton::~Singleton()
{
s = nullptr;
}
void Singleton::show()
{
cout << "num "<< endl;//类内函数可以直接访问静态成员
}
Singleton * Singleton::Singleton_create() //静态虽然特殊,但也是类成员,所以能够调用构造函数
{
if (s == nullptr)//判断类对象是不是没创建过
{
s = new Singleton;//Singleton_create是类内成员,可以访问其他类内成员
return s;
}
return s;
}
int main()
{
Singleton *s = Singleton::Singleton_create();//接收new Singleton返回的地址
s->show();
Singleton *s1 = Singleton::Singleton_create();//nullptr
Singleton *s2 = Singleton::Singleton_create();//nullptr
//这种方式后续再加上共享指针就可以解决 一个指针delete,其他指针无法使用问题
//Singleton::num = 1; //这样能被修改
//Singleton a;//构造函数私有,无法创建
}
Singleton_v3.cpp
// Singleton_v3.cpp代码文件
#include<iostream>
using namespace std;
class Singleton
{
private:
Singleton();//将构造函数设为私有的,那么就无法直接创建对象了
public:
static Singleton *Singleton_create();//一个共有的静态成员函数去访问num
void show();
~Singleton();
};
Singleton::Singleton()
{
cout << "Singleton()" << endl;
}
Singleton::~Singleton()
{
}
void Singleton::show()
{
cout << "heheh" << endl;//类内函数可以直接访问静态成员
}
Singleton * Singleton::Singleton_create() //静态虽然特殊,但也是类成员,所以能够调用构造函数
{
static Singleton *s = new Singleton; //函数内静态成员特点,只会在第一次函数调用时执行
return s;
}
int main()
{
Singleton *s = Singleton::Singleton_create();//接收new Singleton返回的地址
Singleton *s1 = Singleton::Singleton_create();//接收new Singleton返回的地址
s1->show();
//Singleton::num = 1; //这样能被修改
//Singleton a;//构造函数私有,无法创建
}
10.类的友元
当一个类中的成员的约束是private时,类外对象是无法访问其中内容的,但是有时总会有一些情况需要去使用,比如作业1 =》 判断两个圆是不是相交的 =》 看代码。。。。如果我想在当前类/函数中访问其他类的私有成员 => 友元(friend)
1.普通友元函数(非类内函数作为类的友元)
使用方式:只需要在类内的任意位置(约束可以忽略),写上如下的声明即可
friend 返回值类型 函数名(参数列表);
比如:
class A
{
int num; //private
friend int get_num(A& a);
};int get_num(A &a)
{
cout << num << endl; //不行的,没有this玩不动
cout << a.num << endl;
}
int main()
{
A a;
get_num(a);
}示例代码:
frind_v1.cpp
//friend_v1.cpp代码文件
#include<iostream>
using namespace std;
class A
{
int num; //private
friend void get_num(A& a);
public:
A(int n);
};
A::A(int n)
{
num = n;
}
void get_num(A &a)
{
//cout << num << endl; //不行的,没有this玩不动
cout << a.num << endl;
}
int main()
{
A a(12);
get_num(a);
}
2.类内的函数作为另外一个类的友元函数
使用方式:只需要在类内的任意位置(约束可以忽略),写上如下的声明即可
friend 返回值类型 类名::函数名(参数列表);
示例代码:
frind_v2.cpp
//friend_v2.cpp代码文件
#include<iostream>
using namespace std;
class pig; //这个声明一写,在没确定内容之前,代码中只能使用引用/指针
class butcher
{
private:
//属性自己写吧
public:
void kill_pig(pig &p);
};
class pig
{
private:
//属性自己写吧
void die()
{
cout << "ao ao ao ao ao!!! e" << endl;
}
public:
friend void butcher::kill_pig(pig &p);
};
void butcher::kill_pig(pig &p)
{
p.die();
}
int main()
{
pig p;
butcher b;
b.kill_pig(p);
}
3.类内的所有函数作为另外一个类的友元函数 => 友元类
使用方式:只需要在类内的任意位置(约束可以忽略),写上如下的声明即可
friend class 类名;
示例代码:
frind_v3.cpp
ps:关系是单向的,我把你当朋友,但是你不一定是我的朋友
练习:
写个复数类,成员 实部和虚部
写个友元函数,求两个复数类对象的和
// frind_v3.cpp代码文件
#include<iostream>
using namespace std;
class lick_dog;
class goddess //女神
{
private:
char *name;
int money;
int td[3]; //三围
public:
void dancing(lick_dog &d);
void sing(lick_dog &d);
void get_money(lick_dog &d);
};
void goddess::dancing(lick_dog &d)
{
get_money(d);
cout << "dancing" << endl;
}
void goddess::sing(lick_dog &d)
{
get_money(d);
cout << "sing" << endl;
}
class lick_dog //舔狗
{
private:
char *name;
int money;
int patience; //耐心值
public:
void lick();
void get_td(goddess &g);
friend class goddess;
};
void lick_dog::get_td(goddess &g)
{
//cout << g.td[0] << g.td[1] << g.td[2] << endl;//单向关系,不能访问
}
void lick_dog::lick()
{
money -= 100;
}
void goddess::get_money(lick_dog &d)
{
d.money -= 1000;
money += 300;
}
int main()
{
goddess g;
lick_dog dog;
g.dancing(dog);
dog.lick();
g.sing(dog);
}
11.运算符重载
在上一个练习中我们写了一个求两个复数类对象和的函数。。。。
我们回想一下,我们平时求两个普通数据的和,一般是 a+b...,a+b为啥能够直接加呢?肯定是因为编译内部定义了基础类型的加减乘除的基本运算,那么这个行为编译器可以定义,那么我写代码可以也想这么做。。实现这种操作的方式就是运行符重载
void complex_sum(complex &a,complex &b) => a+b
运算符重载方式:
运算符重载函数向编译器说明这个符号在某个对象的使用上,方式被重新定义了,请编译器在看到这个函数之后,后续使用对应运算符操作这个类对象的时候,请按照我定义的规则去操作。
运算符重载函数格式:
返回值类型 operator运算符(参数列表){自己定义的运算方式}接下来我们从最简单的+法运算符开始:
例子:complex.cpp作业:
1.完善复数类的其他的运算符重载 (-,/,*)
2.构建一个字符串类(String),这个字符串类可以通过一个char *类型的字符串初始化
string(const char *ptr) => string s("qweqweq")
且完成,字符串拼接操作 => 完成string s,s1; => s+s1 =>完成+运算符重载
3.没事刷题 => 75以上的命中率,可以转头去搞搞算法 => 笔试
面试 => 最多是一道现场编程(只考算法),其他都是问答(你简历写啥就问啥,把你写的点的内容,百度下记下来)
//complex.cpp代码文件
//complex.cpp代码文件
#include<iostream>
using namespace std;
//类到底怎么设计,到底要不要拆分
class complex
{
private:
float real;
float vir; //virtual 是关键字,不要起名
public:
complex(float vr, float vv) :real(vr), vir(vv) {}
friend void complex_sum(complex&, complex&);
friend complex &operator+(complex &a, complex &b);
void show();
};
void complex::show()
{
cout << "real = " << real;
cout << " ,vir = " << vir << endl;
}
void complex_sum(complex &a, complex &b)
{
cout << "real = " << a.real + b.real << endl;
cout << "vir = " << a.vir + b.vir << endl;
}
complex &operator+(complex &a, complex &b)
{
a.real += b.real;
a.vir += b.vir;
return a;
}
int main()
{
complex a{ 1.1,2.2 };
complex b{ 5.5,9.9 };
complex c = a + b + a + b;
c.show();
}
//complex_v2.cpp代码文件
//complex_v2.cpp代码文件
#include <iostream>
using namespace std;
/*
1.完善复数类的其他的运算符重载 (-,/,*)
*/
class complex
{
float m_real;
float m_vir;
public:
complex(float real, float vir);
complex(){}
friend complex operator+(const complex &, const complex &);
friend complex operator-(const complex &, const complex &);
friend complex operator*(const complex &, const complex &);
friend complex operator/(const complex &, const complex &);
void show();
};
void complex::show()
{
cout << "real = " << m_real << " ,vir = " << m_vir << endl;
}
complex::complex(float real, float vir)
{
m_real = real;
m_vir = vir;
cout << "complex(f,f)" << endl;
}
//由于不能返回局部变量的引用
complex operator+(const complex &c1, const complex &c2)
{
complex temp{ c1.m_real + c2.m_real,c1.m_vir + c2.m_vir };
return temp; //这种方式虽然会调用很多次构造
//return {c1.m_real + c2.m_real,c1.m_vir + c2.m_vir};//这样也行
//上面两种返回操作的效果是一样的,区别只是,第一种我是显式的调用了构造函数
//第二种,是编译器帮你调用的构造函数,为了保证你返回的数据是complex类型
//但是第二种需要你确保{}内的参数列表有对应的构造函数
}
complex operator-(const complex &c1, const complex &c2)
{
return { c1.m_real - c2.m_real,c1.m_vir - c2.m_vir };
}
complex operator*(const complex &c1, const complex &c2)
{
return { c1.m_real * c2.m_real - (c1.m_vir*c2.m_vir),c1.m_real*c2.m_vir + c2.m_real*c1.m_vir };
}
//不写引用是因为不能返回一个局部变量的引用 => 全局变量不建议
complex operator/(const complex &c1, const complex &c2)
{
float d;
d = c2.m_real*c2.m_real + c2.m_vir*c2.m_vir;
return { (c1.m_real*c2.m_real + c1.m_vir*c2.m_vir) / d, (c1.m_vir*c2.m_real - c1.m_real*c2.m_vir) / d };
}//普通引用只能使用左值初始化,常引用可以使用右值赋予
int main()
{
complex c1(2.0, 3.0);
complex c2(2.0, 4.0);
complex c = c1 + c2 / c1;//c2/c1 之后留下的值是一个右值
//右值不能赋予一个引用对象,引用是需要指向一个已存在变量的,需要指向这个变量的地址(需要一个左值)
c.show();
}
类内函数运算符重载:
运算符分为单目,双目,和三目(a>b?a:b),在正常使用中,一般只会重载单目运算符和双目运算符,如果一个操作符是单目运算符 => 操作数一般只有一个,这个操作数肯定就是调用这个符号的操作数 ;
如果是双目运算符 =》操作数就会有两个,调用运行符的操作数往往是左边的那个 =》 a+b =>a调用符号;
重载的方式上 => 单目运算符,一般都是作为类内运算符重载 ++ -- * 等都是类内函数重载,当然某些特殊的双目运算符也是需要类内重载的,比如: [] << >> += -= 等等,这些符号的特点,这种操作符的特点就是左边的那个操作符必须是类对象。
类内运算符重载函数格式:
返回值类型 operator运算符(参数列表){自己定义的运算方式}
注意:如果运算符重载函数,写在类内,那么参数列表的第一个默认是当前类对象指针(this),且这个顺序无法修改.
例子:
class A
{
public:
operator++(){} => operator++(A *const this){} 等同于这样
operator+(A &b){} => operator+(A *const this,A &b){}
};示例代码:
operator_v2.c小结:
运算符重载:分析下参数列表
双目运算符需要两个
类外:两个参数,第一个参数时运算符左边的,第二个参数是运算符右边的
类内:默认第一个参数是当前类对象指针,所以参数列表只需要写第二个参数
单目运算:不需要参数 => ++/--是个奇怪的东西
因为有前后之分
operator++(); //前++
operator++(int);//后++ ,参数是没用的,只是为了区分
使用时钟类描述吧
//operator_v2.cpp代码文件
//operator_v2.c代码文件
#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;
/*
构建一个字符串类(String),这个字符串类可以通过一个char *类型的字符串初始化
string(const char *ptr) => string s("qweqweq")
且完成,字符串拼接操作 => 完成string s,s1; => s+s1 =>完成+运算符重载
*/
class String
{
char *Pstr;
public:
String();
//explicit String(const char *p);//explicit 显式调用声明
String(const char *p);
~String();
//String operator+(const String &s);//类内函数运算符重载
String &operator=(const String &s);
//下标运算符重载函数 => 实现的功能,可以通过类对象[下标值] 访问Pstr对应下标的字符
char &operator[](unsigned int index); //char p[3] = "ab"; p[1] = 'c', char c = p[2]
void show();
String& operator+=(const String &s);//s1 += s2; // s1+=s2+=s3;
friend String operator+(const String &s, const String &s1);
//a+=a*=a-=a==a
};
String::String()
{
Pstr = nullptr;
}
char &String::operator[](unsigned int index)
{
if (index <= strlen(Pstr))
{
return Pstr[index];
}
else
{//此处应有异常(后续)
//throw => 抛出异常
return Pstr[0]; //先暂定是这个样子
}
}
String& String::operator+=(const String &s)
{
char *p = new char[strlen(Pstr) + strlen(s.Pstr) + 1];
strcat_s(p, strlen(Pstr) + strlen(s.Pstr) + 1, Pstr);
strcat_s(p, strlen(Pstr) + strlen(s.Pstr) + 1,s.Pstr);
delete[]Pstr;
Pstr = p;
return *this;
}
String operator+(const String &s, const String &s1)
{
/*String s2;
s2.Pstr = new char[strlen(s.Pstr)+strlen(s1.Pstr)+1];
strcat(p,s.Pstr);
strcat(p,s1.Pstr);*/
char *p = new char[strlen(s.Pstr) + strlen(s1.Pstr) + 1];
strcat_s(p, strlen(s.Pstr) + strlen(s1.Pstr) + 1, s.Pstr);
strcat_s(p, strlen(s.Pstr) + strlen(s1.Pstr) + 1, s1.Pstr);
return p;//这里是隐式调用构造函数
}
void String::show()
{
printf("%s\n", Pstr);
}
String::String(const char *p)
{
Pstr = new char[strlen(p) + 1];
strcpy_s(Pstr, strlen(p) + 1,p);
}
/*
String String::operator+(const String &s)
{
char *p=new char[strlen(Pstr) + strlen(s.Pstr)+1];
p=strcat(Pstr,s.Pstr);
return String(p);
}
*/
/*
编译器会为每个类生成一个默认的赋值运算符重载函数
*/
String& String::operator=(const String &s)
{
if (strcmp(Pstr, s.Pstr) == 0) //如果两个字符串相等
{
return *this; //返回调用该函数的那个对象
}
else
{
if (strlen(Pstr) >= strlen(s.Pstr))
{
strcpy_s(Pstr, strlen(s.Pstr) + 1, s.Pstr); //注意一下!!!
}
else
{
delete[]Pstr; //释放掉
Pstr = new char[strlen(s.Pstr) + 1];
strcpy_s(Pstr, strlen(s.Pstr) + 1, s.Pstr);
}
return *this;
}
}
String::~String()
{
if (Pstr)
delete[] Pstr;
}
int main()
{
String s1("eeee");
String s2("aaaaaaaaaa");
//cout << s1[0] << endl;
//s1[2] = 'k';
s1 += s2;
s1.show();
}
练习:
写个日期类,实现++ -- 运算符重载操作
年,月,日
//operator_riqi.cpp代码文件
#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;
class riqi
{
private:
int year;
int month;
int day;
public:
riqi(int new_year, int new_month, int new_day);
//析构函数没有
riqi & operator++();//前++
riqi operator++(int);//后++
riqi & operator--();//前--
riqi operator--(int);//后--
void show();
};
riqi::riqi(int new_year, int new_month, int new_day)
{
year = new_year;
month = new_month;
day = new_day;
}
void riqi::show()
{
cout << year << "年" << month << "月" << day << "日" << endl;
}
//前++
riqi & riqi::operator++()
{
day++;
if (day == 30)
{
month++;
day = 1;
if (month == 12)
{
year++;
month = 1;
}
}
return *this;
}
riqi riqi::operator++(int i)//后++
{
riqi tempx(*this);
++(*this);
return tempx;
}
//前--
riqi & riqi::operator--()
{
day--;
if (day == 0)
{
month--;
day = 30;
if (month == 0)
{
year--;
month = 12;
}
}
return *this;
}
riqi riqi::operator--(int i)//后--
{
riqi tempx(*this);
--(*this);
return tempx;
}
int main()
{
riqi riqi_cs{ 2021,1,1 };
//++riqi_cs;
//--riqi_cs;
//riqi cs1(riqi_cs++);
riqi cs2(riqi_cs--);
cs2.show();
riqi_cs.show();
return 0;
}
在上面的代码中,当我们想要输出类内内容的时候,总是需要写个show函数去输出,但是输出普通数据时,我们是可以通过 << 进行输出,我们类也可以通过重载这个运算符进行操作,接下来看下这个运算符重载方式
具体见:operator_v3.cpp
//operator_v3.cpp代码文件
#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;
class Time
{
private:
int hour;//时
int min;//分
int sec;//秒
public:
Time(int, int, int);
Time &operator++();//前++操作
Time operator++(int);//后++操作,这个int没有任何作用,只是用于区分前后
Time operator=(const Time &t);
int operator()(int a, int b, int c);
void show();
friend ostream &operator<<( ostream &out, const Time &t);
friend istream &operator>>( istream &in, Time &t);
friend bool operator==(const Time &t, const Time &t1);
};
Time::Time(int h, int m, int s)
{
hour = h;
min = m;
sec = s;
}
Time Time::operator++(int i)
{
//先把值返回?不可能,返回了我怎么继续计算 =》先保存计算前的值
Time tem(*this); //先使用当前类对象内容初始化一个临时对象,这个临时对象作为返回值
++(*this);//调用前++重载函数进行+1操作
return tem;
}
Time &Time::operator++()
{
sec++;
if (sec == 60)
{
min++;
sec = 0;
if (min == 60)
{
hour++;
min = 0;
if (hour == 24)
{
hour = 0;
}
}
}
return *this; //返回当前类对象
}
//输出运算符重载,一般只能写在类外,输出运算符重载需要借助标准输入输出流中的标准输出流对象
//平时用法 cout << a cout作为<<调用的那个对象,就表示它必须是这个函数的第一个参数,a是输出的对象
//那么只能作为第二个参数,如果这个函数写在类内,那么第一个参数必然是对象自身,这是不行的。。
//会改变原运算符使用方式 a << cout
//cout => 也是一个类构建出的对象 ,类名 ostream
ostream &operator<<(ostream &out, const Time& t)
{//out是输出流对象,输出内容时,输出流对象的状态会改变,所以out不能是const
out << t.hour << ":" << t.min << ":" << t.sec; //输出不要写endl,是为了不改变cout原来的输出形式
return out;
}//cout << a << b << end; 为了维持原使用方式,返回值类型使用输出流对象引用
//cin => 也是一个类构建出的对象 ,类名 istream
istream &operator>>(istream &in, Time& t)
{//in是输入流对象,输入内容时,输入流对象的状态会改变,所以in不能是const
in >> t.hour >> t.min >> t.sec;
return in;
}
//关系运算符重载
//使用 > < != == 关系运算符,判断类对象之间的关系
//关系最后的结果是真假值即可 => 返回值类型都是bool类型
//判断对象间的关系只需要读取数据 => 参数类型 const 引用类型
//上面几种都是双目 (函数建议类外重载)
bool operator==(const Time& t, const Time& t1)
{
if (t.hour == t1.hour && t.min == t1.min && t.sec == t1.sec)
{
return true;
}
return false;
}
//赋值运算符重载
// = 运算符,左边固定是调用者,第二个参数是赋予的值所表示的对象
// 赋值运算符重载函数,一般是写成类内函数
// = 结合性是从右到左,返回值类型 非引用?
Time Time::operator=(const Time& t)
{
hour = t.hour;
min = t.min;
sec = t.sec;
return *this;
}
int Time::operator()(int a, int b, int c)
{
return a + b + c;
}
//函数调用运算符重载 => 目前用不上,STL会用到
//可以把类构建的对象,像函数一样去调用
//比如: Time t; t(1,2,3); =>函数调用运算符重载 重载的效果
//函数调用运算符只能写类内
void Time::show()
{
cout << hour << ":" << min << ":" << sec << endl;
}
int main()
{
Time t{ 14,10,31 };
Time t1(t++);//调用拷贝构造函数,使用++后的t对象给t1初始化
//编译器会自动生成一个拷贝构造函数(浅拷贝版本)
//cin >> t;
cout << t(1, 2, 3) << endl;
}
运算符重载注意事项:
1.不能重载预定义的运算符
int operator+(int,int); // err 你想重新定义1+1
2.只能重载已有运算符,不能发明新的运算符
int operator@(int,int); //err
3.重载运算符优先级以及结合性是不能改变的,且含义必须一致(加法即加法)
4.可重载与不可重载运算符
不可重载:
:: . ?: .* sizeof typeid(求一个对象的类型的) ,const_cast(类型强转),dynamic_cast(类型强转),reinterpret_cast(类型强转),static_cast(类型强转)
不建议重载的:
&& || 逗号运算符 取地址符 =>无法保留符号特性(惰性运算会丢失)
5.以下运算符只能通过成员函数重载
= 赋值运算符 () 函数调用运算符 [] 下标运算符 -> 指针访问类对象运算符可能笔试题会有,以下哪个运算符是不可被重载的?
作业:
String 类的完善
属性:
char *指针
一个 size 表示malloc空间的长度
一个 len 表示当前字符串的长度
行为:
构造函数
析构函数
拷贝构造函数
+= 重载
= 重载
== 重载
c_str => 可以输出char *指针 指针的首地址
<< 重载
>> 重载
预习:
类的泛化关系,关联关系
类的设计原则
//string.cpp代码文件
//string.cpp代码文件
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
class str
{
private:
char *p;
int size;
int len;
public:
str();//构造函数
str(const str &);//拷贝构造函数
str(const char *ptr, int m);//构造函数
str& operator += (const str &b);//+=重载
str& operator = (const str &b);//重载=
void show();
char *c_str();//返回字符串首地址
~str();//析构函数
friend bool operator ==(const str &a, const str &b);
friend ostream& operator <<(ostream &out, const str &b);//重载<<
friend istream& operator >>(istream &out, str &b);//重载>>
};
str::str(const str &b)
{
p = new char[b.size];
size = b.size;
strcpy_s(p, size, b.p);
//cout << p << endl;
len = b.len;
}
char *str::c_str()
{
return p;
}
str::str()
{
p = nullptr;
}
str::str(const char * ptr, int m)
{
size = m;
len = strlen(ptr);
if (ptr == nullptr)
{
p = new char[1];
strcpy_s(p, 1,"\0");
}
else
{
p = new char[strlen(ptr) + 1];
strcpy_s(p, strlen(ptr) + 1,ptr);
}
cout << "str()" << endl;
}
str& str::operator +=(const str &b)
{
char *h = new char[strlen(p) + strlen(b.p) + 1];
strcat_s(h, strlen(p) + strlen(b.p) + 1, p);
strcat_s(h, strlen(p) + strlen(b.p) + 1, b.p);
delete[] p;
p = h;
return *this;
}
str& str::operator =(const str &b)
{
delete[]p;
p = new char[b.size];
size = b.size;
strcpy_s(p, size,b.p);
//cout << p << endl;
len = b.len;
return *this;
}
void str::show()
{
cout << p << endl;
}
bool operator ==(const str &a, const str &b)
{
if (a.size != b.size)
{
cout << "size err" << endl;//空间长度不同
}
if (strcmp(a.p, b.p) == 0)
{
return true;
}
return false;
}
ostream& operator <<(ostream &out, const str &b)
{
out << b.p << " " << b.size << " " << b.len;
return out;
}
istream& operator >>(istream &in, str &b)
{
in >> b.p >> b.size;
return in;
}
str::~str()
{
delete p;
cout << "~str()" << endl;
}
int main()
{
str s("hello", 8);
str g("xqxq", 8);
g.show();
s.show();
if (g == s)
{
cout << "yes" << endl;
}
else
{
cout << "no" << endl;
}
g = s;
g.show();
if (g == s)
{
cout << "yes" << endl;
}
else
{
cout << "no" << endl;
}
//cout << s << endl;
return 0;
}
可以遇到的错误
'strcat': This function or variable may be unsafe. Consider using strcat_s instead. To disable deprecation, use _CRT_SECURE_NO_WARNINGS. See online help for details.
'strcat':此函数或变量可能不安全。 考虑使用strcat_s代替。 要禁用弃用,请使用_CRT_SECURE_NO_WARNINGS。 详情请参阅联机帮助。
修改:
strcpy_s(p, size,b.p);
strcat_s(h, strlen(p) + strlen(b.p) + 1, p);
12.类的内存结构
1.类作为一个构造类型,肯定会有内存对齐机制
2.类的对象中/类的结构中,保存的数据只有类的成员变量
=> 类的成员函数 => 全部变成全局函数了(主要是修改了内部函数的参数列表,使得函数调用时的语句转变)
class A
{
void func(); => func(A *this);
};
A a;
a.func(); => func(&a);
=> 静态成员变量 => .data区域
=> 静态成员函数 => .text区域
示例代码:
class_struct.cpp
//class_struct.cpp代码文件
#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;
class A
{
int m;
char *p;//64位机器是8字节 =》 看编译器的
//x86 => 32位 =》 4字节 x64=> 64位 =》 8字节
//对齐机制和之前C结构体一致 =》 送分题
static int k;
void func();
void func1();
};
void A::func() {}
void A::func1() {}
int A::k = 100;
int main()
{
A a;
cout << sizeof(a) << endl;
}
Thank you, Mr. T.Z. for teaching me this lesson, on September 7, 2021, at the beginning of my senior year.
End2022,3,14