目录
一.类的概念和定义
C语言中的结构体到了C++中就变成了类,而且有一个比struct更好的关键字是class,C语言结构体只能写变量,类能写成员变量/函数,甚至嵌套类。定义类不要忘记了分号!
class classname//类的名字
{
//成员变量/函数,嵌套类
};//分号很重要
类的函数有两种定义方式,一种是声明和定义都放在类里面(此时函数会默认变成内联函数)。第二种是声明定义分离,可防止多个文件调用.h文件导致链接错误(前提是声明定义都写在.h文件中),声明放入类中在.h文件中,定义在.cpp文件中,.cpp文件中的函数名前需要加上类名。
//.h文件中
#include<iostream>
using namespace std;
class Date
{
void print();//.h只写声明
int _year;
int _month;
int _day;
};
//.cpp文件中
#include"Date.h"
void Date::print()
{
//定义
}
二.类的访问限定符
访问限定符有:public,protected,private。
类访问限定符就是限制了在类外面访问类的成员的权限。这里我们认为protect和private是一样的,这两个的区别要在继承才能有所体现。
1. public修饰的成员在类外可以直接被访问2. protected和 private 修饰的成员在类外不能直接被访问 ( 此处 protected 和 private 是类似的)3. 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止4. 如果后面没有访问限定符,作用域就到 } 即类结束。5. class的默认访问权限为 private , struct 为 public( 因为 struct 要兼容C)注意: 访问限定符只在编译时有用,当数据映射到内存后,没有任何访问限定符上的区别!
问:C++的class和struct的区别?
答:C++兼容C,所以struct可以当成结构体也可以当成类,和class的区别是前者默认访问权限是public后者是private。继承和模板参数列表位置也有区别。
三.封装
封装是C++三大特性之一,封装就是将数据和操作数据的方法有机结合,隐藏对象的属性和具体实现,对于使用者只能使用其对外公开的接口与其交互。目的是让使用者无需关心内部的实现,直接使用对应的接口即可,比较方便。
四.类的作用域
在类的外面定义成员的时候需要指定类域,就如文章最开始演示的.cpp和.h分离那样给成员函数指定类域。
五.类的实例化
#include<iostream>
using namespace std;
class Date//类的声明,不占任何空间
{
public:
int _year;
int _month;
int _day;
};
int main()
{
Date d;//类的实例化,占空间
d._year = 2023;//修改类的成员变量
d._month = 9;
d._day = 7;
return 0;
}
六.类的对象模型
1.类的大小计算
C++中类的大小计算和C语言的结构体是一样的,都遵循内存对齐。
#include<iostream>
using namespace std;
class A
{
public:
char a;
int b;
char c ;
};
int main()
{
cout << sizeof(A) << endl;//输出结果为12字节
return 0;
}
2.类的成员函数存放规则
类中的成员函数存放位置一般有三种:
1.函数存放在实例化的类里面,这样比较浪费空间,因为每个实例化的类调用这个函数的代码都是相同的。
2.实例化的类里面存放一个成员函数表的地址。
3.成员函数存放在公共代码区,成员变量就存放在实例化的类里面。
可见第三种是比较优的方案,所以也可以得出计算类的大小时,不用算成员函数的结论。
假如一个类什么成员都没有那么实例化之后的大小仍然有1字节,目的是用来占位,表示我是这个类实例化出来的对象,存在过。下面是一段代码:
#include<iostream>
using namespace std;
class A
{
int a;
};
class B
{
void b()
{}
};
class C
{
};
int main()
{
cout << sizeof(A) << endl;//输出结果为4字节
cout << sizeof(B) << endl;//输出结果为1字节
cout << sizeof(C) << endl;//输出结果为1字节,和B的输出结果对比说明VS下的成员函数存放在公共代码区
//空类也会有1字节的大小,用来占位,表示存在过,不然无法区分下面两个实例化的类
C c1;
C c2;
return 0;
}
七.this指针
类实例化的对象在调用成员函数时会额外有一个this指针传参,this指针指向对象,不会显示出来。这样就可以让两个不同的对象调用同一个函数时能够区分是哪个对象。
#include<iostream>
using namespace std;
class A
{
public:
void Init(int a)//括号里实际上应该是(A* const this, int a)隐藏了一个this指针
{
_a = a;//这句话实际上是(this->_a = a;)暗含了一个this指针指向成员变量
}
int _a;
};
int main()
{
A a1;
a1.Init(1);//调用成员函数,传入1
cout << a1._a << endl;//输出结果为1
return 0;
}
C++ 编译器给每个 “ 非静态的成员函数 “ 增加了一个隐藏 的指针参数,让该指针指向当前对象 ( 函数运行时调用该函数的对象 ) ,在函数体中所有 “ 成员变量 ” 的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编 译器自动完成 。
注意:
1.this指针由const修饰,所以是不能更改的
2.this指针是对象调用函数时的“形式参数”,所以生命周期就是当前函数的作用域
3.对象调用函数传递的this指针实际上就是传递当前对象的地址,this指针不存在对象中,vs编译器一般会通过寄存器ecx传递this指针
总的来说this指针就是调用函数的对象的地址。
this指针是可以为空的
#include<iostream>
using namespace std;
class A
{
public:
void Init()//隐藏的形式参数this指针为空
{
//什么都不做
}
int _a;
};
int main()
{
A* a1=nullptr;
a1->Init();//this指针为空,相当于是调用这个函数,把a1的值传给函数的隐藏参数this指针
(*a1).Init();//这句和上句是一样的
a1->_a;//一般编译器会无视这句话,因为没有意义,所以也不会报错,老编译器则不一定
return 0;
}
上面这段代码实际上是可以运行的,成员函数调用逻辑是将前面调用函数的对象的地址直接传给this指针,这里并没有对空指针进行解引用。