01 this指针
C++是在C语言的基础上发展而来的,第一个C++的编译器实际上是将C++程序翻译为C语言,然后再使用C语言编译器编译
C语言中没有类的概念,只有结构,函数都是全局函数,没有成员函数的概念翻译的时候,将class变成struct,对象翻译为结构体变量是显而易见的,但是对于类的成员函数该如何翻译呢?对于 t1.show() 这样通过一个对象调用成员函数的语句,又应该如何翻译呢?
C语言只有全局函数,最开始的时候,成员函数都是被翻译为全局函数,对于 t1.show() 这样通过一个对象调用成员函数的语句也只能翻译为全局函数的调用。此时产生了一个问题,如何让翻译之后的show函数还能作用于 t1 这个对象呢?
解决方法:
t1.show(); ------> show(&t1);
t2.show(); ------> show(&t2);
偷偷的给函数传递一个参数,把调用者对象(t1)的地址作为参数传入函数的内部(实参)
在函数内部就可以通过这个地址访问这个调用者对象的属性
并且这个地址的形参名就是 this (不是程序员定义的,而是编译器加的)
void show();
======>
void show(Test *const this) {}
所有的非静态成员函数中都可以使用 this 指针,this指针指向了调用这个函数的那一个对象的地址
可以通过gdb验证
为什么需要 this 指针?
为了让函数能够区分不同的调用对象,C++编译器在每一个非静态成员函数中都隐藏了一个this指针,这个指针保存的是当前调用者的地址(让操作和对象能够绑定在一起)
构造函数中的this表示正在初始化的对象的地址
析构函数中的this表示正在销毁的对象的地址
this指针的作用域是在类成员函数的内部(因为是一个形式参数),当类的非静态成员函数访问类的成员变量时,编译器将对象自己本身的地址作为一个隐藏的参数传递给函数
为什么在成员函数中(t1.show())可以访问调用者对象(t1)的成员属性呢?
就是因为有this指针
在成员函数中,对类成员变量的访问,其实都是对this的隐式使用在C++中,使用类类型创建的对象,每一个对象都有自己的地址空间,该空间中存储的是对象的非static成员变量,不存储成员函数,所有同类的对象,共享成员函数
所以,在计算对象所占用的空间大小时,不需要考虑成员函数
只需要考虑非static成员变量即可(考虑对齐,参考结构体中的内容)
一个对象的this指针会影响对象的大小吗?
不会,this不是对象的成员,是成员函数的形式参数,不影响sizeof的结果
this指针可以改变指向吗?
不行
void show();
------>
void show(Test * const this)const修饰的函数是一个常函数,不能修改对象数据
int a;
int *p = &a; // YES
const int *p = &a; // YES
const int b;int *q = &b; // ERROR,权限只能降,不能升
const int *q = &b; // YES
void show(); -----> void show(Test *const this);
上面的this只能指向非常量对象,不能指向常量对象
Test t1{100};
t1.show(); // YES Test *const this = &t1;
const Test t2{100};
t2.show(); // ERROR Test *const this = &t2; // 两边的类型不一致
// Test * cosnt this const Test *
void show() const; ------> void show(const Test * const this);上面的this既可以指向常量对象,又可以指向非常量对象
Test t1{100};
t1.show(); // YES // const Test *const this = &t1;
const Test t2{100};
t2.show(); // YES // const Test * const this = &t2; // 两边的类型一致
// const Test *this const Test*
给程序员的表现就是常量对象只能调用常函数,但是非常量对象,既可以调用常函数,又可以调用非const函数
const修饰一个函数,实际上const是修饰了函数的this指针
02 静态成员
需求:在项目的运行过程中,需要统计某一种类型的对象的数量
思路:
1. 使用一个专门的变量(count)来保存对象的数量
2. 在对象创建的时候(构造函数中),给count+1,在对象销毁的时候(析构函数中),count-1
3. 当需要查看对象的数量的时候,只需要直接读取count的值即可
========>
count 应该是一个全局变量......
成员变量可不可以?
不行,因为这个变量需要有"共享"性,不能是单独的属于某一个对象
但是全局变量的数据非常的不安全,最终可能无法保证结果的正确性
需要一个变量,能够在所有同类型的对象之间实现数据共享,同时又能够保证数据的安全性
======>
就可以使用静态成员(静态成员变量)
(1) 静态成员变量
使用 static 修饰的成员变量,就是静态成员变量
特点:
static成员变量不属于某一个特定的对象,而是属于整个类,是类的一部分,该类的所有对象共享静态成员
静态成员变量使用前必须初始化,而且必须在类外初始化,不能再类内初始化
格式:
类型 类名::静态成员变量名 = 初始值;
不影响 类类型 和 对象 的大小(sizeof):
静态成员变量,只有唯一的一份副本,而其他的非静态成员变量,有多少个对象就有多少份副本(每一个对象中都有自己的数据)
======>
sizeof(类名) and sizeof(对象名):不算静态成员变量的大小
静态成员变量,可以像正常变量一样,在成员函数中被访问
而且,静态成员变量是属于整个类的,在没有任何对象的情况下,也存在,那么如何使用呢?
a. 通过类名访问 -----> 成员变量必须是公用的,一般不这么设计
cout << Student::count << endl;
b. 静态成员函数
(2) 静态成员函数
使用 static 修饰的成员函数,就是静态成员函数
特点:
静态成员函数可以直接通过类名调用,不需要通过特定的对象访问
// 静态成员函数不需要通过对象名调用(不依赖对象,静态成员函数中没有this指针)
cout << Student::GetCount() << endl;
普通成员函数为什么需要依赖对象?
因为普通的成员函数默认有一个参数,需要一个this指针
t1.show(); ---> show(&t1);
静态成员函数的调用:
1. 可以不通过对象,直接通过类名调用
类名::静态成员函数();
如:
cout << Student::GetCount() << endl;
2. 静态成员函数也可以像普通成员函数一样,使用对象调用(静态成员函数依然是成员函数,只不过没有this指针)
对象名.静态成员函数名();
or
对象指针->静态成员函数名();
如:
count << s1.GetCount() << endl;
注意:
1. 静态成员函数中没有 this 指针(非静态成员函数中才有 this 指针)
this指针表示调用者对象的地址,静态成员函数可以直接通过类名调用
有可能在调用前,类都没有实例化对象
2. 静态成员函数中不能访问非静态成员
a. 当对象不存在时,静态成员函数可以直接通过类名调用,但是非静态成员是属于某一个对象的
b. 在成员函数中,能够对非静态成员的访问,其实都是this的隐式使用,既然在静态成员函数中没有this指针,那么就不可以访问非静态成员变量
3. 静态成员函数不能是const函数
**const修饰成员函数其实修饰的是成员函数的this指针,**静态函数都没有this指针!!!
const修饰函数的作用是避免在函数内部修改当前对象的状态,静态成员函数都不能访问对象的属性(非静态成员变量),肯定是不能修改对象的状态的
4. 静态成员不属于任何对象,而是与类同时存在,属于该类型
即静态成员不依赖于某一个对象
很多情况下,静态成员函数都是配合静态成员变量使用的
5. 构造函数和析构函数可以是static吗?
不能!!!
构造函数是对对象进行初始化的,析构函数是释放对象占用的资源,肯定需要可以访问对象的普通成员属性
cpp#include <iostream> #include <string.h> using namespace std; class Test { private: int m_a; public: Test(int a) { // this是一个隐藏的参数,表示调用者对象的地址 // 在此处,表示正在初始化的对象的地址 this->m_a = a; cout << "构造函数:this" << this << endl; // this->show(); // ---> show(this) } ~Test() { cout << "析构函数:" << this <<endl; } void show() { // void show(Test* const this) // 在成员函数中,对类成员的访问,其实都是this指针的隐式使用 cout << "show:this:" << this << endl; cout << "m_a:" << this->m_a << endl; // this = nullptr; this不允许改变指向,但是允许改变this指向的地址中的内容 // (*this) 表示调用者对象本身 (*this).m_a = 100; } }; class Student { private: // 普通成员变量,每一个对象都有单独的一份拷贝 int m_age; char *m_name; // static Test t; // 静态成员对象,在使用前,也需要在类外初始化 // 使用static修饰的成员就叫做静态成员,静态成员属于整个类,而不是属于某一个对象 static int count; // 用来统计学生类的所有对象个数 public: // 构造函数 Student(const char *name = "zhangsan", int age = 30):m_age{age} { cout << "构造函数" << endl; m_name = new char[strlen(name) + 1]; // 开辟空间存储名字 strcpy(m_name, name); count++; } // 拷贝构造函数 Student(const Student &oth):m_age{oth.m_age} { cout << "const-拷贝-构造函数" << endl; // m_age = oth.m_age; // m_name = oth.m_name; // 有问题!!!! // a.为新对象开辟空间 m_name = new char[strlen(oth.m_name) + 1]; // b.把原有对象的数据赋值给新对象 strcpy(m_name, oth.m_name); count++; } // 析构函数 ~Student() { cout << "析构函数" << endl; delete [] m_name; count--; } // 展示函数 void show() const { cout << "name:" << m_name << endl; cout << "age:" << m_age << endl; cout << "count" << m_count << endl; } // 静态成员函数,不需要通过对象调用 static int GetCount(); // const 静态成员函数不能有const修饰 }; // 声明和定义分开时,定义不能加static修饰 int Student::GetCount() { cout << "静态成员函数" << endl; // cout << m_age << endl; // 静态成员函数中,不能访问非静态成员 return count; } // 静态成员变量必须在类外初始化 int Student::count = 0; // Test Student::t{1024}; // 静态成员对象在类外初始化 Student s2; // 全局对象 ---> 静态成员变量在程序运行时,就已经初始化了 int main() { cout << "sizeof(Student):" << sizeof(Student) << endl; // 16 不影响 类类型 和 对象 的大小(sizeof) Student s{"ikun", 20}; Student s1(s); // 拷贝构造函数 cout << "sizeof(s):" << sizeof(s) << endl; // 16 不影响 类类型 和 对象 的大小(sizeof) cout << "count:" << Student::GetCount() << endl; // 静态成员函数,通过类名调用 // cout << "count:" << s.GetCount() << endl; // 静态成员函数,通过对象调用 return 0; }
(3) 一个关于函数指针(保存函数地址的指针变量)的问题
函数指针:
返回值 (*指针变量名)(参数列表);
函数:
void fun(int a, double b);
定义一个指针p,保存fun的地址
void (*p)(int , double); // p就是一个指针变量,可以保存一个void(int,double)函数的地址
赋值:
p = fun; p = &fun;
调用:
p(1, 1.2); (*p)(1, 1.2);
1. 能不能定义一个函数指针,指向类的非静态成员函数
定义方式:
返回值 (类名::*指针变量名)(参数列表);
// 返回值 (类名::*指针变量名)(参数列表) const;
如:
void (Student::*p)();
// void (Student::*p)() const;
赋值方式:
p = &类名::函数名
如:
p = &Student::show;
调用:
调用必须通过类的对象进行调用,因为有一个this指针的隐藏参数
(s1.*p)();
// s1是一个指针:(s1->*q)();
2. 能不能定义一个函数指针,指向类的静态成员函数
定义和使用方式类似于普通的全局变量
定义方式:
返回值 (*指针变量名)(参数列表); // 返回值 (*指针变量名)(参数列表);
如:
int (*p)();
赋值方式:
p = 类名::函数名;
如:
p = Student::GetCount;
调用:
p();
cpp#include <iostream> #include <string.h> using namespace std; class Student { private: // 普通成员变量,每一个对象都有单独的一份拷贝 int m_age; char *m_name; // 使用static修饰的成员就叫做静态成员,静态成员属于整个类,而不是属于某一个对象 static int count; // 用来统计学生类的所有对象个数 public: // 构造函数 Student(const char *name = "zhangsan", int age = 30):m_age{age} { cout << "构造函数" << endl; m_name = new char[strlen(name) + 1]; // 开辟空间存储名字 strcpy(m_name, name); count++; } // 拷贝构造函数 Student(const Student &oth):m_age{oth.m_age} { cout << "const-拷贝-构造函数" << endl; // m_age = oth.m_age; // m_name = oth.m_name; //有问题!!!! // a.为新对象开辟空间 m_name = new char[strlen(oth.m_name) + 1]; // b.把原有对象的数据赋值给新对象 strcpy(m_name, oth.m_name); count++; } // 析构函数 ~Student() { cout << "析构函数" <<endl; delete [] m_name; count--; } // 展示函数 void show() const { cout << "name:" << m_name << endl; cout << "age:" << m_age << endl; cout << "count:" << count << endl; } // 静态成员函数,不需要通过对象调用 static int getCount() { // const 静态成员函数不能有const修饰 cout << "静态成员函数" << endl; // cout << m_age << endl; // 静态成员函数中,不能访问非静态成员 return count; } }; //静态成员变量必须在类外初始化 int Student::count=0; // 普通的函数 void fun(int a, double b) { cout << "function" << endl; cout << "a:" << a << endl; cout << "b:" << b << endl; } int main() { // 1.函数指针指向普通函数 // 定义 void (*p)(int, double); // 赋值 p = fun; // p = &fun; // 调用 p(1, 1.2); // (*p)(1, 1.2); cout << "=======================" << endl; // 2.函数指针指向类的非静态成员函数 // 定义方式: // 返回值 (类名::*指针变量名)(参数列表); // 返回值 (类名::*指针变量名)(参数列表) const; // 需要说明指向哪一个类的函数 void (Student::*q)() const; // 赋值方式: q = &Student::show; // 调用:调用必须通过类的对象进行调用,因为有一个this指针的隐藏参数 Student s1{"ikun",30}; // 如果s1是一个指针: (s1->*q)(); (s1.*q)(); cout << "=======================" << endl; // 3.函数指针指向类的静态成员函数,定义和使用方式类似于普通的全局函数 // 定义方式: 返回值 (*指针变量名)(参数列表); int (*pf)(); // 赋值方式:p = 类名::函数名; pf = Student::getCount; // 调用: cout << pf() << endl; return 0; }
(4) 练习
设计一个日期类,实现获取"当天"的日期,使用静态成员函数
cpp#include <iostream> #include <time.h> using namespace std; class Date { private: int m_year; int m_mon; int m_day; public: Date(int y = 2000, int m = 1, int d = 1); Date(const Date &d); ~Date(); void show(); void setDate(int y = 2000, int m = 1, int d = 1); // 每次调用函数可以获取当天日期 static Date getToday(); }; int main() { // Date d{2022,9,24}; // d.show(); // 通过类名调用静态函数,获取当前的日期!!! Date d = Date::getToday(); // ???为什么少了一次拷贝构造 d.show(); return 0; } Date::Date(int y, int m, int d):m_year{y}, m_mon{m}, m_day{d} { cout << "构造函数" << endl; } Date::Date(const Date &d):m_year{d.m_year}, m_mon{d.m_mon}, m_day{d.m_day} { cout << "拷贝构造函数" << endl; } Date::~Date() { cout << "~析构函数" << endl; } void Date::show() { // cout << "Date(" << this->m_year << "-" << this->m_mon << "-" << this->m_day << ")" << endl; cout << "Date(" << m_year << "-" << m_mon << "-" << m_day << ")" << endl; } void Date::setDate(int y, int m, int d) { m_year = y; m_mon = m; m_day = d; } // 每次调用函数可以获取当天日期 Date Date::getToday() { // 获取系统当前时间 time_t tm; // 保存当前的时间(秒数) time(&tm); // 根据获取的时间返回一个对象 struct tm *pt = localtime(&tm); return Date{pt->tm_year + 1900, pt->tm_mon + 1, pt->tm_mday}; }
03 单例模式
单例模式是经典设计模式中的一种
程序在运行的时候,通常每一个类都会实例化很多个对象,如:string类的实例,当有100个字符串时,string就会实例化100个string类型的对象
但是,当我们想在程序中表示某一个对象的时候,并且这个对象永远只会存在一个,无论在哪里创建/访问都只会用到哪个唯一的实例,就会有"类只能实例化一个对象"的需求
如:回收站、任务管理器、数据库连接对象、LCD屏幕......
像这种确保一个类只生成一个实例化的模式,称为单例模式(SingleTon)
如何实现:
1. 确保用户无法自己创建对象(只需要把构造函数设置为私有的,删除拷贝构造函数)
2. 让类自己负责管理这唯一的一个对象(使用静态成员对象)
3. 提供一个安全的全局访问点,即提供一个公共的接口,让用户可以使用(获取)这唯一的一个实例
实现方式:
不同的语言,不同的程序员,可能有很多种实现方式,总体可以分为以下两种
饿汉式(预先创建)、懒汉式(延时创建)
饿汉式(预先创建):
唯一的实例在类加载的时候就创建,预先初始化(不管有没有用到都创建这个实例)
线程安全的,可以避免多任务的时候出现多次创建的情况
在类加载的时候创建,调用的时候反应速度快
在类加载的时候创建,有可能永远也用不到这个实例(资源浪费)
懒汉式(延时创建):
唯一的实例在第一次调用获取实例的时候才会被创建,建,延时初始化(不用到就可能永远不会创建)
可能是非线程安全的(C++11后,规定静态对象是线程安全的)
避免了饿汉式那种在没有用到的情况下也会创建实例的问题,资源利用率高
在使用的时候才会创建,调用的时候反应速度没有饿汉式快