【访问限定符说明】
1. public 修饰的成员在类外可以直接被访问
2. protected 和 private 修饰的成员在类外不能直接被访问 ( 此处 protected 和 private 是类似的)
3. 访问权限 作用域从该 访问限定符 出现的位置开始直到下一个访问限定符出现时为止
class Stack // 类型 { public: void Push(int x) { // Init(); //... } void Init(int N = 4) { // ... top = 0; capacity = 0; } //private: int* a; int top; int capacity; };
若有private,则public的作用域到private,private的作用域到结尾。
若没有,则直接到结尾,整个都为公开public。
5. class 的默认访问权限为 private , struct 为 public( 因为 struct 要兼容 C)
这就是为什么,struct中,变量访问结构体中的值,可以随便访问。
面向对象的三大特性:封装、继承、多态。(最重要的三个特性)
在类和对象阶段,主要是研究类的封装特性,那什么是封装呢?
封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来
和对象进行交互。
封装本质上是一种管理,让用户更方便使用类。 一种管理方式。
举例说明:
在C语言中,实现栈时,大家如果都很遵循规则,访问栈顶元素时,就会调用Top函数,但是总会有人直接去 打印st.a[top],但是可能此时的top不一定是栈顶,可能也是栈顶的前一个,每个人实现的方式都不同,所以就会出现问题。
但是在C++中,就体现了封装的作用,是一种管理,就会将类的成员变量设置为私有private,就不会让你去访问,只是通过函数接口来访问,这样就不会出现问题。
类定义了一个新的作用域 ,类的所有成员都在类的作用域中 。 在类体外定义成员时,需要使用 ::
作用域操作符指明成员属于哪个类域。
class Person { public: void PrintPersonInfo(); private: char _name[20]; char _gender[3]; int _age; }; // 这里需要指定PrintPersonInfo是属于Person这个类域 void Person::PrintPersonInfo() { cout << _name << " "<< _gender << " " << _age; }
那就会有人想,可不可以这样使用 Person::_age = 1;不是命名空间域访问里面的变量时,就可以通过域操作符来访问吗?
但是这里是有区别的,命名空间域相当于设置了一堵围墙,他将里面的变量围了起来,只能通过域操作符来访问到里面变量,函数等,但是类的作用域(类域)它是一堵虚拟墙,他没有实体,就相当于加了密码锁的一张图纸,没有实质内容的,必须按照图纸造出对象以后,才可以通过域操作符来访问。
类实例化出对象就像现实中使用建筑设计图建造出房子,类就像是设计图 ,只设
计出需要什么东西,但是并没有实体的建筑存在,同样类也只是一个设计,实例化出的对象
才能实际存储数据,占用物理空间
class A { public : void PrintA () { cout << _a << endl ; } private : char _a ; }; A aa; cout<<sizeof(aa)<<endl;
在计算类对象的大小时,我们可以类比计算 结构体大小,只不过不同的一点是,类中加了成员函数,我们不知道成员函数是否需要占空间???
调用函数时,是通过其地址去找到函数的,那么是函数指针吗??
2.类对象的存储方式猜测
那成员函数到底怎么存储的呢??
缺陷:每个对象中成员变量是不同的,但是调用同一份函数,如果按照此种方式存储,当一
个类创建多个对象时, 每个对象中都会保存一份代码,相同代码保存多次,浪费空间。那么
如何解决呢?
代码只保存一份,在对象中保存存放代码的地址
只保存成员变量,成员函数存放在公共的代码段,只算成员变量,成员函数不算空间
那么方案三不同于方案二的是,方案三没有将函数地址放到类中。
因为公共区,大家都知道的地方,就没必要每个人再给一把钥匙,直接开放,大家想去就去了。
那我们就去通过结果去推测:
我们会发现:一个类的大小,实际就是该类中”成员变量”之和,当然要注意内存对齐 。
注意空类的大小,空类比较特殊,编译器给了空类一个字节来唯一标识这个类的对象。
当开辟了多个类时,类里面为空,若不占空间的话,那就是没有,这个类不存在,所以占一个字节要证明这个类是存在的。
所以,现在懂了吗?类的大小只跟成员变量有关系,和成员函数没有关系!
忘记内存对齐了吗?
结构体内存对齐规则
1. 第一个成员在与结构体偏移量为 0 的地址处。
2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
注意:对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。
VS 中默认的对齐数为 8
3. 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整
体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
我们定义一个日期类:
class Data { public: void Init(int year, int month, int data) { _year = year; _month = month; _data = data; } void Print() { cout << _year << " " << _month << " " << _data << endl; } private: int _year; int _month; int _data; }; int main() { Data d1; Data d2; d1.Init(2022, 10, 8); d2.Init(2022, 10, 9); d1.Print(); d2.Print(); }
想一想,既然成员函数都在公共区中,那么调用的就是同一个函数Print,那么为什么结果不相同呢??
void Print() { cout << _year << " " << _month << " " << _data << endl; } void Print(Data*this) { cout << this->_year << " " << this->_month << " " << this->_data << endl; }
原因在这里:当调用类的成员函数时,会在公共区去调用这个函数Print,其默认的第一个参数是this指针,存放调用它的那个类的地址。
C++ 中通过引入 this 指针解决该问题,即: C++ 编译器给每个 “ 非静态的成员函数 “ 增加了一个隐藏 的指针参数,让该指针指向当前对象 ( 函数运行时调用该函数的对象 ) ,在函数体中所有 “ 成员变量 ” 的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编 译器自动完成 。
this指针的定义和传递,都是编译器的活,我们不能去抢,但是我们可以再类里面用this指针
所以,这些步骤都是编译器完成的,我们不需要写出来。
1. this 指针的类型:类类型 * const ,即成员函数中,不能给 this 指针赋值。
void Print(Data* const this)
{
cout << this->_year << " " << this->_month << " " << this->_data << endl;
}
const在*后面,限制的是指针变量,this指针不可以被修改。
Data const*this
const Data*this 这两都是限制的this指针指向的那个变量不能被修改
2. 只能在 “ 成员函数 ” 的内部使用,不可以出了函数使用,只能在函数内部使用。
3. this 指针本质上是 “ 成员函数 ” 的形参 ,当对象调用成员函数时,将对象地址作为实参传递给
this 形参。所以 对象中不存储 this 指针 。
所以作为形参,他也是在栈帧中,在栈区,但在vs中,因为this指针频繁调用,就放到了ecx寄存器自动传递。
来一道题考考你!
1.p本身就作为类的地址,直接传递给void Print(),p为空指针,但没有解引用,可以!
2.p直接传递给void Print(),可以,但是 做了一个这样的操作,this->_a,this本身是一个空指针,去访问,那就是解引用了,空指针怎么可能解引用呢??所以是运行错误!
类和对象很好的解决了C语言中的许多问题,其中有很多细节需要我们留心!
下期再见!及时消化
Copyright © 2023 leiyu.cn. All Rights Reserved. 磊宇云计算 版权所有 许可证编号:B1-20233142/B2-20230630 山东磊宇云计算有限公司 鲁ICP备2020045424号
磊宇云计算致力于以最 “绿色节能” 的方式,让每一位上云的客户成为全球绿色节能和降低碳排放的贡献者