1.访问限定符
三种访问限定符:public(公有)、private(私有)、protected(保护)
访问限定符:用于修饰成员,限定成员的权限。
一般类中的成员变量是被private(私有)修饰;成员函数是被public修饰。这样操作的好处是类中的成员变量只有类中的成员函数才可以访问,不可公开引用,成员函数可以由公开引用
访问限定符的作用域:从当前访问限定符开始,直到下一个访问限定符出现
(可以看完类的实例化部分再返回看这一块的代码)
#include<bits/stdc++.h>
using namespace std;
class Date
{
public:
void Init(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
private:
// 声明
int _year;
int _month;
int _day;
};
// 定义
int year;
int main()
{
//访问私有的成员变量(若把注释部分去除,会报错,可以试试)
//这里因为_year是私有的,所以不能在main里面访问并修改
//Date::_year = 1;
//year = 1;
//d1._year;
//d2._year;
// 类实例化出对象
Date d1;
Date d2;
d1.Init(2024, 9, 13);
d2.Init(2025, 9, 14);
//访问公有的成员函数
d1.Print();
d2.Print();
//打印结果
cout << sizeof(d1) << endl;
cout << sizeof(d2) << endl;
return 0;
}
2.类
定义类
- 在c++中一般使用class来定义类,但是也可以使用c语言中的struct。c++中struct既可当做类(函数+变量)来使用,也可以当做一般的结构体(只有变量)一样使用。
- 若无限定修饰符,使用class定义类时,默认修饰的限定符是private私有;struct默认为public公有
- 默认类里面的函数是inline函数(内联函数)
下面是一个类的示例
#include<bits/stdc++.h>
using namespace std;
//一个模拟栈类
class Stack
{
//公有化(类外可以访问到)
public:
void Init(int n = 4)//缺省参数
{
arry = (int*)malloc(sizeof(int*) * n);
if (nullptr == arry)
{
cout << "创建失败";
return;
}
capcity = n;
top = 0;
}
//取栈顶
int Top()
{
if (top > 0)
return arry[top-1];
}
//插入数据
void Push(int x)
{
arry[top] = x;
top++;
}
void arry_length()
{
for(int i = 0;i<top;i++)
cout << arry[i];
cout << endl << top;
}
//销毁数据
void Destroy()
{
free(arry);
arry = nullptr;
capcity = 0;
}
//私有化(只能在类里面访问到)
private:
//声明成员变量
int* arry;
size_t capcity;
size_t top;
};
int main()
{
Stack st1;
st1.Init();
st1.Push(1);
st1.Push(2);
st1.Push(3);
st1.Push(4);
cout << st1.Top()<<endl;
st1.arry_length();
st1.Destroy();
return 0;
}
3.类的实例化
区别是否声明主要看是否给其分配空间。类是对象的抽象概念,类里面只是声明了有哪些函数和变量,实例化就是创建了类的对象。举例来说,建房子,类就像是一个工程图纸,实例化就是根据图纸来分配空间建造一栋房子,可以使用这张图纸来建造许多相同的房子。也就是一个类可以抽象出许多个实例化对象,类无法储存数据,实例化才能储存数据。
3.1实例化对象的大小
- 类的实例化对象大小只看实例化给的成员变量的字节数,不会加上成员函数的字节数。即类实例化时,只会存储成员变量,不会存储成员函数。
- 对于没有成员变量的类,即只有成员函数,但是没有成员变量;甚至说是成员变量和函数都没有。因计算类对象大小时不计算成员函数的的大小,只计算成员变量的大小,那是否这个类对象的大小是0呢?🧭不是的,没有成员变量的类对象,(甚至什么都没有的类)仍为其分配1byte空间大小,用来占位,但不存储有效数据,用于标识对象的存在。简言之:没有成员变量的类对象,大小为1byte,用来占位,但不存储有效数据
计算实例化类对象的大小:
#include<bits/stdc++.h>
using namespace std;
//定义一个Date类
class Date
{
public:
void Init(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
private:
// 声明,没分配空间
int _year;
int _month;
int _day;
};
// 定义,分配了空间
int year;
int main()
{
/*Date::_year = 1;
year = 1;*/
// 1->N
// 类实例化出对象
Date d1;
Date d2;
d1.Init(2024, 8, 6);
d2.Init(2025, 8, 7);
//d1._year;
//d2._year;
d1.Print();
d2.Print();
cout << sizeof(d1) << endl;
cout << sizeof(d2) << endl;
return 0;
}
根据运行的结果可以看到:d1,d2的字节数都是12个字节,d1和d2都有三个成员变量且都是int类型,3*4=12字节,都是12个字节,所以实例化的对象的大小计算,只计算成员变量的大小,不计算成员函数的大小。
下面的有成员变量和成员函数的类A;只有成员函数的类B;什么都没有的类C
#include<bits/stdc++.h>
using namespace std;
//计算一下A/B/C实例化的对象是多大?
class A
{
public:
void Print()
{
cout << _ch << endl;
}
private:
char _ch;
int _i;
};
// 没有成员变量的类对象,开1byte,占位,不存储有效数据
// 标识对象的存在
class B
{
public:
void Print()
{
//...
}
};
class C
{};
int main()
{
A a;
B b;
C c;
cout << sizeof(a) << endl;
cout << sizeof(b) << endl;
cout << sizeof(c) << endl;
cout << &b << endl;
cout << &c << endl;
return 0;
}
根据结果可以看到没有成员变量的类对象的大小时1个字节
3.2成员函数和变量的保存位置
🧭成员函数的指针是在编译时就已经确定,没有存在对象中;但是成员变量是存在于对象中。
下面两个代码就可以体现出来
//第一个代码
#include<iostream>
using namespace std;
class A
{
public:
void Print()
{
cout << "A::Print()" << endl;
}
private:
int _a;
};
int main()
{
A* p = nullptr;
p->Print();
return 0;
}
//第二个代码
#include<iostream>
using namespace std;
class A
{
public:
void Print()
{
cout << "A::Print()" << endl;
cout << _a << endl;
}
private:
int _a;
};
int main()
{
A* p = nullptr;
p->Print();
return 0;
}
第一个代码运行正常,第二个代码运行崩溃。乍一看,两个代码都是野指针的错用,应该两个都会崩溃?但为什么第一个正常……?🧭这是因为,在编译时,就已经将成员函数确定好了,那么在运行时,第一个代码虽然使用p->Print,但是实际上,这个Print中的cout结果并不会根据p进行任何变化,编译时就是确定好的,所以运行正常;但是第二个代码,Print函数中有cout<<_a,因为_a是成员变量,成员变量是保存在对象中,所以当对象指针变为nullptr时,就找不到这个对象,输出cout<<_a时就会崩溃。
3.2内存对齐的规则
内存对齐情况,虽然浪费了些许空间,但是内存对齐的情况下,若需要访问某一个或某些变量,访问的速度更快。
对齐 未对齐
🧭下图就是内存对齐的好处,一次读取读取四个字节。若访问_i,未对齐的情况下,需要两次读取才能完全读取到_i,拼好据;然而,在对齐的情况下,只需要读取一次数据,即可完整的读取到_i这个数据。若是放大这种情况,内存对齐下的访问速度是没未齐的两倍,快!
4.this指针
-
Date类中有Init与Print两个成员函数,函数体中没有关于不同对象的区分,那当d1调⽤Init和 Print函数时,该函数是如何知道应该访问的是d1对象还是d2对象呢?那么这⾥就要看到C++给了 ⼀个隐含的this指针解决这⾥的问题
-
编译器编译后,类的成员函数默认都会在形参第⼀个位置,增加⼀个当前类类型的指针,叫做this 指针。⽐如Date类的Init的真实原型为, void Init(Date* const this, int year, int month, int day)
-
类的成员函数中访问成员变量,本质都是通过this指针访问的,如Init函数中给_year赋值, this- >_year = year;
-
C++规定不能在实参和形参的位置显⽰的写this指针(编译时编译器会处理),但是可以在函数体内显 ⽰使⽤this指针。
#include<iostream>
using namespace std;
class Date
{
public:
// void Init(Date* const this, int year, int month, int day)
void Init(int year, int month, int day)
{
// 编译报错:error C2106: “=”: 左操作数必须为左值
// this = nullptr;
// this->_year = year;
_year = year;
this->_month = month;
this->_day = day;
}
void Print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
private:
// 这⾥只是声明,没有开空间
int _year;
int _month;
int _day;
};
int main()
{
// Date类实例化出对象d1和d2
Date d1;
Date d2;
// d1.Init(&d1, 2024, 3, 31);
d1.Init(2024, 3, 31);
d1.Print();
d2.Init(2024, 7, 5);
d2.Print();
return 0;
}