类和对象(前章)
类和对象(前章)
类的定义
类的定义格式
定义一个类需要使用关键字class,然后指定类的名称,{}中是类的主体,主体内包含类的成员变量和成员函数。
我们可以按照上面的来试一下用关键字class(C++中也可以用struct来定义类,兼容C的同时升级了struct的功能,可以用来定义函数) 定义一下Date类:
#include<iostream>
using namespace std;
class Date{public:void Init(int year, int month, int day) {_year = year;_month = month;_day = day;}
private://为了区分成员变量,一般会加上特殊标识,例如“_”int _year;int _month;int _day;
};int main()
{Date d;d.Init(2025, 1, 1);return 0;
}
定义在类中的成员函数默认为inline。
访问修饰符
数据封装是面向对象的一个重要特点,可以防止函数直接访问类类型的内部成员。关键字public、private、protected称为访问修饰符。
一个类中可以有多个public、private或protected标记区域,每个标记区域在下一个标记区域开始之前或者类主体结束右括号之前都是有效的。
class name
{public:
//公有成员protected:
//受保护成员private:
//私有成员
};
成员和类的默认访问修饰符是private,struct默认为public。一般成员变量都会被限制为private/protected,需要给别人使用的成员函数放为public。
public(公有成员)
#include<iostream>
using namespace std;
class Solution {
public:int count;int getcount(void);void setcount(int num);
};
// 成员函数定义
int Solution::getcount(void) {return count;
}
void Solution::setcount(int num)
{count = num;
}int main()
{Solution s;s.setcount(10);cout << "使用成员函数设置长度:"<<s.getcount() << endl;//不使用成员函数设置长度s.count = 20;cout << "不使用成员函数设置长度:" << s.count << endl;return 0;
}
我们看到编译结果当使用public时,成员变量都是可以访问的。
private(私有成员)
私有成员变量或函数在类的外部是不可访问的,甚至是不可查看的。只有类和友元函数可以访问私有成员。
前面说到,默认情况下,类的所有成员都是私有的,在实际情况中,我们一般会在私有区域定义数据,在公有区域定义相关的函数,以便在类的外部也可以调用这些函数:
#include<iostream>
using namespace std;
class Solution {
public:int size;int getcount(void);void setcount(int num);
private:int count;
};
// 成员函数定义
int Solution::getcount(void) {return count;
}
void Solution::setcount(int num)
{count = num;
}int main()
{Solution s;s.size = 10;cout << "公有size变量:"<<s.size<<endl;//不使用成员函数设置长度//s.count(10); // 这里会报错,因为count是私有成员,不能直接访问s.setcount(10); // 使用成员函数设置长度cout << "不使用成员函数设置长度:" << s.getcount() << endl;return 0;
}
可以看到如果我们在外面访问私有变量count时,代码是会报错的。所以我们通过调用公有的成员函数。
protected(受保护成员)
protected成员变量或函数与私有成员十分相似,但是区别时protected成员在派生类(子类)中可以访问。
#include<iostream>
using namespace std;
class BigSolution
{protected:int size;
};
class Solution:BigSolution{
public:int getcount(void);void setcount(int num);
};
//子类的成员函数
int Solution::getcount(void) {return size;
}
// 成员函数定义void Solution::setcount(int num)
{size = num;
}int main()
{Solution s;// 调用成员函数s.setcount(10);cout<<"使用成员函数获取size:"<<s.getcount()<<endl;return 0;
}
类域
- 类定义一个新的作用域,类的所有成员都在类的作用域中,在类外定义成员时,要使用::操作符指明成员属于哪个类域。
- 类域影响的是编译的查找规则,例如下面的程序中如果在类外使用Init不指定类域Date,编译器就会认为Init是全局函数,在编译过程中就找不到year_这些成员变量。只要指定了类域,编译器就会知道Init是成员函数,就会取类域里面找。
#include<iostream>
using namespace std;
class Date {
public:void Init(int year = 1, int month = 1, int day = 1);
private:int year_;int month_;int day_;
};
//声明定义分离,指明类域
void Date::Init(int year, int month, int day) {year_ = year;month_ = month;day_ = day;
}
int main() {Date d;d.Init(2021, 12, 31);return 0;
实例化
概念
- 类是对象进行一种抽象描述,限定了类有哪些成员变量,这些成员变量只是声明,没有分配空间,用类实例化出对象时,才会分配空间。
- 类就像是房屋设计图,这个设计图可以规划房子具有多少房间,房间大小功能,但也仅仅是个图纸,不是实质的建筑,所以一个类可以实例化出多个对象,实例化出的对象占用实际的物理空间,存储类成员变量。下面就是势力的日期类的两个对象实例化。
对象大小
当我们设计了一个类后,我们该如何知道类对象的大小呢?我先给出一个类:
#include <iostream>
using namespace std;
class Person {
public:void Init(int name = 0, int gender = 0, int age = 0) {name_ = name;gender_ = gender;age_ = age;}void print(){cout << "Name: " << name_ << '\n'<< "Gender: " << gender_ << '\n'<< "Age: " << age_ << '\n';}
private:int name_;int gender_;int age_;
};
int main() {Person s;s.Init(1, 2, 3);s.print();cout << "sizeof(Person) = " << sizeof(s) << " bytes\n";return 0;
}
这个代码允许出来的结果是12,这个12是怎么来的呢?我们猜一下类对象的存储方式。
猜测一:对象中包含类的各个成员既有成员变量也有成员函数,但是每个对象里的成员变量互不相同,所有对象调用的成员函数是同一份机器指令,如果这样存储的话每new一个对象,就会把完成相同的函数代码复制一份导致空间浪费。
猜测二:对象里只放成员变量,成员函数放在公共代码区,这样成员变量独立,互不打扰,成员函数只保留一份,可以大幅节省内存。那么我们类对象的存储方式会是猜测二嘛?
没猜错,按照我们代码运行结果12可以得出类对象的存储方式就是采取猜测二的方式那么下面这段代码的对象大小是多少呢?
#include <iostream>
using namespace std;
class A1 {
public:void F1() {}
private:int _a;
};
class A2
{
public:void F2() {}
};
class A3
{};
int main()
{A1 a1;A2 a2;A3 a3;cout << sizeof(a1)<<endl;cout << sizeof(a2)<<endl;cout << sizeof(a3)<<endl;return 0;
}
注意:当像A3这样类里没有成员变量时,至少会给它开一个字节的空间,这1个字节仅仅是为了占位,表示对象存在过。
总而言之类的大小就是该类中的成员变量之和,进行内存对齐1,特殊的空类会有一个字节来唯一标识这个类。
首成员位置:结构体的第一个成员位于偏移量0处
成员对齐:其他成员对齐到
min(编译器默认对齐数, 成员大小)
的整数倍地址结构体大小:结构体总大小是最大对齐数的整数倍
嵌套结构体:
- 嵌套结构体对齐到其自身最大对齐数的整数倍
- 整体大小是所有最大对齐数(含嵌套结构体)的整数倍
VS默认对齐数:8字节
gcc默认对齐数:通常与成员大小相同(可通过#pragma pack
修改)#include <iostream> using namespace std;// 基础对齐 struct S1 {char a; // 1字节int b; // 4字节 (对齐到4)short c; // 2字节 }; // 大小: 12字节 // 含double struct S2 {char a; // 1字节double b; // 8字节 (对齐到8)int c; // 4字节 }; // 大小: 24字节 // 嵌套结构体 struct Inner {double a; // 8字节int b; // 4字节 }; // 大小: 16字节 struct Outer {char a; // 1字节Inner inner; // 16字节 (对齐到8)short b; // 2字节 }; // 大小: 32字节 int main() {cout << "S1大小: " << sizeof(S1) << endl; cout << "S2大小: " << sizeof(S2) << endl; cout << "Inner大小: " << sizeof(Inner) << endl; cout << "Outer大小: " << sizeof(Outer) << endl; return 0; }
S1(12字节)
0:【a】 //char 1字节
1-3:填充至四的倍数
4-7:【b】【b】【b】【b】 //int 4字节
8-9:【c】【c】 //short 2字节
10-11:填充至四的倍数
S2(24字节)
0:【a】 //char 1字节
1-7:填充至八的倍数
min(编译器默认对齐数, 成员大小)
(8,8)8-15:【b】【b】【b】【b】… //double 8字节
16-19:【c】【c】【c】【c】 //int 4字节
20-23:填充
Outer(32字节)
0:【a】 //char 1字节
1-7:填充至八的倍数
min(编译器默认对齐数, 成员大小)
(8,8)8-23:Inner结构 //double(8)+int(4)+填充(4)
24-25:【b】【b】 //short 2字节
26-31:填充
this指针
还记得我们前文实现的Date类嘛,我们增加点东西再来看一下代码:
#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;d1.Init(2025, 1, 1);d2.Init(2025, 1, 2);d1.Print();d2.Print();return 0;
}
修改的代码里我们定义了两个对象,但是成员函数都在公共代码里,Print函数是怎么可以保证第一个Print调用的d1,第二个调用的是d2呢?
其实这个问题就是this指针帮我们解决的,this指针是一种隐含的形参,编译器编译后,类的成员函数默认都会在形参第一个位置,增加一个当前类类型的指针,比如Date类的Init真实情况是,void Init(Date const this,int year,int month,int day);* 类里的成员函数中访问成员变量,本质也是通过this指针来访问的,例如this->_year = year;
C++规定不能在实参和形参的位置显示this指针,但是可以在函数体内显示this指针。
#include <iostream>
using namespace std;class Date {
public:void Init(int year, int month, int day) {// 编译器自动转换为:this->_year = year;_year = year;_month = month;_day = day;// 显式使用this指针cout << "Init this指针地址: " << this << endl;}void Print() {// 编译器自动转换为:cout << this->_year << ...cout << _year << "-" << _month << "-" << _day;// 显式使用this指针cout << " (对象地址: " << this << ")" << endl;}// 比较函数:显式使用this指针bool IsEqual(const Date& other) {// 使用this指针访问当前对象成员return (this->_year == other._year) &&(this->_month == other._month) &&(this->_day == other._day);}// 链式调用演示:返回this指针Date& AddDay(int days) {_day += days;// 返回当前对象的引用return *this;}
private:int _year;int _month;int _day;
};
int main() {// 创建两个Date对象Date d1, d2;cout << "d1 地址: " << &d1 << endl;cout << "d2 地址: " << &d2 << endl << endl;// 初始化对象d1.Init(2025, 1, 1);d2.Init(2025, 1, 2);cout << "\n===== 成员函数调用 =====" << endl;d1.Print(); // 等价于 Print(&d1)d2.Print(); // 等价于 Print(&d2)cout << "\n===== this指针显式使用 =====" << endl;// 比较两个日期if (d1.IsEqual(d2)) {cout << "d1 和 d2 日期相同" << endl;}else {cout << "d1 和 d2 日期不同" << endl;}cout << "\n===== 链式调用演示 =====" << endl;// 链式调用:连续操作同一个对象d1.AddDay(5).AddDay(3).Print();cout << "说明:连续调用作用于同一个对象 (地址: " << &d1 << ")" << endl;return 0;
}
以上就是类和对象前篇知识的所有内容啦,有不懂的或者任何问题欢迎留言,关注我,高质量文章不断哟~
内存对齐
-
首成员位置:结构体的第一个成员位于偏移量0处
-
成员对齐:其他成员对齐到
min(编译器默认对齐数, 成员大小)
的整数倍地址 -
结构体大小:结构体总大小是最大对齐数的整数倍
-
嵌套结构体:
- 嵌套结构体对齐到其自身最大对齐数的整数倍
- 整体大小是所有最大对齐数(含嵌套结构体)的整数倍
VS默认对齐数:8字节
gcc默认对齐数:通常与成员大小相同(可通过#pragma pack
修改)#include <iostream> using namespace std;// 基础对齐 struct S1 {char a; // 1字节int b; // 4字节 (对齐到4)short c; // 2字节 }; // 大小: 12字节 // 含double struct S2 {char a; // 1字节double b; // 8字节 (对齐到8)int c; // 4字节 }; // 大小: 24字节 // 嵌套结构体 struct Inner {double a; // 8字节int b; // 4字节 }; // 大小: 16字节 struct Outer {char a; // 1字节Inner inner; // 16字节 (对齐到8)short b; // 2字节 }; // 大小: 32字节 int main() {cout << "S1大小: " << sizeof(S1) << endl; cout << "S2大小: " << sizeof(S2) << endl; cout << "Inner大小: " << sizeof(Inner) << endl; cout << "Outer大小: " << sizeof(Outer) << endl; return 0; }
S1(12字节)
0:【a】 //char 1字节
1-3:填充至四的倍数
4-7:【b】【b】【b】【b】 //int 4字节
8-9:【c】【c】 //short 2字节
10-11:填充至四的倍数
S2(24字节)
0:【a】 //char 1字节
1-7:填充至八的倍数
min(编译器默认对齐数, 成员大小)
(8,8)8-15:【b】【b】【b】【b】… //double 8字节
16-19:【c】【c】【c】【c】 //int 4字节
20-23:填充
Outer(32字节)
0:【a】 //char 1字节
1-7:填充至八的倍数
min(编译器默认对齐数, 成员大小)
(8,8)8-23:Inner结构 //double(8)+int(4)+填充(4)
24-25:【b】【b】 //short 2字节
26-31:填充 ↩︎
-