类的定义
类的定义格式
- class是作为定义类的关键字,classname为类的名字,具体语法为:class classname{};在类中的变量称为成员变量或者类的属性;类中的函数称为成员函数或者类的方法;具体代码如下:
#include<iostream> using namespace std; class Person { public: void GiveVal(int age)//成员函数 { _age = age; } int _age;//成员变量 }; int main() { Person p1; p1.GiveVal(18); cout << " Person's age is: " << p1._age; return 0; }
👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇
运行结果为:但一般来说成员变量应该放在private:里,而这里为了方便演示就不放了。
-
如上代码一般成员变量习惯在变量名前加一个“_”,这是为了更好的和成员函数中的参数分辨开来。
-
定义在类里面的成员函数默认前面加inline。
-
补充:C++中struct也可以定义类,在C++里struct升级成了类,而且在C++的struct创建的类里可以定义函数,而且struct名称就可以直接当成类型而不需要typedef struct......代码如下:
#include<iostream>
using namespace std;
struct ListNode
{
void Init(int x)
{
next = nullptr;
val = x;
}
ListNode* next;
int val;
};
int main()
{
return 0;
}
访问限定符
- C++实现封装的方式,用类将对象的属性和方法结合在一起,通过访问限定符规定访问权限选择性地将借口提供给用户使用。
- 访问限定符分为public、private和protected;public修饰的成员在类外可以直接使用,反之被private和protected修饰的成员在类外不能直接使用。代码如下:
-
访问权限作⽤域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止,如果后面没有访问限定符,作用域就到 }即类结束。
-
对于成员变量,一般都是放在private里避免成员变量被随意修改,而需要给别人使用的成员函数则放在public里。
-
注意:class定义成员中如果没有访问限定符在里面的话则默认访问限定符为private,而struct的则是public;如下图可知。
-
类域
- 类定义了一个新的作用域,当我们想要在类外对类内的成员函数或者成员变量进行定义时需要使用" :: "作用域操作符指明成员属于哪个域;如下代码所示:
#include<iostream> using namespace std; class Stack { public: //类内声明成员函数然后类外实现 void Init(int x = 4); private: int* arr; size_t capacity; size_t top; }; //类外实现成员函数 void Stack::Init(int x) { arr = (int*)malloc(sizeof(int) * x); if (nullptr == arr) { perror("Stack malloc fail!"); return; } capacity = x; top = 0; } int main() { Stack st; st.Init(); return 0; }
👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇
调试结果为:;
- 类域影响的是编译的查找规则,下⾯程序中Init如果不指定类域Stack,那么编译器就把Init当成全 局函数,那么编译时,找不到array等成员的声明/定义在哪⾥,就会报错。指定类域Stack,就是知道Init是成员函数,当前域找不到的array等成员,就会到类域中去查找。
实例化
- 用类类型在内存中创建对象的过程称为类实例化出对象。
-
类是一种抽象的表述方式,就像一个模型,它明确了对象应具备的特性。在定义类时,我们指定了一些成员变量,尽管它们在类的定义阶段并未实际占用内存空间。只有当我们通过类实例化(创建对象)时,对应的内存空间才会被分配。具体在代码里如下:
#include<iostream> 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 main() { //Date类实例化出对象(d1,d2) Date d1; Date d2; d1.Init(2004, 1, 10); d1.Print(); d2.Init(2005, 7, 3); d2.Print(); return 0; }
👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇
运行结果为:
-
⼀个类可以实例化出多个对象,实例化出的对象 占⽤实际的物理空间,存储类成员变量。举个例子:类实例化出对象的过程就像是现实中工程师拿着图纸建房子,类就是图纸,对象就是房子,图纸里面设计的房屋的个数、房屋的功能、家具等,但并没有实体存在(这也对应了类里面创建的成员变量只是声明没有开辟空间),而实例化出的对象分配物理内存存储数据,也就是建出来的房子能住人,有实体的存在(这对应了对象中的成员变量)
对象的大小
- 对象的大小计算是同C的struct结构体的大小计算方法一致,都是根据内存对齐的规则来计算。由下代码和图可见:
#include<iostream>
using namespace std;
// 计算⼀下A/B/C实例化的对象是多⼤?
class A
{
public:
void Print()
{
cout << _ch << endl;
}
private:
char _ch;
int _i;
};
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;
return 0;
}
👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇
运行结果为:
为什么上面的class B和class C的大小为1呢?首先我们分析一下class B,class B的类里面有一个成员函数func()如果我们认为func()在class B中占用了一个字节那就错了,因为class C里没有成员函数和成员变量,而这个1其实是类在内存中的占位字节,回到func()中,func成员函数实际上并没有存储在对象b中,想想看如果我们通过这个类B实例化n个对象,那么成员函数就要重复储存n次。实际上成员函数是存储在一个公共代码区,调用函数被编译成汇编指令[call地址](就到公共代码区找到被调用的成员函数的函数指针的地址),其实编译器在编译连接时就要找到函数的地址,不是在运行时找。
内存对齐的规则
-
第⼀个成员在与结构体偏移量为0的地址处。
-
其他成员变量要对齐到对齐数的整数倍的地址处。
-
注意:对齐数 = 编译器默认的⼀个对齐数与该成员大小比较后取较小值
-
VS中默认的对齐数为8.
-
结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。
-
如果嵌套了结构体的情况,嵌套的结构体对齐到自己最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍
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(int year, int month, int day) { //事实上其实可以不写this-> 因为编译器会处理; this->_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; //实际初始化的原代码为:d1.Init(&d1, 2004,1,10); d1.Init(2004,1,10); d1.Print(); Date d2; d2.Init(2005, 7, 3); d2.Print(); return 0; }
👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇
运行结果为:
下面有几道关于this指针的题目,代码如下:
1.
#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; }
👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇
运行结果为:
运行的结果为正常打印,开始我们可能以为p是个空指针,而p->print()是对空指针的解引用,但实际上不是的,由上面所提及的成员函数是放在一个公共区里,通过汇编可以看到执行了call指令然后直接跳转执行成员函数的内容,所以这里并没有对空指针p解引用。
2.
#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;
}
👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇
运行结果为:
运行结果并没有正常打印,因为后面显示代码为-1073741819。虽然上面那一道题目和这一道很类似,但是在成员函数中cout << _a << endl;这一串代码的原形其实是cout<<this->_a<<endl;而这里是对空指针解引用了,所以程序崩溃。
END!