目录
一.面向过程和面向对象
二.结构体与类
三.类的定义
四.类的访问限定符
五.封装
六.类的作用域
七.类的实例化
八.类对象模型
1.类对象存储方式
2.计算类对象大小
九.this指针
(1)this指针
(2)this指针特性
前言:类与对象是学习C++的基础,是非常重要的地方。类与对象代表着面向对象中三大特性(封装、继承、多态)中的封装,只要我们用C++,那么就一定要先学好类与对象。
一.面向过程和面向对象我们学习过了C语言,而C++则是在C语言的基础上,增加了类等概念。
C语言是面向过程的,关注的是过程,分析出求解问题的步骤,通过函数调用逐步解决问题。
C++是基于面向对象的,关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交互完成。
二.结构体与类
C语言中,结构体只能定义变量。在C++中,结构体不仅可以定义变量,也可以定义函数。
// C++兼容C struct的用法
// C++同时对struct进行了升级,把struct升级成了类
// 1、结构体名称可以做类型
// 2、里面可以定义函数
struct Student
{
void Init(const char* name, const char* gender, int age)
{
strcpy(_name, name);
strcpy(_gender, gender);
_age = age;
}
void Print()
{
cout << _name << " " << _gender << " " << _age << endl;
}
// 这里并不是必须加_
// 习惯加这个,用来标识成员变量
char _name[20];
char _gender[3];
int _age;
};
struct ListNode
{
int val;
//C++中下面两种写法都可,而C语言只能用注释掉里面的struct ListNode* next
//struct ListNode* next;
ListNode* next;
};
在C++中,更喜欢用class来代替struct。
注意:struct默认是共有,class默认是私有
三.类的定义
class className
{
// 类体:由成员函数和成员变量组成
}; // 一定要注意后面的分号
class为定义类的关键字,className为类的名字,{}中为类的主体,注意类定义结束时后面的分号。
类中的元素称为类的成员;类中的数据称为类的属性或者成员变量;类中的函数称为类的方法或者成员函数。
// 类
// struct 不加访问限定符,默认是public
// class 不加访问限定符,默认是private
class Student
{
public:
// 类体:由成员函数和成员变量组成
void Init(const char* name, const char* gender, int age)
{
strcpy(_name, name);
strcpy(_gender, gender);
_age = age;
}
void Print()
{
cout << _name << " " << _gender << " " << _age << endl;
}
private:
char _name[20];
char _gender[3];
protected:
int _age;
};
int main()
{
// 对象
Student s2;
s2.Init("张三", "男", 18);
s2.Print();
cout << s2._name << endl;
cout << s2._age << endl;
return 0;
}
类的两种定义方式:
(1)声明和定义全部放在类体中
注意:成员函数如果在类中定义,编译器可能会将其当成内联函数处理。
class Student
{
public:
void Print()
{
cout << _name << " " << _gender << " " << _age << endl;
}
private:
char _name[20];
char _gender[3];
};
(2)声明放在.h中(类内),类的定义放在.cpp中(类外)
class Student
{
public:
void Print();
private:
char _name[20];
char _gender[3];
};
//要表明作用域::
void Student::Print()
{
cout << _name << " " << _gender << " " << _age << endl;
}
一般情况下,如果函数较短可以放在类内,函数较长推荐放在类外。
四.类的访问限定符
C++实现封装的方式:用类将对象的属性与方法结合在一块,让对象更加完善,通过访问权限选择性的将其接口提供给外部的用户使用。
访问限定符分为3种:
1.public(公有)
2.protected(保护)
3.private(私有)
说明:
(1)public修饰的成员在类外可以直接被访问
(2)protected和private修饰的成员在类外不能直接被访问(protected是在继承中引入的)
(3)访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现为止
(4)clss的默认访问权限为private,struct的默认访问权限为public(因为struc要兼容C语言)
注意:访问限定符只在编译时有用,当数据映射到内存后,没有任何访问限定符上的区别。
C++中struct和class的区别:
C++需要兼容C语言,所以C++中struct可以当成结构体去使用,另外C++中struct还可以用来定义类和class定义类是一样的,区别是struct的成员默认访问方式是public,class的成员默认访问方式是private。
五.封装
面向对象的三大特性:封装、继承、多态
类和对象中,我们所研究的就是类的封装特性。
封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。
封装本质是一种管理。
封装:更严格管理设计
1、数据和方法封装到一起,类里面
2、想给你自由访问的设计成共有,不想给你直接访问的设计成私有一般情况设计类,成员数据都是私有或者保护,想给访问的函数是共有,不想给你访问时私有或保护
C语言中,数据和方法是分离的,比较自由。
六.类的作用域
类定义了一个新的作用域,类的所以成员都在类的作用域中。
在类体外定义成员,需要使用 ::作用域解析符 指明成员属于哪个类域。
class Person
{
public:
void Print();
private:
char _name[20];
char _gender[3];
int _age;
};
// 这里需要指定Print是属于Person这个类域
void Person::Print()
{
cout << _name << " "_gender << " " << _age << endl;
}
七.类的实例化
用类类型创建对象的过程,称为类的实例化
(1)类只是一个模型一样的东西,限定了类有哪些成员,定义出一个类并没有分配实际的内存空间来存储它。
(2)一个类可以实例化出多个对象,实例化出的对象,占用实际的物理空间,存储类成员变量。
(3)举个例子:类实例化出对象就像是设计一个东西时,类就是设计图,只需要设计出有什么东西,但并没有实体,只有实例化出了对象才能实际存储数据,占用物理空间。
class Person
{
//...
}
int main()
{
Person p;//实例化对象
}
八.类对象模型
1.类对象存储方式
有两种的可能方式:
方式(1):对象中包含类的各个成员(不是计算机的实际存储方式)
这种方式存在缺陷:每个对象中成员变量时不同的,但是调用的是同一份函数,如果按照这种方式存储,当一个类创建多个对象时,每个对象中都会保存一份代码,相同的代码保存多次,浪费空间。
方式(2)只保存成员变量,成员函数存放在公共的代码段。
这个是计算机的实际存储方式。
我们看下面这段代码:
// 总结:没有成员变量的类对象,编译器会给他们分配1byte占位,表示对象存在过
// 类中仅有成员函数
class A2 {
public:
void f2() {}
};
// 类中什么都没有---空类
class A3
{
};
int main()
{
cout << sizeof(A2) << endl;
cout << sizeof(A3) << endl;
A2 aa;
A2 aaa;
cout << &aa << endl;
cout << &aaa << endl;
return 0;
}
这两个类中,一个只有成员函数,一个是空类。
它们的大小比较特殊,因为没有成员变量,我们可能会认为它们的大小都是0,但是实际上不是。
它们的大小都是1,对于没有成员变量的类,编译器会给他们分配1byte占位,表示对象存在过。
结论:一个类的大小,实际就是该类中的成员变量之和,因此要进行内存对齐。
而对于没有成员变量的函数,编译器会给他们分配1byte占位,用来标识这个类,表示对象存在过。
2.计算类对象大小
计算类对象大小,就是计算成员变量的大小,方法与求C语言的结构体大小相同。
它们都会进行内存对齐。
内存对齐:
(1)第一个成员在与结构体偏移量为0的地址处。
(2)其它成员变量要对齐到对齐数的整数倍的地址处。
对齐数 = 编译器默认的一个对齐数与该成员大小的较小值
VS默认对齐数为8
(3)结构体总大小为:最大对齐数的整数倍
最大对齐数:所有变量类型最大者与默认对齐参数较小值
如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
举个例子:
class A
{
private:
int a;
short b;
int c;
char d;
};
a从偏移量为0开始,int占4个字节,到偏移量为4的位置;
b为short,占2个字节,b与默认对齐数8相比,b较小,因此b对齐到2的倍数处,这时因为a占了4个字节,到了偏移量为4的位置,为2的倍数,因此b从偏移量为4的位置开始,占2个字节,到偏移量为6的位置;
c为int,占4个字节,比8小,因此c对齐到4的倍数处,此时偏移量到了6,6不是4的倍数,因此c对齐到距离偏移量6最近的为4的倍数处的位置,即偏移量为8的位置,占4个字节,到偏移量为12的位置;
d为char,占1个字节,比8小,因此d对齐到1的倍数处,c占到偏移量为12的位置,为1的倍数,d占一个字节,到偏移量为13的位置;
a,b,c,d的内存对齐计算之后,应该算总的大小:
所有变量类型最大者:int,4个字节 4比默认对齐数小,因此总大小应该为4的倍数。距离13最近的4的倍数的数为16,因此总大小为16。
九.this指针
(1)this指针
class Date
{
public:
//Print的实际调用
void Print()
{
cout << this << endl;
cout << this->_year << "-" << this->_month << "-" << this->_day << endl;
}
//Init的实际调用
void Init(int year, int month, int day)
{
//this = nullptr; this指针本身不能修改,因为他是const修饰的
// this指向对象可以被修改
cout << this << endl;
this->_year = year;
_month = month;
_day = day;
}
private:
int _year; // 年
int _month; // 月
int _day; // 日
};
int main()
{
Date d1;
Date d2;
cout << "d1:" << &d1 << endl;
cout << "d2:" << &d2 << endl;
cout << endl;
//Init的实际传参
//d1.Init(&d1, 2022, 5, 15);
//d2.Init(&d2, 2022, 5, 20);
d1.Init(2022, 5, 15);
d2.Init(2022, 5, 20);
cout << endl;
//Print的实际传参
//d1.Print(&d1);
//d2.Print(&d2);
d1.Print();
d2.Print();
cout << endl;
Date* p = &d1;
//实际传过去了p
//p->Print(p);
p->Print();
return 0;
}
class Date
{
public:
//Print的实际调用
void Print()
{
cout << this << endl;
cout << this->_year << "-" << this->_month << "-" << this->_day << endl;
}
//Init的实际调用
void Init(int year, int month, int day)
{
//this = nullptr; this指针本身不能修改,因为他是const修饰的
// this指向对象可以被修改
cout << this << endl;
this->_year = year;
_month = month;
_day = day;
}
private:
int _year; // 年
int _month; // 月
int _day; // 日
};
int main()
{
Date d1;
Date d2;
cout << "d1:" << &d1 << endl;
cout << "d2:" << &d2 << endl;
cout << endl;
//Init的实际传参
//d1.Init(&d1, 2022, 5, 15);
//d2.Init(&d2, 2022, 5, 20);
d1.Init(2022, 5, 15);
d2.Init(2022, 5, 20);
cout << endl;
//Print的实际传参
//d1.Print(&d1);
//d2.Print(&d2);
d1.Print();
d2.Print();
cout << endl;
Date* p = &d1;
//实际传过去了p
//p->Print(p);
p->Print();
return 0;
}
输出结果如下:
在这个Date类中,有Init和Print两个成员函数,但是在函数体中没有关于不同对象的区分,那么当d1调用函数时,该函数是如何知道是d1调用的,而不是d2调用的呢?
C++是通过引入this指针来解决该问题的。
C++编译器给每个“非静态的成员函数”增加了一个隐藏的指针参数,让该指针指向当前对象(函数允许是调用该函数的对象),在函数体中所有成员变量的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的。即用户不需要来传递,编译器自动完成。
通过上面的输出结果,我们可以看出d1和d2的地址不同,而直接打印的d1和d2地址就是函数中this指针的地址。
注意:this指针存在于栈中(一般是在栈中的寄存器中)
(2)this指针特性
①this指针的类型:类类型*const'
②只能在“成员函数”的内部使用
③this指针本质上其实是一个成员函数的形参,是对象调用成员函数时,将对象地址作为实参传递给this形参。所有对象中不存储this指针
④this指针时成员函数第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递。



