一. 类成员
1.1 空类
空class定义的对象大小不能为0,实际为1字节,起到了一个占位作用,用来唯一标识,区分各个对象。
class CTest {};
cout << sizeof(CTest) << endl; //1
1.2 类成员属性
类成员属性: 属于对象,只有在定义对象的时候,才会存在, 多个对象中存在多份,彼此独立,互不干扰,属性在对象被销毁也会被销毁。
class CTest {
public:
int m_a; //栈区
CTest() { m_a = 0; }
void show() { cout << m_a << endl; }
void play() { cout << "play" << endl; }
};
CTest p, q;
cout << &p.m_a << ' ' << &q.m_a << endl;
//012FF8B8 012FF8AC
对于堆来讲,生长方向是向下的,也就是向着内存地址增加的方向;对于栈来讲,它的生长方式是向上的,是向着内存地址减小的方向增长。
1.3 类成员方法
类成员方法: 属于类的,编译期就存在,一个类中只会存在一份,多个对象共享这个函数, 他的存在与否和是否定义对象无关。
CTest* pp = nullptr; //指针并不是实际存在的
pp->play(); //利用空指针调用成员函数
二. this 指针
this 关键字 也是指针变量
是类中 非静态成员函数 中,隐藏的第一个参数,默认由编译器去添加
void show(/* CTest* const this */) {
cout << m_a << endl;
cout << this->m_a << endl;
play();
this->play();
}
void play(/* CTest* const this */) { cout << "play" << endl; }
作用:在非静态成员函数中,使用其他成员(包括静态)时,都是通过this指针隐式调用的
类型:当前类对象的指针,类名* const this,指向了调用该函数的对象。作为对象和成员之间的连接桥梁,在函数中使用其他成员可以不用显式的写出this指针,让使用者无感知的使用this指针。
构造函数和析构函数也会有隐藏的this指针
在《C++函数编译原理和成员函数的实现》一节中讲到,成员函数最终被编译成与对象无关的普通函数,除了成员变量,会丢失所有信息,所以编译时要在成员函数中添加一个额外的参数,把当前对象的首地址传入,以此来关联成员函数和成员变量。这个额外的参数,实际上就是 this,它是成员函数和成员变量关联的桥梁。
三. 静态成员
3.1 静态属性
静态属性:不属于对象,也不会包含在对象之中。属于类,编译期存在,存在与否和是否定义对象无关。
调用:可以通过 类名作用域 直接调用静态成员,如果有对象的话,通过对象调用也是可以的。
#include <iostream>
using namespace std;
class CTest {
public:
int m_a = 1;
static int m_b; //带有类内初始值设定项的成员必须为常量
CTest() {
m_a = 1;
m_b = 4; //赋值,更改值
}
};
//静态成员属性,在类外定义 以及初始化,在类外应该省略static关键字
int CTest::m_b = 2;
int main() {
cout << CTest::m_b << endl; //不通过对象直接调用
//2
CTest test;
cout << sizeof(test) << " " << sizeof(CTest) << endl; //4 4
cout << test.m_a << ' ' << test.m_b << endl; //1 4
}
一个类中存在一份的静态属性,多个对象共享这个静态成员。
CTest test2;
cout << &test.m_b << " " << &test2.m_b << endl;
//0065C008 0065C008
无论静态放在哪,内存都在全局/静态区
3.2 静态成员函数
静态成员函数:属于类的,编译期存在,一个类只有一份,多个对象共享这同一份静态的成员函数。
调用: 可以通过 类名作用域直接调用静态成员,如果有对象的话,通过对象调用也是可以的。
与一般成员函数区别:
1. 静态函数没有隐藏的this指针参数
2. 静态函数只能使用静态的成员属性,一般的函数静态、非静态成员都可以使用。
3. 静态函数 通过类名作用域直接调用静态成员,一般函数不能通过类名作用域直接调用(必须通过对象调用)
void fun();
static void funStatic(/* 没有隐藏的this指针 */) { //静态成员函数
cout << "funStatic" << endl;
//cout << this->m_a; //ERROR:“this”只能用于非静态成员函数内部
cout << m_b << endl; //CTest::m_b
fun2(); //可以调用其他静态成员函数
//this->fun(); //ERROR:没有this没法调用一般的成员函数
}
static void fun2() {
cout << "static fun2 " << endl;
}
CTest::funStatic();
//CTest::fun(); //ERROR:非静态成员引用必须与特定对象相对
3.3 常量
3.3.1 初始化参数列表
初始化参数列表:在构造参数列表后,加上“:”后面是要初始化的成员名字,使用"()"进行初始化,才是一个严格意义上的初始化操作。普通的变量也可以在初始化参数列表中进行初始化(不可以对静态初始化),初始化多个成员,使用”,“分割。
class CTest {
public:
int m_a;
//声明
const int m_b; //定义了就要初始化,初始化后,不可修改,不可赋值
//初始化参数列表:在构造参数列表后,加上“:”后面是要初始化的成员名字,使用()进行初始化
//定义
CTest():m_b(2) ,m_a(1){
//m_b = 2; //不允许赋值操作
}
};
初始化参数列表 初始化成员的顺序,取决于成员在类中声明的先后顺序。和写在初始化参数列表中的顺序无关
CTest(int v) :m_a(v),m_b(m_a) { }
3.3.2 常函数
class CTest {
public:
mutable int m_a; //mutable可变的,指该变量在常函数中是可变的
const int m_b; //定义了就要初始化,初始化后 ,不可修改、赋值
//常成员函数,加在void 前是修饰返回值
//const 修饰了this指针指向的对象,const 类名 * const this
void fun( /* const CTest* const this */) const {
//常量指针常量
cout << m_a << ' ' << m_b << endl;
//m_a = 10; 左值错误
//this->m_a = 10; const约束了this指针指向的对象,不能修改
int* p = (int*)&m_a; //注意:const int *类型的值不能用于初始化 int*类型的实体
*p = 30;
cout << m_a << ' ' << m_b << endl;
}
};
const int *类型的值不能用于初始化 int* 类型的实体。因为若此操作合法,就可以直接用int*去修改const int*中的值,这样的话const就没有意义了。想要使用需要强转。
int a = 0;
const int b = 1;
int* const p1 = &a; //const 修饰了指针本身,指针的指向不能修改,但是指针指向的内容可以修改
*p1 = 2;
const int* p2 = &a; //const 修饰了指针指向的内容,不能通过指针去修改内容,但是指针的指向可以改
p2 = &b;
品析常成员函数,普通成员函数,静态成员函数之间的关系
void fun1(/* const CTest* const this */) const {
//fun2(); //this指针无法传递
fun3(); //没有this不受影响
}
void fun2(/* CTest* const this */) {
//fun2->this=fun1->this //const int *类型的值不能用于初始化 int* 类型的实体
fun1();
}
static void fun3() {
}
同名同参的常函数和普通成员函数是重载,优先匹配普通,常量会优先匹配常函数。
静态函数和普通函数同名同参不可同时存在,因为没法区分