
前言本篇博客主要讲解C中类和对象的基本内容。目录一. 类的定义1.1 类定义格式1.2 访问限定符1.3 类域二. 实例化2.1 实例化的概念2.2 对象大小计算练习三. this指针题目练习四. 类的默认成员函数4.1 构造函数4.2 析构函数4.3 拷贝构造函数五. 赋值运算符重载5.1 运算符重载5.2 赋值运算符重载5.3 / --运算符重载5.4 运算符重载5.5 友元函数六. 取地址运算符重载6.1 const成员函数6.2 取地址运算符重载七. 再探构造函数题目练习八. 类型转换九. static成员题目练习十. 友元十一. 内部类十二. 匿名对象十三. 对象拷贝时的编译器优化结束语一. 类的定义1.1 类定义格式1.2 访问限定符访问限定符private、public、protected1.3 类域#includeiostream using namespace std; class Date { public: void init(int day, int month, int year); private: int _day; int _month; int _year; }; void Date::init(int day, int month, int year) { _day day; _month month; _year year; }二. 实例化2.1 实例化的概念这里要注意类内的成员变量只是声明没有被定义我们可以通过代码来验证。#includeiostream using namespace std; class Date { public: int _day; int _month; int _year; }; int main() { Date::_day 0;//不行因为此时_day只是被声明还没有定义所以要先实例化 Date A; A._day 0; return 0; }2.2 对象大小我们这里用Date类来通俗的解释一下类是“模具”对象是模具做出的“实体物品”成员变量是物品的“零件”像Date类的_day/_month/_year每个对象都有独立的成员变量并占用自身空间成员函数是共用的“说明书”如init函数统一存在代码段里无需每个对象重复存储因此对象的内存大小只由成员变量决定成员函数不计入其中。接下来我们来看看如何计算对象大小计算练习成员 a char大小1放在偏移量0的位置占0~0。 成员 b int大小4对齐数是min(8,4)4所以要放在偏移量4的位置跳过1~3占4~7。成员 c double大小8对齐数是min(8,8)8放在偏移量8的位置占8~15。总大小最大对齐数是8当前占0~15共16字节是8的整数倍所以结构体大小是16。我们先计算嵌套的子结构体Sub的大小按照内存对齐规则我们可以轻松算出Sub的大小是8最大对齐数是4。计算Main的大小成员 a short大小2对齐数2放在偏移量0的位置占0~1。成员 b 嵌套的 Sub 嵌套结构体的对齐数是其自身的最大对齐数即4所以要放在偏移量4的位置跳过2~3。Sub 大小是8所以占4~11。成员 c double大小8对齐数8要放在偏移量16的位置跳过12~15占16~23。计算 Main 的总大小所有成员的最大对齐数是8 c 的对齐数当前占0~23共24字节是8的3倍所以 Main 的大小是24字节。上面的程序运行后我们看到没有成员变量的B和C类对象的大小是1为什么没有成员变量还要给1个字节呢因为如果一个字节都不给怎么表示对象存在过呢所以这里给1字节纯粹是为了占位标识对象存在。三. this指针题目练习this指针是 C 非静态成员函数中的一个隐含指针参数用来指向当前调用该成员函数的对象。比如使用对象aa调用aa.Func()时可以理解为编译器在调用Func时偷偷传入了aa因此函数内部的this就保存着对象aa的地址。this本身并不是对象中的成员所以不会占用对象的存储空间它所指向的对象可以位于栈区也可以位于堆区。对于这类基础选择题通常会把this看作函数的隐含形参而形参传统上被认为存放在函数栈帧中因此一般选择 A. 栈。不过从严格意义上讲C 标准并没有规定this一定存放在栈中实际编译器可能通过寄存器传递this。正确答案答案应该是Cp中保存的是空指针值调用p-Print()时可以理解为把p的值传给隐藏的this指针因此此时this也是空指针。由于Print()中并没有通过this访问对象中的成员所以在常见编译器下可能正常执行如果函数中存在this-_a等成员访问就需要通过空的this去访问对象内存通常会因为访问无效内存而导致程序崩溃。四. 类的默认成员函数4.1 构造函数对于第七点我再提一嘴·如果自定义类型成员变量有构造函数但我们依旧使用列表对其初始化列表优先级更高。#includeiostream using namespace std; class Date { public: //无参构造 Date() { _year 2024; _month 1; _day 1; } //带参数构造 Date(int year, int month, int day) { _year year; _month month; _day day; } void Print() { cout _year / _month / _day endl; } private: int _day; int _month; int _year; }; int main() { Date date1; date1.Print(); Date date2(2025, 10, 10); date2.Print(); return 0; }这里我们想一想为什么调用无参构造不能写成Date date1(),因为我们会发现这样无法区分是不是函数声明4.2 析构函数4.3 拷贝构造函数#includeiostream using namespace std; class Date { public: //全缺省构造 Date(int year2014, int month1, int day1) { _year year; _month month; _day day; } //拷贝构造 Date(const Date d) { _year d._year; _month d._month; _day d._day; } void Print() { cout _year / _month / _day endl; } private: int _day; int _month; int _year; }; int main() { Date date1; date1.Print(); Date date2(date1); date2.Print(); return 0; }为什么这里要写成const Date d,而不是Date d呢因为调用拷贝函数的时候要传值传参而自定义类型传值传参要调用拷贝构造这会产生一个死循环所以我们这里要传引用加const是避免手误导致对对象d的修改。#include iostream using namespace std; int test(int a) { int b a 1; return b; } int main() { int c test(10); cout c endl; cout fjguk endl; cout c endl; return 0; }五. 赋值运算符重载5.1 运算符重载#define _CRT_SECURE_NO_WARNINGS #includeiostream using namespace std; class Date { public: //全缺省构造 Date(int year2014, int month1, int day1) { _year year; _month month; _day day; } //拷贝构造 Date(const Date d) { _year d._year; _month d._month; _day d._day; } bool operator(const Date d) { return _day d._day _month d._month _year d._year; } void Print() { cout _year / _month / _day endl; } int _day; int _month; int _year; }; bool operator(const Date c,const Date d) { return c._day d._day c._month d._month c._year d._year; } int main() { Date date1; Date date2 date1;//拷贝构造 operator(date1, date2);//调用全局函数 date1.operator(date2);//调用成员函数 date1 date2;//优先调用成员函数 return 0; }5.2 赋值运算符重载#define _CRT_SECURE_NO_WARNINGS #includeiostream using namespace std; class Date { public: //全缺省构造 Date(int year2014, int month1, int day1) { _year year; _month month; _day day; } //拷贝构造 Date(const Date d) { _year d._year; _month d._month; _day d._day; } //赋值重载 Date operator(const Date d) { _year d._year; _month d._month; _day d._day; return *this; } bool operator(const Date d) { return _day d._day _month d._month _year d._year; } void Print() { cout _year / _month / _day endl; } int _day; int _month; int _year; }; bool operator(const Date c,const Date d) { return c._day d._day c._month d._month c._year d._year; } int main() { Date date1; Date date2 date1;//拷贝构造 operator(date1, date2);//调用全局函数 date1.operator(date2);//调用成员函数 date1 date2;//优先调用成员函数 Date date3; date3.operator(date1);//调用赋值重载 date3 date1; return 0; }5.3 / --运算符重载#define _CRT_SECURE_NO_WARNINGS #includeiostream using namespace std; class Date { public: //全缺省构造 Date(int year2014, int month1, int day1) { _year year; _month month; _day day; } //前置 Date operator() { _day; return *this; } //后置 Date operator(int) { Date temp *this; _day; return temp; } void Print() { cout _year / _month / _day endl; } private: int _day; int _month; int _year; }; int main() { Date date1; date1; date1.Print(); date1; date1.Print(); return 0; }这里只是简单演示日期类的还涉及日期以及年代的变换。还有一点我们要清楚前置和后置的区别前置是先修改对象值再使用修改后的值后置是先使用对象原值再修改对象值这一点也体现在重载和-和-上。还有后置不能返回引用如果引用返回的话它返回的是一个临时变量的别名这个临时变量生命周期结束后可能会导致野引用。5.4 /运算符重载#define _CRT_SECURE_NO_WARNINGS #includeiostream using namespace std; class Date { //友元函数声明 friend ostream operator(ostream out, const Date d); friend istream operator(istream in, Date d); public: //全缺省构造 Date(int year2014, int month1, int day1) { _year year; _month month; _day day; } //拷贝构造 Date operator(const Date d) { _year d._year; _month d._month; _day d._day; return *this; } void Print() { cout _year / _month / _day endl; } private: int _day; int _month; int _year; }; ostream operator(ostream out, const Date d) { out d._year / d._month / d._day endl; return out; } istream operator(istream in, Date d) { cout 请输入年月日 endl; in d._year d._month d._day; return in; } int main() { Date test1; cin test1; cout test1; return 0; }5.5 友元函数我们先来看看重载为全局函数想要访问私有变量有几种方法成员放公有Date提供getxxx函数友元函数重载为成员函数注意友元声明写在类内可放在 public/protected/private 任意位置访问权限不影响友元生效建议统一放 public 区可读性更高。六. 取地址运算符重载6.1 const成员函数我们需要明白const对象在调用成员函数上存在一些限制如图这里又要说到我们之前在const引用提及的权限放大和权限缩小问题const修饰的对象表明该对象只读不改所以我们也应该给隐含的this指针加上const避免触及权限放大的问题。还有一点constDate*constthis这两个const的含义不同外层的const表示this指针所指对象内容不可修改内层const表示this指针本身不可修改。我们在这里推荐能加const就加const这样一来const对象和非const对象都可以调用也可以避免误操作导致对象被修改。6.2 取地址运算符重载#define _CRT_SECURE_NO_WARNINGS #includeiostream using namespace std; class Date { //友元函数声明 friend ostream operator(ostream out, const Date d); friend istream operator(istream in, Date d); public: Date* operator() { return nullptr; } const Date* operator()const { return nullptr; } //全缺省构造 Date(int year2014, int month1, int day1) { _year year; _month month; _day day; } //拷贝构造 Date operator(const Date d) { _year d._year; _month d._month; _day d._day; return *this; } void Print()const { cout _year / _month / _day endl; } private: int _day; int _month; int _year; }; ostream operator(ostream out, const Date d) { out d._year / d._month / d._day endl; return out; } istream operator(istream in, Date d) { cout 请输入年月日 endl; in d._year d._month d._day; return in; } int main() { Date test1; cout test1 endl; return 0; }七. 再探构造函数注意哦每个构造函数都有初始化列表。为什么引用成员变量const成员变量一定要放在初始化列表初始化呢因为这两个规定必须在定义的时候初始化而初始化列表可以认为是每个成员变量定义初始化的地方所以要在初始化列表中初始化。为什么没有默认构造的类类型变量一定要放在初始化列表初始化呢我们要明白成员变量无论如何都会过一遍初始化列表因为这是他们定义初始化的地方如果没有在初始化列表初始化类类型那么就会调用它的默认构造函数如果没有默认构造函数那么就会报错。成员变量声明中的缺省值有什么用呢主要是给没有在初始化列表初始化的成员变量使用在之前我们也提到过如果我们不对内置类型初始化编译器可能会给其随机值但如果我们有缺省值编译器就会接受成员变量声明中的缺省值来初始化。#include iostream using namespace std; class Time { public: Time(int hour 0) :_hour(hour) { cout Time() endl; } private: int _hour; }; class Date { public: Date(const int xx,int year0,int month0,int day0) :_day(day) ,_month(month) ,_year(year) ,_x(1) ,_y(xx) ,_t(1) {} void Print()const { cout _year / _month / _day endl; } private: int _year0; int _month0; int _day0; const int _x0; const int _y0; int* ptr (int*)malloc(12); Time _t1; }; int main() { Date test1(11, 2026, 1, 1); test1.Print(); return 0; }题目练习#includeiostream using namespace std; class A { public: A(int a) :_a1(a) ,_a2(_a1) {} void Print() { cout _a1 _a2 endl; } private: int _a2 2; int _a1 2; }; int main() { A aa(1); aa.Print(); return 0; }该程序的运行结果是是什么A.输出 1 1 B.输出 2 2 C.编译错误 D.输出 1 随机值 E.输出 1 2.答案选D,注意初始化列表按成员变量声明顺序来初始化所以先进行_a2(_a1)再进行_a1(a)。八. 类型转换#includeiostream using namespace std; class A { public: A(int a10) :_a1(a) ,_a2(_a1) { cout A(int a) endl; } A(const A a) :_a1(a._a1) ,_a2(a._a2) { cout A(const A a) endl; } void Print()const { cout _a1 _a2 endl; } private: int _a2 2; int _a1 2; }; class Stack { public: void push(const A aa) { //...... } private: A _arr[10]; int _top; }; int main() { //隐式类型转换 //2先构造一个临时对象再用这个临时对象拷贝构造aa2 //编译器遇到连续构造拷贝构造-优化为直接构造 A aa2 2; aa2.Print(); Stack st; //第一种方法 A aa3(3); st.push(aa3); //第二种方法利用隐式转换 st.push(3); return 0; }#includeiostream using namespace std; class A { public: //explicit不允许隐式类型转换 explicit A(int a110,int a220) :_a1(a1) ,_a2(a2) { cout A(int a) endl; } A(const A a) :_a1(a._a1) ,_a2(a._a2) { cout A(const A a) endl; } void Print()const { cout _a1 _a2 endl; } private: int _a2 2; int _a1 2; }; class Stack { public: void push(const A aa) { //...... } private: A _arr[10]; int _top; }; int main() { //多参数隐式类型转换 A aa1 { 5,5 }; Stack st; st.push(aa1); //多参数隐式类型转换 st.push({ 5,5 }); return 0; }九. static成员#define _CRT_SECURE_NO_WARNINGS #includeiostream using namespace std; class A { public: A() { _scount; } A(const A d) { _scount; } ~A() { --_scount; } static int GetACount() { return _scount; } void test() { GetACount(); _scount; _a1; } static int _scount; int _a1 1; }; int A::_scount 0; int main() { cout A::GetACount() endl; A a1, a2; A a3 a1; cout A::GetACount() endl; cout a1.GetACount() endl; cout a1._scount endl; cout A::_scount endl; return 0; }需要注意当静态成员变量受到访问限定符限制时我们仍无法在类外直接用域作用限定符或者对象.静态成员进行访问。题目练习#define _CRT_SECURE_NO_WARNINGS #includeiostream using namespace std; class A {}; class B {}; class C {}; class D {}; C c; int main() { A a; B b; static D d; return 0; return 0; }AD B A C BB A D C CC D B A DA B D C EC A B D FC D A B第一题答案是 E。首先全局变量在main函数之前就创建了那么c先初始化再来看局部变量a,b,因为先定义的先初始化那么a,b依次初始化d是静态局部变量是第一次运行到它所在位置的时候才初始化只有全局静态变量才会在main函数之前初始化所以d排在a,b后面。第二题答案是B。首先要明白d虽然是静态局部变量但它的生命周期和c一样是全局的那么最早调用析构函数的应该是a,b因为它们的生命周期是局部的又因为后定义先析构那么b,a依次析构然后是d,c。十. 友元#define _CRT_SECURE_NO_WARNINGS #includeiostream using namespace std; //前置声明否则A的友元函数声明编译器不认识B class B; class A { public: //友元声明 friend void func(const A aa, const B b); private: int _a1 1; int _a2 2; }; class B { public: //友元声明 friend void func(const A aa, const B b); private: int _b1 3; int _b2 4; }; void func(const A aa, const B bb) { cout aa._a1 endl; cout bb._b1 endl; } int main() { A aa; B bb; func(aa, bb); return 0; }#define _CRT_SECURE_NO_WARNINGS #includeiostream using namespace std; //前置声明否则A的友元函数声明编译器不认识B class B; class A { public: //友元声明 friend class B; private: int _a1 1; int _a2 2; }; class B { public: void func1(const A aa) { cout aa._a1 endl; cout _b1 endl; } void func2(const A aa) { cout aa._a2 endl; cout _b2 endl; } private: int _b1 3; int _b2 4; }; int main() { A aa; B bb; bb.func1(aa); bb.func2(aa); return 0; }十一. 内部类#define _CRT_SECURE_NO_WARNINGS #includeiostream using namespace std; class A { public: class B { public: friend void Print(const A::B bb); void foo(const A a) { cout _k endl; cout a._h endl; } private: int _b 1; }; private: static int _k; int _h 1; }; int A::_k 1; void Print(const A::B bb) { cout bb._b endl; } int main() { A aa; A::B bb; Print(bb); bb.foo(aa); return 0; }十二. 匿名对象#define _CRT_SECURE_NO_WARNINGS #includeiostream using namespace std; class A { public: A(int a 0) :_a(a) { cout A(int a 0) endl; } ~A() { cout ~A() endl; } void Print() { cout _a endl; } private: int _a 10; }; int main() { //有名对象 A aa1; A aa2(1); //匿名对象 A(); A(1); A bb; bb.Print(); //为了更方便 A().Print(); return 0; }十三. 对象拷贝时的编译器优化了解即可不同编译器优化效果不同。#define _CRT_SECURE_NO_WARNINGS #includeiostream using namespace std; class A { public: A(int a 0) :_a(a) { cout A(int a 0) endl; } A(const A aa) :_a(aa._a) { cout A(const A aa) endl; } A operator(const A aa) { cout A operator(const A aa) endl; if (this ! aa) { _a aa._a; } return *this; } ~A() { cout ~A() endl; } void Print() { cout void Print() endl; } private: int _a 10; }; void f1(A aa) {} A f2() { A aa; cout ######## endl; return aa; } int main() { A aa1 1;//构造临时对象拷贝构造给aa1—直接构造 cout endl; const A aa2 1;//构造临时对象 cout endl; f1(aa1);//拷贝构造给形参 cout endl; f1(A(1));//构造A(1)拷贝构造aa—直接构造 cout endl; f1(1);//(隐式类型转换)构造临时对象拷贝构造aa—直接构造 cout endl; A aa3(1); f1(aa3);//先构造aa3拷贝构造给aa cout endl; f2();//先构造aaaa拷贝构造临时对象—直接构造 cout endl; f2().Print();//先构造aa拷贝构造给临时对象临时对象调用Print()函数—直接构造临时对象调用Print()函数 cout endl; A ret; ret f2();//优化为构造ret构造aa(充当临时对象)赋值给ret return 0; }结束语类和对象到这就全部学完了下一篇我们将学习内存管理。感谢您的浏览如若发现术语或者代码有问题欢迎在评论区指出。传送门C基础二