前言:更多内容请看总纲《嵌入式C/C++学习路》
举个简单的例子,假如基类是我,子类是我的老婆儿子,外部是我的同事:
下面通过一段代码学习一下:
#include
using namespace std;
class Student // 创建学生类
{
public: // 公有成员访问控制符
void who(void) // 行为 成员函数
{
cout << "我是" << m_name << ",今年" << m_age << "岁." << endl;
}
void learn(const string &course) // 用引用是为了防止复制的开销,加const是为了防止意外的修改
{
cout << "我在学习" << course << "课程。" << endl;
}
string m_name; // 成员变量
int m_age;
};
// 把学生类变成对象(实例化)
int main(void)
{
Student s1 = {"张三", 22}; // 初始化对象
s1.who(); // 调用成员函数
s1.learn("C++");
s1.m_name = "二货"; // 用户可以随意修改公有成员变量
s1.m_age = -100;
s1.who(); // 调用成员函数
}
从输出可以看到,用户可以随意修改公有成员变量,所以出现了私有成员,将名字和年龄放入私有成员以后,再次编译代码将会报错:
#include
using namespace std;
class Student // 创建学生类
{
public: // 公有成员访问控制符
void who(void) // 行为 成员函数
{
cout << "我是" << m_name << ",今年" << m_age << "岁." << endl;
}
void learn(const string &course) // 用引用是为了防止复制的开销,加const是为了防止意外的修改
{
cout << "我在学习" << course << "课程。" << endl;
}
private: // 私有成员控制访问符
string m_name; // 成员变量
int m_age;
};
// 把学生类变成对象(实例化)
int main(void)
{
Student s1 = {"张三", 22}; // 初始化对象
s1.who(); // 调用成员函数
s1.learn("C++");
s1.m_name = "二货"; // 用户可以随意修改公有成员变量
s1.m_age = -100;
s1.who(); // 调用成员函数
}
将名字放入私有成员后,用户该怎么给学生对象命名呢?可以在公有成员里面创建一个函数,当学生姓名/年龄满足一定条件的时候,才创建学生对象。因为类的内部可以访问私有成员变量。
例子如下:假如用户给学生命名为”二货“或者年龄 <=0 的时候,拒绝创建。
#include
using namespace std;
class Student // 创建学生类
{
public: // 公有成员访问控制符
void who(void) // 行为 成员函数
{
cout << "我是" << m_name << ",今年" << m_age << "岁." << endl;
}
void learn(const string &course) // 用引用是为了防止复制的开销,加const是为了防止意外的修改
{
cout << "我在学习" << course << "课程。" << endl;
}
void setName(const string& name){
if(name == "二货"){
cout << "你才是二货!!!" << endl;
return ;
}
m_name = name;
}
void setAge(int age){
if(age <= 0){
cout << "非法年龄!" << endl;
return ;
}
m_age = age;
}
private:
string m_name; // 成员变量
int m_age;
};
// 把学生类变成对象(实例化)
int main(void)
{
Student s1; // 初始化对象
s1.setName("二货");
s1.setAge(-50);
s1.who(); // 调用成员函数
s1.setName("张飞");
s1.setAge(22);
s1.who(); // 调用成员函数
}
可以看到输入的名字和年龄不满足条件时,s1就是我们初次定义的对象即Student s1;,这里名字是字符串‘\0’,age是随机的数字(这里其实跟代码有关,我们没有对s1进行初始化赋值,因为名字和年龄变成私有变量后,不能通过等号的方式赋值,初始化的方式后面会讲)。下面重新定义合理的名字和年龄后,创建对象成功。
下面通过构造函数来初始化对象:
#include
using namespace std;
class Student // 创建学生类
{
public: // 公有成员访问控制符
Student(const string& name,int age){ // 构造函数
m_name = name;
m_age = age;
}
void who(void) // 行为 成员函数
{
cout << "我是" << m_name << ",今年" << m_age << "岁." << endl;
}
private:
string m_name; // 成员变量
int m_age;
};
// 把学生类变成对象(实例化)
int main(void)
{
Student s1("张三",22);
s1.who();
Student s2("二货",23);
s2.who();
}
class 类名{
返回类型 函数名 (形参表); // 在类的里面只给出函数的声明
};
返回类型 类名::函数名 (形参表){ // 在类的外面给出函数定义
函数体;
}
例如,我把构造函数和who函数的定义放到类外:
#include
using namespace std;
class Student // 创建学生类
{
public: // 公有成员访问控制符
Student(const string& name,int age); // 声明
void who(void);
private:
string m_name; // 成员变量
int m_age;
};
// 在类的外面定义函数
Student::Student(const string& name,int age){
m_name = name;
m_age = age;
}
void Student::who(void){
cout << "我是" << m_name << ",今年" << m_age << "岁." << endl;
}
// 把学生类变成对象(实例化)
int main(void)
{
Student s1("张三", 22);
s1.who();
Student s2("二货", 23);
s2.who();
return 0;
}
类名 对象; // 注意不要加空括号
类名 对象(实参表);
类名 对象数组[元素个数];
类名 对象数组[元素个数] = {类名 (实参表),...};
类名 对象数组[ ] = {类名 (实参表),...};
销毁在缺省构造函数的下面
下面为Student构造函数加入缺省参数:
#include
using namespace std;
class Student // 创建学生类
{
public: // 公有成员访问控制符
Student(const string& name = "新人",int age = 18); // 声明 加入缺省参数
void who(void);
private:
string m_name; // 成员变量
int m_age;
};
// 在类的外面定义函数
Student::Student(const string& name,int age){
m_name = name;
m_age = age;
}
void Student::who(void){
cout << "我是" << m_name << ",今年" << m_age << "岁." << endl;
}
// 把学生类变成对象(实例化)
int main(void)
{
Student s1("张三", 22);
s1.who();
Student s2("二货", 23);
s2.who();
Student s3; // 创建一个学生对象不进行初始化,默认取缺省参数的值
s3.who();
return 0;
}
下面创建一个对象数组:
#include
using namespace std;
class Student // 创建学生类
{
public: // 公有成员访问控制符
Student(const string &name = "新人", int age = 18); // 声明
void who(void);
void learn(const string &course) // 用引用是为了防止复制的开销,加const是为了防止意外的修改
{
cout << "我在学习" << course << "课程。" << endl;
}
void setName(const string &name)
{
if (name == "二货")
{
cout << "你才是二货!!!" << endl;
return;
}
m_name = name;
}
void setAge(int age)
{
if (age <= 0)
{
cout << "非法年龄!" << endl;
return;
}
m_age = age;
}
private:
string m_name; // 成员变量
int m_age;
};
// 在类的外面定义函数
Student::Student(const string &name, int age)
{
m_name = name;
m_age = age;
}
void Student::who(void)
{
cout << "我是" << m_name << ",今年" << m_age << "岁." << endl;
}
// 把学生类变成对象(实例化)
int main(void)
{
// 采用无参方式构造数组
Student sa[3];
for (size_t i = 0; i < 3; i++)
{
sa[i].who();
}
// 采用有参方式构造数组
Student sc[3] = { // 这里括号里的3也可以不写
Student("张飞", 22), Student("马超", 23), Student("鲁班", 2)};
for (size_t i = 0; i < 3; i++)
{
sc[i].who();
}
return 0;
}
类名*对象指针 = new 类名;
类名*对象指针 = new 类名();
类名*对象指针 = new 类名(实参表);
delete 对象指针
类名* 对象指针数组 = new 类名[元素个数];
类名* 对象指针数组 = new 类名[元素个数]{ 类名 (实参表),....};
// 上面的写法需要编译器支持C++11标准,关于C++标准,请参考《C++语言标准》
delete[ ] 对象数组指针;
具体示例如下:
#include
using namespace std;
class Student // 创建学生类
{
public: // 公有成员访问控制符
Student(const string &name = "新人", int age = 18); // 声明
void who(void);
private:
string m_name; // 成员变量
int m_age;
};
// 在类的外面定义函数
Student::Student(const string &name, int age)
{
m_name = name;
m_age = age;
}
void Student::who(void)
{
cout << "我是" << m_name << ",今年" << m_age << "岁." << endl;
}
// 把学生类变成对象(实例化)
int main(void)
{
// 在堆中创建单个对象
Student* ps = new Student;
ps->who();
delete ps;
ps = new Student(); // 这个括号可加可不加,看个人习惯
ps->who();
delete ps;
ps = new Student("张飞",22);
ps->who();
delete ps;
cout << "-------------------------------" << endl;
// 在堆中创建对象数组,无参数
Student* pw = new Student[3];
for (size_t i = 0; i < 3; i++)
{
pw[i].who();
}
// 销毁数组
delete[] pw;
cout << "-------------------------------" << endl;
// 有参方式创建数组
Student* pe = new Student[3]{
Student("鲁班",2),Student("孙尚香",19),Student("刘备",20)
};
for (size_t i = 0; i < 3; i++)
{
pe[i].who();
}
delete[] pe;
return 0;
}
在实际工作中都是分开的,如下图:
将上述Student分开写为:先看一下整体的目录:
头文件student.h的内容,存放的是Student类的声明:
// Student类的声明
#ifndef _STUDENT_H
#define _STUDENT_H
#include
using namespace std;
class Student{
public:
Student(const string& name = "新成员",int age = 20);
void who(void);
void learn(const string& course);
void setName(const string& name);
void setAge(int age);
private:
string m_name;
int m_age;
};
#endif // _STUDENT_H
再看student.cpp的内容,存放的是Student类的实现:
// Student类的实现
#include "student.h"
Student::Student(const string& name, int age)
{
m_name = name;
m_age = age;
}
void Student::who(void)
{
cout << "我是" << m_name << ",今年" << m_age << "岁." << endl;
}
void Student::learn(const string& course)
{
cout << "我在学习" << course << "课程。" << endl;
}
void Student::setName(const string &name)
{
if (name == "二货")
{
cout << "你才是二货!!!" << endl;
return;
}
m_name = name;
}
void Student::setAge(int age)
{
if (age <= 0)
{
cout << "非法年龄!" << endl;
return;
}
m_age = age;
}
然后是main.cpp,使用Student类:
//使用Student类
#include "student.h"
int main(void){
Student luban("鲁班",2);
luban.who();
luban.learn("二级能接闪现");
return 0;
}
编译并输出如下:
值得注意的是,在编译的时候我在后面加上了student.cpp,正常来说直接编译main.cpp就好了,但是我遇到了一个错误:
如果你也遇到了和我一样的问题,可以参考这篇文章:《"undefined reference to XXX"问题总结》
https://zhuanlan.zhihu.com/p/81681440
练习:编写一个动态时钟,在终端显示当前时间,有兴趣可以看一下-->链接
来看一个重载的例子:
#include
using namespace std;
class Student
{
public:
// 下面的四个构造函数属于重载关系
Student(void)
{
m_name = "新人";
m_age = 20;
}
Student(const string &name)
{
m_name = name;
m_age = 20;
}
Student(int age)
{
m_name = "新人";
m_age = age;
}
Student(const string &name, int age) // 这个函数可以代替这四个函数
{
m_name = name;
m_age = age;
}
void show(void)
{
cout << m_name << "," << m_age << endl;
}
private:
string m_name;
int m_age;
};
int main()
{
Student s1;
s1.show();
Student s2("张飞");
s2.show();
Student s3(30);
s3.show();
Student s4("赵云", 12);
s4.show();
}
那么为什么说使用缺省参数可以减少构造函数重载版本的数量呢?其实上面的例子中,四个重载版本只需要最后一个版本即可,因为里面有两个缺省参数,覆盖了前面的三种情况。
来看一个例子:
#include
using namespace std;
class A
{
public:
A(int a) {}
};
class B // 没有构造函数,系统自己定义
{
public:
B(void) : m_a(10) {} // 初始化表,显式,非缺省构造方式构造m_a
private:
A m_a; // B里面有A类型的成员变量
};
int main()
{
B b; // A类的A是B类的子对象
return 0;
}
class 目标类型{
目标类型(const 源类型& src){....}
};
class 目标类型{
explicit 目标类型(const 源类型& src){....}
};
来看一个例子:将小狗变成小猫:
#include
using namespace std;
class Dog; // 声明
class Cat
{
public:
Cat(const string &name)
{
m_name = name;
}
// 类型转换操作函数 Cat是转换目标 Dog是转换源
explicit Cat(const Dog& dog); // 声明 explicit是显式的,不加表示隐式
void talk(void)
{
cout << m_name << ": 喵~~~喵~~~" << endl;
}
private:
string m_name;
};
class Dog
{
public:
Dog(const string &name)
{
m_name = name;
}
void talk(void)
{
cout << m_name << ": 汪汪!!" << endl;
}
private:
string m_name;
friend class Cat; // 友元,Cat可以访问Dog的私有成员
};
Cat::Cat(const Dog& dog){ // 类外定义 dog可以转换为cat
m_name = dog.m_name;
};
int main(void)
{
Dog dog("小白");
dog.talk();
// 用static_cast显式地完成
// Cat cat = dog; 隐式
Cat cat = static_cast(dog); // 不同源 !!!!!!!!!!!!!11
cat.talk();
Cat cat2("咪咪");
cat2.talk();
//cat2 = dog; 隐式
cat2 = static_cast(dog); // !!!!!!!!!!!!1
cat2.talk();
return 0;
}
输出如下:实际中常用的为显式类型转换,即需要加explicit关键字,隐式类型转换的语句已在代码中注明
class 类名{
类名(const 类名& that){ ... }
};
自定义构造函数 | 系统定义构造函数 |
无 | 缺省构造函数 缺省拷贝构造函数 |
除拷贝构造函数意外的任何构造函数 | 缺省拷贝构造函数 |
拷贝构造函数 | 无 |
下面看一个拷贝构造的例子:
#include
using namespace std;
class Student
{
public:
Student(const string &name, int age)
{
m_name = name;
m_age = age;
}
// 拷贝构造函数
Student(const Student& that){
m_name = that.m_name;
m_age = that.m_age;
}
void show(void){
cout << m_name << " " << m_age << endl;
}
private:
string m_name;
int m_age;
};
void foo(Student s){
s.show();
}
int main(void)
{
Student s1("张飞", 22);
s1.show();
Student s2(s1); // s2就是s1的克隆
s2.show();
// 以对象的形式向函数传参,引发拷贝构造
foo(s2); // 这也是拷贝手法,即foo的参数s是s2的拷贝
return 0;
}
主要作用在于对象销毁前系统自动调用,执行一些清理操作
格式: ~类名(){ }
例子如下:
#include
using namespace std;
class Student
{
public:
Student(const string &name, int age)
{
m_name = name;
m_age = age;
}
void show(void)
{
cout << m_name << " " << m_age << endl;
}
// 析构函数,如果不提供,系统会提供一个空实现析构函数
~Student()
{
cout << "析构函数调用" << endl;
}
private:
string m_name;
int m_age;
};
int main(void)
{
Student s1{"张三", 22};
s1.show();
return 0;
}
先来看一段代码:
#include
using namespace std;
class Student
{
public:
Student()
{
cout << "Student默认构造函数调用" << endl;
}
Student(int age, int height)
{
m_age = age;
m_height = new int(height); // 用指针接收堆区的数据
cout << "Student有参构造函数调用" << endl;
}
~Student()
{
// 析构代码,将堆区开辟的数据做释放操作
if (m_height != NULL)
{
delete m_height;
m_height = NULL;
}
cout << "Student的析构函数调用" << endl;
}
int m_age;
int *m_height; // 身高,指针类型,目的是开辟到堆里面去
};
void test01()
{
Student s1(18, 160);
cout << "s1的年龄为:" << s1.m_age << "身高为:" << *s1.m_height << endl;
Student s2(s1); // 浅拷贝
cout << "s2的年龄为:" << s2.m_age << "身高为:" << *s2.m_height << endl;
}
int main()
{
test01();
return 0;
}
这就是浅拷贝带来的问题,其原理图如下所示:在执行析构函数的时候,s2释放了堆区的内容,s1再次释放就会报错。
这个时候就要利用深拷贝来解决,即自己定义一个拷贝构造函数,重新开辟一块内存,让s2指向新的堆区,数据一样,但是地址不一样,这样释放的时候就不会重复。
在Student类中加入深拷贝构造函数如下:
// 拷贝构造函数
Student(const Student &that)
{
m_age = that.m_age;
// m_height = that.m_height; // 编译器默认执行的是这条语句
m_height = new int(*that.m_height); // 重新开辟内存
}
原理图如下所示:
程序结束时应该调用析构函数将堆区释放。
构造函数():属性1(值1),属性2(值2).....{ }
#include
using namespace std;
//初始化列表
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, m_b, m_c;
};
void test01()
{
Person p(10, 20, 30);
cout << p.m_a << " " << p.m_b << " " << p.m_c << endl;
}
int main()
{
test01();
return 0;
}
通过上一节我们知道在C++中成员变量和成员函数是分开存储的,每一个非静态成员函数只会诞生一份函数实例,也就是说多个类型的对象会共用一块代码,那么问题是:这一块代码是如何区分哪个对象调用自己的呢?
C++通过提供特殊的指针,this指针,解决上述问题。this指针指向被调用的成员函数所属的对象
this指针是隐含每一个非静态成员函数内的一种指针
this指针不需要定义,直接使用即可
this指针指向的是被调用的成员函数所属的对象
this指针的用途:
例如:看下面一段代码,先猜猜他会输出什么?
#include
using namespace std;
class Person{
public:
Person(int age){
age = age;
}
int age;
};
void test01(){
Person p1(10);
cout << "p1的年龄为:"<< p1.age << endl;
}
int main(){
test01();
return 0;
}
这是因为系统认为黄色箭头指向的age是一个东西(当鼠标点击任意一个age时,可以看到这三个都有相同的背景色),而红色箭头指向的是另一个。
第一种解决方案就像我们前面代码里面的,将age写为m_age,代表这是一个成员变量。
第二种方法就是this指针:可以看到this指向的age和下面的int age变成了同一个变量。
这个时候我们再次运行代码,输出10岁:
上面的例子解释了this指针的第一个用途,解决名称冲突,下面的代码展示如何返回对象本身:
#include
using namespace std;
class Person{
public:
Person(int age){
this->age = age;
}
// 定义一个函数,将一个person对象的age加到当前对象
Person& PersonAddAge(Person &p){ // 如果想返回对象本体,需要用引用的方式返回
this->age += p.age;
return *this;
}
int age;
};
void test01(){
Person p1(10);
Person p2(10);
// 由于该函数返回对象本体,所以可以继续调用函数,调用多少次都行
p2.PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1); //链式编程思想
cout << "p2的年龄为:"<< p2.age << endl;
}
int main(){
test01();
return 0;
}
这种无限往后追加的编程方式叫做链式编程思想。
在类成员函数的形参表之后,函数体之前加上const关键字,该成员函数的this指针即具有常属性,这样的成员函数被称为常函数。
class 类名{
返回类型 函数名(形参表)const{
函数体;
}
};
在常函数内部无法修改成员变量的值,除非该成员变量被mutable关键字修饰。
来看一个例子:定义一个电子文档,定义常函数print,使用户在print里面不可以修改电子文档的内容,并且定义一个计数器,打印三次之后就不可以打印。
#include
using namespace std;
// 定义一个电子文档
class Document
{
public:
Document(const string &content) : // 一种初始化的方式
m_content(content){}
void print(void) const
{
if (++m_counter > 3)
{
cout << "交钱!!!" << endl;
return;
}
cout << m_content << endl;
// m_content = ""; 这条语句是程序员意外加入的,会导致电子文档的内容清空,但是我们并不想出现这种情况,就可以将print变为常函数
// 但是如果我想实现这样一个功能:当打印3次之后,就不可以打印电子文档内容,这个时候就需要定义一个计数器
// 但是我想在常函数里面修改计数器的值,此时应该加mutable关键字
}
void pay(float money)
{ // 付钱之后又可以打印10遍
if (money >= 10000)
{
m_counter = 0;
}
}
private:
string m_content;
mutable int m_counter = 0; // 可以在常函数中做修改
};
int main()
{
Document doc("Hello world!");
for (size_t i = 0; i < 5; i++)
{
doc.print();
}
return 0;
}
const User user(...);
const User* cptr = &user;
const User& cref = user;
例如,将我们上例中的main函数改为:
int main()
{
Document doc("Hello world!"); // 非常对象
for (size_t i = 0; i < 5; i++)
{
doc.print(); // 调用常函数
}
doc.pay(20000); // 调用非常函数
// 定义一个常对象
const Document& cd = doc;
cd.print();
cd.pay(20000);
return 0;
}
由于cd是一个常对象,而pay是非常函数,所以编译错误:
再来看一个常函数与非常函数构成重载的例子:
#include
using namespace std;
class A{
public:
void foo(void){
cout << "我是非常函数" << endl;
}
void foo(void) const{
cout << "我是常函数" << endl;
}
void print(void) const{
cout << "print只有我一个常版本的,你只能选择我喽" << endl;
}
};
int main(){
A a1; // 非常对象
a1.foo();
const A a2; // 常对象
a2.foo();
a1.print(); // 优先选择非常函数,如果没有,也可以选择常函数
return 0;
}
静态成员就是在成员变量和成员函数前面加上关键字static,称为静态成员
静态成员分为静态成员变量和静态成员函数
静态成员变量:
静态成员函数:
先来看一个静态成员变量的例子:
#include
using namespace std;
//静态成员变量
class Bank
{
public:
// 所有对象共享同一份数据 类内声明,类外初始化
static int money;
void draw(int a); // 取钱
void show(); // 查看余额
private:
static string b_name; // 私有静态成员变量,类外无法访问
};
int Bank::money = 100; // 余额的初始值
// 类外定义
void Bank::draw(int a)
{
money -= a;
}
void Bank::show()
{
cout << "余额为:" << money << endl;
}
int main()
{
Bank b1, b2;
b1.show();
b1.draw(50);
b1.show();
b2.draw(20);
b2.show();
// 通过类名访问静态成员变量:
cout << Bank::money << endl;
return 0;
}
再来看静态成员函数的例子:
#include
using namespace std;
class Person
{
public:
static void func()
{
m_age = 100; // 静态函数可以访问静态变量
// m_height = 180; // 错误,不可以访问非静态成员
cout << "static void func调用" << endl;
}
static int m_age; // 静态成员变量
int m_height;
private:
static void func2(){ // 访问权限!
cout << "static void func222调用" << endl;
}
};
int Person::m_age = 0;
void test01()
{
Person p;
p.func();
}
int main()
{
// 通过对象访问静态函数
test01();
//通过类访问静态函数(不需要创建对象)
Person::func();
return 0;
}
下面来看饿汉模式的例子:
// 饿汉模式
#include
using namespace std;
class Singleton{
public:
static Singleton& getInstance(void){
return s_instance;
}
private:
Singleton(void){}
Singleton(const Singleton& that){}
static Singleton s_instance;
};
Singleton Singleton::s_instance;
int main(){
Singleton& s1 = Singleton::getInstance();
cout << &s1 << endl;
Singleton& s2 = s1.getInstance();
cout << &s2 << endl;
return 0;
}
再来看懒汉模式的例子:
// 懒汉模式
#include
using namespace std;
class Singleton{
public:
static Singleton& getInstance(void){
if(! s_instance){ // 第一次用的时候才new
s_instance = new Singleton;
++s_counter; // 每次调用就给计数器加一,表示有多少人拿到了这个实例
}
return *s_instance;
}
// 懒汉的一点改进:释放内存
void releaseInstance(void){ // 不想用的时候就将内存释放
if(s_counter && --s_counter == 0){ // 当所有人都不用这个实例的时候,释放内存
delete this;
}
}
private:
Singleton(void){
cout << "构造" << endl;
}
Singleton(const Singleton& that){}
static Singleton* s_instance;
static size_t s_counter; // 定义一个计数器
~Singleton(void){ // 析构函数,改进
cout << "析构函数" << endl;
s_instance = NULL;
}
};
Singleton* Singleton::s_instance = NULL;
size_t Singleton::s_counter = 0;
int main(){
Singleton& s1 = Singleton::getInstance();
cout << &s1 << endl;
Singleton& s2 = s1.getInstance();
cout << &s2 << endl;
s2.releaseInstance();
s1.releaseInstance(); // 当两个对象都释放的时候,才真正释放内存,运行析构函数
}
可以看到析构函数只调用了一次,因为只有当所有对象都释放后才会调用。
类型 类名::*成员变量指针;
成员变量指针=&类名::成员变量;
对象.*成员变量指针
对象->*成员变量指针
其本质就是特定成员变量在对象实例中的相对地址,解引用时再根据调用对象的地址计算出该成员变量的绝对地址
来看一个例子:
#include
using namespace std;
class Student
{
public:
Student(const string& name,int age):m_name(name),m_age(age){}
string m_name;
int m_age;
};
int main(void)
{
// 成员变量指针,指向的是Student中类型为string的成员变量,如果想让它指向m_name,可以这么写:
string Student::*p1 = &Student::m_name;
// 再定义一个指向m_age的成员变量指针
int Student::*p2 = &Student::m_age;
Student sa("张飞",23);
// 解引用
cout << sa.*p1 << endl;
cout << sa.*p2 << endl;
// 定义一个新的对象,让p1 p2指向新的对象,再次解引用
Student sc("赵云",22);
cout << sc.*p1 << endl;
cout << sc.*p2 << endl;
return 0;
}
从例子中可以看到,成员指针只进行一次初始化,就可以指向多个对象(普通指针只能指向一个对象)
返回类型(类名::*成员函数指针)(形参表);
成员函数指针 = &类名::成员函数名;
(对象.*成员函数指针)(实参表)
(对象指针->*成员函数指针)(实参表)
虽然成员函数并不存储在对象中,但也要通过对象或者对象指针对成员函数的指针解引用,其目的只有一个,即提供this指针
#include
using namespace std;
class Student
{
public:
Student(const string& name,int age):m_name(name),m_age(age){}
void who(void) const{
cout << m_name << ", " << m_age << endl;
}
string m_name;
int m_age;
};
int main(void)
{
// 成员变量指针,指向的是Student中类型为string的成员变量,如果想让它指向m_name,可以这么写:
string Student::*p1 = &Student::m_name;
// 再定义一个指向m_age的成员变量指针
int Student::*p2 = &Student::m_age;
Student sa("张飞",23);
// 解引用
cout << sa.*p1 << endl;
cout << sa.*p2 << endl;
// 定义一个新的对象,让p1 p2指向新的对象,再次解引用
Student sc("赵云",22);
cout << sc.*p1 << endl;
cout << sc.*p2 << endl;
// 成员函数指针
void (Student::*p3)(void) const = &Student::who;
(sa.*p3)();
return 0;
}
为什么要进行操作符重载呢?为了满足我们的需求,比如正常的加法是 1+1 = 2
那么如果我有两个复数 1+2i 和 2+3i ,他们直接相加,怎么得出 3+5i 呢?这个时候就需要进行操作符重载,使两个复数相加时返回我们想要的结果。
单目操作符: -、++ 、-- 、* 、->等
双目操作符:+ 、 - 、 += 、 -= 、 >> 、 << 、[ ]等
三目操作符:? :
1.在特定条件下,编译器有能力把一个由操作数和操作符共同组成的表达式,解释为对一个全局或成员函数的调用,该全局或成员函数被称为操作符函数
2.通过定义操作符函数,可以实现针对自定义类型的运算法则,并使之与内置类型一样参与各种表达式
成员函数形式:L.operator#(R)
左操作数是调用对象,右操作数是参数对象
全局函数形式: ::operator#(L , R)
左操作数是第一参数,右操作数是第二参数
成员函数形式: O.operator#( )
全局函数形式: ::operator#(O)
无法重载
左右操作符均可为左值或右值
表达式的值为右值
成员函数形式:
class LEFT{
const RESULT operator#( const RIGHT& right) cosnt {...}
};
全局函数形式
const RESULT operator#(const LEFT& left , const RIGHT& right){...}
下面来看一个例子:用成员函数重载+号,用全局函数重载-号,实现复数的加减
#include
using namespace std;
class Complex
{
public:
Complex(int r = 0, int i = 0) : m_r(r), m_i(i) {}
void print(void) const
{
cout << m_r << '+' << m_i << 'i' << endl;
}
// 下面第一个const 是为了返回右值
// 第二个const是为了右操作数既能够接受左值,也能接受右值,即c2可以是 const Complex c2
// 第三个const是为了左操作数可以是const属性,即c1可以是const类型的
const Complex operator+(const Complex &r) const
{
return Complex(m_r + r.m_r, m_i + r.m_i);
}
private:
int m_r;
int m_i;
friend const Complex operator-(const Complex &l, const Complex r); // 友元函数
};
// 全局函数形式
const Complex operator-(const Complex &l, const Complex r)
{
return Complex(l.m_r - r.m_r, l.m_i - r.m_i);
}
int main(void)
{
Complex c1(1, 2);
Complex c2(2, 3);
Complex c3 = c1 + c2;
c3.print();
Complex c4 = c1 - c2;
c4.print();
return 0;
}
右操作数为左值或者右值,但左操作数必须是左值
表达式的值为左值,且为左操作数本身(而非副本)
成员函数形式:
class LEFT{
LEFT& operator#(const RIGHT& right){ ... }
};
全局函数形式:
LEFT& operator#(LEFT& left, const RIGHT& right){ ... }
下面用成员函数形式重写 += ,用全局函数重写 -= :
#include
using namespace std;
class Complex
{
public:
Complex(int r = 0, int i = 0) : m_r(r), m_i(i) {}
void print(void) const
{
cout << m_r << '+' << m_i << 'i' << endl;
}
Complex &operator+=(const Complex &r)
{
m_r += r.m_r;
m_i += r.m_i;
return *this;
}
private:
int m_r;
int m_i;
friend Complex& operator-=(Complex &l, const Complex& r); // 友元函数
};
// 全局函数形式
Complex& operator-=(Complex &l, const Complex& r)
{
l.m_r -= r.m_r;
l.m_i -= r.m_i;
return l;
}
int main(void)
{
Complex c1(1, 2);
Complex c2(2, 3);
const Complex c3(5, 6);
c1 += c2; // 3+5i
c1.print();
(c1 += c2) += c3; // 10+14i
c1.print();
c1 -= c2;
c1.print(); // 8+11i
return 0;
}
操作数为左值或右值
表达式的值为右值
成员函数形式:
class OPERAND{
const RESULT operator# (void) const {...}
} ;
其中 OPERAND是操作数的类型 # 是操作符
全局函数形式
const RESULT operator#(const OPERAND& operand){ ... }
来看一个例子:用成员函数形式重写 - 取反 ,用全局函数重写~,使其实部虚部互换
#include
using namespace std;
class Complex
{
public:
Complex(int r = 0, int i = 0) : m_r(r), m_i(i) {}
void print(void) const
{
cout << m_r << '+' << m_i << 'i' << endl;
}
// 取负操作 第一个const是为了返回右值,第二个const是为了让我们的操作数具有常属性
const Complex operator-(void) const
{
return Complex(-m_r, -m_i);
}
private:
int m_r;
int m_i;
friend const Complex operator~(const Complex o); // 友元函数
};
// 全局函数形式 用~表示实部虚部互换
const Complex operator~(const Complex o)
{
return Complex(o.m_i,o.m_r);
}
int main(void)
{
Complex c1(1, 2);
Complex c2(2, 3);
const Complex c3(5, 6);
c1 = -c1;
c1.print();
c2 = ~ c1; // 实部虚部互换
c2.print();
return 0;
}
操作数为左值
表达式的值为左值,且为操作数本身(而非副本)
成员函数形式
class OPERAND{
OPERAND& operator#(void){ ...}
};
全局函数形式
OPERAND& operator# (OPERAND& operand){ ... ]
来看一个例子:成员函数重写++ 全局函数重写 --
#include
using namespace std;
class Complex
{
public:
Complex(int r = 0, int i = 0) : m_r(r), m_i(i) {}
void print(void) const
{
cout << m_r << '+' << m_i << 'i' << endl;
}
Complex& operator++(void){
++m_r;
++m_i;
return *this;
}
private:
int m_r;
int m_i;
friend Complex& operator--(Complex& o); // 友元函数
};
// 全局函数形式 用~表示实部虚部互换
Complex& operator--(Complex& o){
--o.m_r;
--o.m_i;
return o;
}
int main(void)
{
Complex c1(1, 2);
Complex c2 = ++++c1;
c1.print();
c2.print();
Complex c3 = --c1;
c3.print();
return 0;
}
操作数为左值
表达式的值为右值,且为自增自减以前的值
成员函数形式
class OPERAND{
const OPERAND operator# (int){...} // 哑元int是为了形成重载,区分前++ 和 后++
};
全局函数形式
const OPERAND operator#(OPERAND& operand , int){ ... }
例子如下:
#include
using namespace std;
class Complex
{
public:
Complex(int r = 0, int i = 0) : m_r(r), m_i(i) {}
void print(void) const
{
cout << m_r << '+' << m_i << 'i' << endl;
}
Complex& operator++(void){
++m_r;
++m_i;
return *this;
}
const Complex operator++ (int){
Complex old = *this;
++*this;
return old; // 返回相加以前的值
}// 哑元int是为了形成重载,区分前++ 和 后++
private:
int m_r;
int m_i;
friend Complex& operator--(Complex& o); // 友元函数 前--
friend const Complex operator--(Complex& o ,int); // 后--
};
// 全局函数形式 用~表示实部虚部互换
Complex& operator--(Complex& o){
--o.m_r;
--o.m_i;
return o;
}
const Complex operator--(Complex& o ,int){
Complex old = o;
--o;
return old;
}
int main(void)
{
Complex c1(1, 2);
Complex c2 = c1++;
c1.print();
c2.print();
Complex c3 = c2--;
c3.print();
c2.print();
return 0;
}
左操作数为左值形式的输出流(ostream)对象,右操作数为左值或右值
表达式的值为左值,且为左操作数本身(而非副本)
左操作数的类型为ostream,若以成员函数形式重载该操作符,就应将其定义为ostream类的成员,该类为标准库提供,无法添加新的成员,因此只能以全局函数形式重载该操作符
ostream& operator << (ostream& os, const RIGHT& right){ ... }
输出 >> 重载:
istream& operator >> (istream& is, RIGHT& right){ ... }
#include
using namespace std;
class Complex
{
public:
Complex(int r = 0, int i = 0) : m_r(r), m_i(i) {}
private:
int m_r;
int m_i;
friend ostream& operator << (ostream& os, const Complex& c){
return os << c.m_r << "+" << c.m_i << "i";
}
friend istream& operator >> (istream& is, Complex& c){
return is >> c.m_r >> c.m_i;
}
};
int main(void)
{
Complex c1(1, 2);
cout << c1 << endl;
Complex c2,c3;
cin >> c2 >> c3;
cout << c2 << "," << c3 << endl;
return 0;
}