C++:类和对象

发布时间:2026/10/24 9:34:10
C++:类和对象 目录1.类的定义一、类定义格式二、访问限定符三、类域2.实例化一、实例化概念二、对象大小内存对齐规则:3.this指针4.类的默认成员函数一、构造函数构造函数的特点:注意:二、析构函数析构函数特点三、拷贝构造函数拷贝构造函数特点四、赋值运算符重载五、取地址运算符重载operator 六、const取地址运算符重载operator () const5.再探构造函数6.static成员7.友元8.内部类9.匿名对象1.类的定义一、类定义格式class为定义类的关键字Stack为类的名字{}中为类的主体。类体中内容称为类的成员类中的变量称为类的属性或成员变量; 类中的函数称为类的⽅法或者成员函数。C中struct也可以定义类C兼容C中struct的⽤法同时struct升级成了类明显的变化struct中可以定义函数⼀般情况下我们还是推荐⽤class定义类。定义在类⾯的成员函数默认为inline。class A { public: void Init() { _a 10; } void Print() { cout _a endl; } private: int _a; };二、访问限定符public修饰的成员在类外可以直接被访问protected和private修饰的成员在类外不能直接被访问protected和private是⼀样的。访问权限作⽤域从该访问限定符出现的位置开始直到下⼀个访问限定符出现时为⽌如果后⾯没有 访问限定符作⽤域就到 }即类结束。class定义成员没有被访问限定符修饰时默认为privatestruct默认为public。⼀般成员变量都会被限制为private/protected需要给别⼈使⽤的成员函数会放为public。class A { public: void Init() { _a 10; } void Print() { cout _a endl; } private: int _a; }; int main() { A a; a.Init(); a.Print(); //a._a;//运行时会报错因为在类外面无法访问private中的成员 return 0; }运行结果10三、类域用class/struct大括号{}包裹起来的区域就是类域。类定义了⼀个新的作⽤域类的所有成员都在类的作⽤域中在类体外定义成员时需要使⽤ :: 作用域操作符指明成员属于哪个类域。class A { public: void Init(); void Print();//声明 private: int _a; }; void A::Init()//A:: 表示Init属于A这个类域 { _a 10; } void A::Print()//定义 { cout _a endl;2.实例化一、实例化概念⽤类类型在物理内存中创建对象的过程称为类实例化出对象。class Date 只是一个类型定义编译时只记录类域成员不分配对象内存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;//Date类实例化出对象d1 d1.Init(2026, 10, 20); d1.Print(); return 0; }二、对象大小类实例化之后对象占用内存字节数只算非静态成员变量成员函数、静态成员变量不算在对象大小里面放在静态区里面不属于对象内存统计时只统计非static成员变量并遵循内存对齐规则。内存对齐规则:第⼀个成员在与结构体偏移量为0的地址处。其他成员变量要对⻬到某个数字对⻬数的整数倍的地址处。注意对⻬数 编译器默认的⼀个对⻬数 与 该成员⼤⼩的较⼩值。VS中默认的对⻬数为8结构体总⼤⼩为最⼤对⻬数所有变量类型最⼤者与默认对⻬参数取最⼩的整数倍。如果嵌套了结构体的情况嵌套的结构体对⻬到⾃⼰的最⼤对⻬数的整数倍处结构体的整体⼤⼩就是所有最⼤对⻬数含嵌套结构体的对⻬数的整数倍。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; cout sizeof(d1) endl;//计算d1的大小 return 0; }运行结果123.this指针编译器编译后类的成员函数默认都会在形参第⼀个位置增加⼀个当前类类型的指针叫做this 指针。⽐如Date类的Init的真实原型为 void Init(Date* const this, int year, int month, int day);类的成员函数中访问成员变量本质都是通过this指针访问的如Init函数中给_year赋值,this- _year year;C规定不能在实参和形参的位置显⽰的写this指针(编译时编译器会处理)但是可以在函数体内显 ⽰使⽤this指针。class Date { public: void Init(int year, int month, int day) { //this nullptr;//编译时会报错因为这里this的类型是Date cont * 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(2026, 1, 1); d1.Print(); return 0; }4.类的默认成员函数默认成员函数就是⽤⼾没有显式实现编译器会⾃动⽣成的成员函数称为默认成员函数。⼀个类我们不写的情况下编译器会默认⽣成以下6个默认成员函数一、构造函数构造函数是特殊的成员函数需要注意的是构造函数虽然名称叫构造但是构造函数的主要任务并 不是开空间创建对象(我们常使⽤的局部对象是栈帧创建时空间就开好了)而是对象实例化时初始化对象。构造函数的特点:函数名与类名相同。无返回值。对象实例化时系统会自动调用对应的构造函数。构造函数可以重载。如果类中没有显式定义构造函数则C编译器会自动⽣成⼀个无参的默认构造函数⼀旦用户显式定义编译器将不再⽣成。无参构造函数、全缺省构造函数、我们不写构造时编译器默认⽣成的构造函数都叫做默认构造函 数。但是这三个函数有且只有⼀个存在不能同时存在。⽆参构造函数和全缺省构造函数虽然构成 函数重载但是调⽤时会存在歧义。class Date { public: Date()//构造函数 { _year 1; _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 _year; int _month; int _day; }; int main() { Date d1; Date d2(2026,1,1); d1.Print(); d2.Print(); return 0; }运行结果1/1/1 2026/1/1注意:调用无参构造时“不要写括号”,写了括号时就相当于时一个函数声明编译时会报错。//Date d3();//函数声明Date d3(void);我们不写编译器默认⽣成的构造对内置类型成员变量的初始化没有要求也就是说是是否初始化是不确定的看编译器。class Date { public: void Print() { cout _year / _month / _day endl; } private: int _year; int _month; int _day; }; int main() { Date d1; d1.Print(); return 0; }运行结果-858993460/-858993460/-858993460对于自定义类型成员变量要求调⽤这个成员变量的默认构造函数初始化。如果这个成员变量没有默认构造函数那么就会报错。二、析构函数析构函数与构造函数功能相反析构函数不是完成对对象本身的销毁比如局部对象是存在栈帧的函数结束栈帧销毁他就释放了不需要我们管C规定对象在销毁时会自动调用析构函数完成对象中资源的清理释放工作。析构函数特点析构函数名是在类名前加上字符 ~。无参数无返回值。一个类只能有一个析构函数。若未显示定义系统会自动生成默认的析构函数。对象生命周期结束时系统会自动调用析构函数。跟构造函数类似我们不写编译器自动生成的析构函数对内置类型成员不做处理自定类型成员会调用他的析构函数。还需要注意的是我们显⽰写析构函数对于自定义类型成员也会调用他的析构也就是说⾃定义类型成员无论什么情况都会⾃动调用析构函数。如果类中没有申请资源时析构函数可以不写直接使⽤编译器⽣成的默认析构函数如Date。class stack { public: stack(int n 4)//构造函数 { int* tmp (int*)malloc(sizeof(int) * n); if (tmp nullptr) { perror(malloc); exit(1); } arr tmp; top 0; capacity n; } ~stack()//析构函数 { cout~stackendl; free(arr); arr nullptr; top capacity 0; } private : int* arr; int top; int capacity; }; int main() { stack st; return 0; }运行结果~stack三、拷贝构造函数拷贝构造函数是一种特殊的构造函数用一个已经存在的同类对象去初始化新建对象拷贝构造函数特点拷贝构造函数的第⼀个参数必须是类类型对象的引用使用传值方式编译器直接报错因为语法逻辑上会引发无穷递归调用。 拷贝构造函数也可以多个参数但是第⼀个参数必须是类类型对象的引 用后面的参数必须有缺省值。拷贝构造函数是构造函数的⼀个重载。C规定自定义类型对象进⾏拷贝行为必须调用拷贝构造所以这里自定义类型传值传参和传值返回都会调⽤拷贝构造完成。若未显式定义拷贝构造编译器会⽣成自动生成拷贝构造函数。自动生成的拷贝构造对内置类型成员变量会完成值拷贝/浅拷贝(⼀个字节⼀个字节的拷贝)对⾃定义类型成员变量会调用他的拷贝构造。class Date { public: Date()//构造函数 { _year 1; _month 1; _day 1; } Date(int year, int month, int day)//函数重载 { _year year; _month month; _day day; } Date(Date d)//拷贝构造 { _year d._year; _month d._month; _day d._day; } void Print() { cout _year / _month / _day endl; } private: int _year; int _month; int _day; }; int main() { Date d1(2026, 1, 1); Date d2 d1; Date d3(d1); d1.Print(); d2.Print(); d3.Print(); return 0; }运行结果2026/1/1 2026/1/1 2026/1/1注意因为Date类成员变量是内置类型当我们不写拷贝构造的时候会默认使用系统的拷贝构造也就是完成浅拷贝一个字节一个字节的拷贝运行出的结果和写了拷贝构造的效果是一样的。自定义类型的拷贝构造class stack { public: stack(int n 4) { int* tmp (int*)malloc(sizeof(int) * n); if (tmp nullptr) { perror(malloc); exit(1); } arr tmp; top 0; capacity n; } ~stack() { free(arr); arr nullptr; top capacity 0; } stack(stack st) { arr (int*)malloc(sizeof(int) * st.capacity); if (arr nullptr) { perror(malloc); exit(1); } memcpy(arr, st.arr, sizeof(int) * st.top); top st.top; capacity st.capacity; } void Push(int n) { if (top capacity) { int newcapacity 2 * capacity; int* tmp (int*)realloc(arr, sizeof(int) * newcapacity); if (tmp nullptr) { perror(realloc); exit(1); } capacity newcapacity; arr tmp; } arr[top] n; } void Print() { for (int i 0; i top; i) { cout arr[i] ; } cout endl; } private : int* arr; int top; int capacity; }; int main() { stack st; st.Push(1); st.Push(2); st.Push(3); st.Push(4); st.Push(5); stack st1 st; st.Print(); st1.Print(); return 0; }运行结果1 2 3 4 5 1 2 3 4 5四、赋值运算符重载只能作为类的成员函数重载不能是全局函数。 运算符重载强制要求成员函数因为它需要隐式this指针左操作数必须是类对象。触发条件是两个对象已经存在进行对象赋值。class Date { public: Date(int year, int month, int day) { _year year; _month month; _day day; } Date operator (const Date d1) { if (this ! d1) { _year d1._year; _month d1._month; _day d1._day; } return *this; } void Print() { cout _year / _month / _day endl; } private: int _year; int _month; int _day; }; int main() { Date d1(2026, 1, 1); Date d2(2026, 2, 2); d1.Print(); d2.Print(); d2 d1;//赋值重载 //Date d3 d1;//这是拷贝构造不是赋值重载 d2.Print(); return 0; }五、取地址运算符重载operator 对非const对象取地址返回当前对象的this指针Date* operator() { return this; //return nullptr//返回空值 }六、const取地址运算符重载operator () const对const对象取地址。成员函数后面的const表示这个成员函数不会修改对象成员才能被 const 对象调用。Date* operator() const { return this; //return nullptr//返回空值 }5.再探构造函数之前我们实现构造函数时初始化成员变量主要使⽤函数体内赋值构造函数初始化还有⼀种方式就是初始化列表初始化列表的使⽤⽅式是以⼀个冒号开始接着是⼀个以逗号分隔的数据成员列表每个成员变量后⾯跟⼀个放在括号中的初始值或表达式。每个成员变量在初始化列表中只能出现⼀次语法理解上初始化列表可以认为是每个成员变量定义初始化的地方。引用成员变量const成员变量没有默认构造的类类型变量必须放在初始化列表位置进⾏初始化否则会编译报错。初始化列表中按照成员变量在类中声明顺序进行初始化跟成员在初始化列表出现的的先后顺序无关。建议声明顺序和初始化列表顺序保持⼀致。C11⽀持在成员变量声明的位置给缺省值这个缺省值主要是给没有显⽰在初始化列表初始化的成员使用的。class Date { public: Date() :_year(2026) { } void Print() { cout _year / _month / _day endl; } private: int _year ; int _month 1; int _day 1; }; int main() { Date d1; d1.Print(); return 0; }运行结果2026/1/16.static成员⽤static修饰的成员变量称之为静态成员变量静态成员变量⼀定要在类外进行初始化。静态成员变量为所有类对象所共享不属于某个具体的对象不存在对象中存放在静态区。用static修饰的成员函数称之为静态成员函数静态成员函数没有this指针。非静态的成员函数可以访问任意的静态成员变量和静态成员函数。突破类域就可以访问静态成员可以通过类名::静态成员 或者 对象.静态成员 来访问静态成员变量和静态成员函数。静态成员也是类的成员受public、protected、private 访问限定符的限制。静态成员变量不能在声明位置给缺省值初始化因为缺省值是个构造函数初始化列表的静态成员变量不属于某个对象不走构造函数初始化列表。class A { public: A() { _count; } ~A() { _count; } A(A a) { _count; } static int Getcount() { return _count; } private: static int _count;//类里面声明 }; int A::_count 0;//类外面初始化 int main() { A a; A b; A c b; cout A::Getcount() endl; return 0; }运行结果37.友元友元提供了⼀种突破类访问限定符封装的⽅式友元分为友元函数和友元类在函数声明或者类声明的前⾯加friend并且把友元声明放到⼀个类的里面。外部友元函数可访问类的私有和保护成员友元函数仅仅是⼀种声明他不是类的成员函数。友元函数可以在类定义的任何地⽅声明不受类访问限定符限制。⼀个函数可以是多个类的友元函数。友元类中的成员函数都可以是另⼀个类的友元函数都可以访问另⼀个类中的私有和保护成员。友元类的关系是单向的不具有交换性比如A类是B类的友元但是B类不是A类的友元。class B;//// 前置声明否则A的友元函数声明编译器不认识B class A { public: friend void func(const A a, const B b);//友元声明 private: int _a 1; int _b 2; }; class B { public: friend void func(const A a, const B b);//友元声明 private: int _c 3; int _d 4; }; void func(const A a, const B b) { cout a._a endl; cout b._c endl; } int main() { A a; B b; func(a, b); return 0; }运行结果1 3class A { public: friend class B;//友元声明 private: int _a 1; int _b 2; }; class B { public: void func(const A a) { cout a._a endl; cout _d endl; } private: int _c 3; int _d 4; }; int main() { A a; B b; b.func(a); return 0; }运行结果1 48.内部类如果⼀个类定义在另⼀个类的内部这个内部类就叫做内部类。内部类是⼀个独⽴的类跟定义在全局相比他只是受外部类类域限制和访问限定符限制所以外部类定义的对象中不包含内部类。内部类默认是外部类的友元类。内部类是独立的类只是作用域被限制在外层类里面内部类不默认拥有外部类的this指针。class A { public: class B//B默认就是A的友元 { public: void func(Aa) { cout a._a endl; cout _c endl; } private: int _c 3; int _d 4; }; private: int _a 1; int _b 2; }; int main() { A::B b; A a; b.func(a); return 0; }运行结果1 39.匿名对象用类型(实参) 定义出来的对象叫做匿名对象相比之前我们定义的类型对象名(实参) 定义出来的叫有名对象。匿名对象生命周期只在当前⼀行⼀般临时定义⼀个对象当前用一下即可就可以定义匿名对象。class A { public: A(int a 0) :_a(a) { cout A(int a) endl; } ~A() { cout ~A()endl; } A(const A a) { _a a._a; } private: int _a; }; int main() { //有名对象 A aa1; A aa2(10); //匿名对象--声明周期只在当前一行 A(); A(1); return 0; }运行结果A(int a)//aa1的构造 A(int a)//aa2的构造 A(int a)//A()的构造 ~A() //A()的析构 A(int a)//A(1)的构造 ~A() //A(1)的析构 ~A() //aa2的析构 ~A() //aa1的析构

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询