news 2026/1/18 9:14:47

8.C++入门:类和对象|static成员|友元|内部类|匿名对象|对象拷贝时的编译器优化

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
8.C++入门:类和对象|static成员|友元|内部类|匿名对象|对象拷贝时的编译器优化

static成员

  • ⽤static修饰的成员变量,称之为静态成员变量,静态成员变量⼀定要在类外进⾏初始化。
  • 静态成员变量为所有类对象所共享,不属于某个具体的对象,不存在对象中,存放在静态区。
  • ⽤static修饰的成员函数,称之为静态成员函数,静态成员函数没有this指针。
  • 静态成员函数中可以访问其他的静态成员,但是不能访问⾮静态的,因为没有this指针。
  • ⾮静态的成员函数,可以访问任意的静态成员变量和静态成员函数。
  • 突破类域就可以访问静态成员,可以通过类名::静态成员或者对象.静态成员来访问静态成员变量和静态成员函数。
  • 静态成员也是类的成员,受public、protected、private访问限定符的限制。
  • 静态成员变量不能在声明位置给缺省值初始化,因为缺省值是个构造函数初始化列表的,静态成员变量不属于某个对象,不⾛构造函数初始化列表。
// 实现⼀个类,计算程序中创建出了多少个类对象? #include<iostream> using namespace std; class A { public: A() { ++_scount; } A(const A& t) { ++_scount; } ~A() { --_scount; } static int GetACount() { return _scount; } private: // 类⾥⾯声明 static int _scount; }; // 类外⾯初始化 int A::_scount = 0; int main() { cout << A::GetACount() << endl; A a1, a2; A a3(a1); cout << A::GetACount() << endl; cout << a1.GetACount() << endl; // 编译报错:error C2248: “A::_scount”: ⽆法访问 private 成员(在“A”类中声明) //cout << A::_scount << endl; return 0; }

如果不实例化对象,需要计算count

//A aa;有名对象 A aa; //为了调用而创建的,所以要-1 cout << aa.GetCount()-1 << endl; //A() 这种写法叫匿名对象,生命周期只在这一行 cout << A().GetCount()-2 << endl;

方法3是使用静态成员函数,没有this指针
静态成员变量和静态成员函数,本质受限制的全局变量和全局函数,专属这个类,受类域和访问限定符的限制
求1+2+3+…+n_牛客题霸_牛客网

class Sum { public: Sum() { _ret += _i; ++_i; } static int GetRet() { return _ret; } private: static int _i; static int _ret; }; int Sum::_i = 1; int Sum::_ret = 0; class Solution { public: int Sum_Solution(int n) { // 变⻓数组 Sum arr[n]; return Sum::GetRet(); } };

静态成员函数不可以调用非静态成员函数,也不能访问非静态成员变量,静态成员函数没有this指针
非静态成员函数可以调用类的静态成员函数

友元

  • 友元提供了⼀种突破类访问限定符封装的⽅式,友元分为:友元函数和友元类,在函数声明或者类声明的前⾯加friend,并且把友元声明放到⼀个类的⾥⾯。
  • 外部友元函数可访问类的私有和保护成员,友元函数仅仅是⼀种声明,他不是类的成员函数。
  • 友元函数可以在类定义的任何地⽅声明,不受类访问限定符限制。
  • ⼀个函数可以是多个类的友元函数。
  • 友元类中的成员函数都可以是另⼀个类的友元函数,都可以访问另⼀个类中的私有和保护成员。
  • 友元类的关系是单向的,不具有交换性,⽐如A类是B类的友元,但是B类不是A类的友元。
  • 友元类关系不能传递,如果A是B的友元,B是C的友元,但是A不是C的友元。
  • 有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多⽤。
#include<iostream> using namespace std; // 前置声明,都则A的友元函数声明编译器不认识B class B; class A { // 友元声明 friend void func(const A& aa, const B& bb); private: int _a1 = 1; int _a2 = 2; }; class B { // 友元声明 friend void func(const A& aa, const B& bb); 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; }
#include<iostream> using namespace std; class A { // 友元声明 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.func1(aa); return 0; }

内部类

  • 如果⼀个类定义在另⼀个类的内部,这个内部类就叫做内部类。内部类是⼀个独⽴的类,跟定义在全局相⽐,他只是受外部类类域限制和访问限定符限制,所以外部类定义的对象中不包含内部类。
  • 内部类默认是外部类的友元类。
  • 内部类本质也是⼀种封装,当A类跟B类紧密关联,A类实现出来主要就是给B类使⽤,那么可以考虑把A类设计为B的内部类,如果放到private/protected位置,那么A类就是B类的专属内部类,其他地⽅都⽤不了。
#include<iostream> using namespace std; class A { private: static int _k; int _h = 1; public: class B // B默认就是A的友元 { public: void foo(const A& a) { cout << _k << endl; //OK cout << a._h << endl; //OK } int _b1; }; }; int A::_k = 1; int main() { cout << sizeof(A) << endl; A::B b; A aa; b.foo(aa); return 0; }

求1+2+3+…+n_牛客题霸_牛客网

class Solution { // 内部类 class Sum { public: Sum() { _ret += _i; ++_i; } }; static int _i; static int _ret; public: int Sum_Solution(int n) { // 变⻓数组 Sum arr[n]; return _ret; } }; int Solution::_i = 1; int Solution::_ret = 0;

匿名对象

  • ⽤类型(实参)定义出来的对象叫做匿名对象,相⽐之前我们定义的类型对象名(实参)定义出来的叫有名对象
  • 匿名对象⽣命周期只在当前⼀⾏,⼀般临时定义⼀个对象当前⽤⼀下即可,就可以定义匿名对象。
class A { public: A(int a = 0) :_a(a) { cout << "A(int a)" << endl; } ~A() { cout << "~A()" << endl; } private: int _a; }; class Solution { public: int Sum_Solution(int n) { //... return n; } }; int main() { A aa1; // 不能这么定义对象,因为编译器⽆法识别下⾯是⼀个函数声明,还是对象定义 //A aa1(); // 但是我们可以这么定义匿名对象,匿名对象的特点不⽤取名字, // 但是他的⽣命周期只有这⼀⾏,我们可以看到下⼀⾏他就会⾃动调⽤析构函数 A(); A(1); A aa2(2); // 匿名对象在这样场景下就很好⽤,当然还有⼀些其他使⽤场景,这个我们以后遇到了再说 Solution().Sum_Solution(10); return 0; }

对象拷⻉时的编译器优化

  • 现代编译器会为了尽可能提⾼程序的效率,在不影响正确性的情况下会尽可能减少⼀些传参和传返回值的过程中可以省略的拷⻉。
  • 如何优化C++标准并没有严格规定,各个编译器会根据情况⾃⾏处理。当前主流的相对新⼀点的编译器对于连续⼀个表达式步骤中的连续拷⻉会进⾏合并优化,有些更新更"激进"的编译器还会进⾏跨⾏跨表达式的合并优化。
  • linux下可以将下⾯代码拷⻉到test.cpp⽂件,编译时⽤g++ test.cpp -fno-elideconstructors的⽅式关闭构造相关的优化。
#include<iostream> using namespace std; class A { public: A(int a = 0) :_a1(a) { cout << "A(int a)" << endl; } A(const A& aa) :_a1(aa._a1) { cout << "A(const A& aa)" << endl; } A& operator=(const A& aa) { cout << "A& operator=(const A& aa)" << endl; if (this != &aa) { _a1 = aa._a1; } return *this; } ~A() { cout << "~A()" << endl; } private: int _a1 = 1; }; void f1(A aa) {} A f2() { A aa; return aa; } int main() { // 传值传参 // 构造+拷⻉构造 A aa1; f1(aa1); cout << endl; // 隐式类型,连续构造+拷⻉构造->优化为直接构造 f1(1); // ⼀个表达式中,连续构造+拷⻉构造->优化为⼀个构造 f1(A(2)); cout << endl; cout << "***********************************************" << endl; // 传值返回 // 不优化的情况下传值返回,编译器会⽣成⼀个拷⻉返回对象的临时对象作为函数调⽤表达式的返回值 // ⽆优化 (vs2019 debug) // ⼀些编译器会优化得更厉害,将构造的局部对象和拷⻉构造的临时对象优化为直接构造(vs2022 debug) f2(); cout << endl; // 返回时⼀个表达式中,连续拷⻉构造+拷⻉构造->优化⼀个拷⻉构造 (vs2019 debug) // ⼀些编译器会优化得更厉害,进⾏跨⾏合并优化,将构造的局部对象aa和拷⻉的临时对象和接收返回值对象aa2优化为⼀个直接构造。(vs2022 debug) A aa2 = f2(); cout << endl; // ⼀个表达式中,开始构造,中间拷⻉构造+赋值重载->⽆法优化(vs2019 debug) // ⼀些编译器会优化得更厉害,进⾏跨⾏合并优化,将构造的局部对象aa和拷⻉临时对象合 并为⼀个直接构造(vs2022 debug) aa1 = f2(); cout << endl; return 0; }

void func(A aa1) { } A aa1(1); //构造 A aa2(aa1); //拷贝构造 A aa3 = aa1; //拷贝构造+赋值拷贝 aa2 = aa3 //赋值拷贝 //拷贝构造 func(aa); //构造+拷贝构造=构造 func(A(2)); //拷贝构造+构造=构造 func(3);

同一个表达式中,
构造+构造->构造
构造+拷贝构造->构造
拷贝构造+拷贝构造->拷贝构造

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2025/12/28 22:34:04

YOLOv11模型训练新选择:PyTorch+GPU云环境部署指南

YOLOv11模型训练新选择&#xff1a;PyTorchGPU云环境部署指南 在智能安防、自动驾驶和工业质检等场景中&#xff0c;实时目标检测的需求正以前所未有的速度增长。面对复杂多变的视觉任务&#xff0c;开发者不仅需要更高效的模型架构&#xff0c;还必须解决训练过程中的算力瓶颈…

作者头像 李华
网站建设 2025/12/28 22:33:00

Vue.js 过渡 动画

Vue.js 过渡 & 动画 在Vue.js中,过渡和动画是提升用户体验和界面动态效果的重要功能。本文将详细介绍Vue.js中的过渡和动画系统,包括其基本概念、使用方法以及一些高级技巧。 基本概念 过渡 过渡是Vue.js提供的一种在元素插入或删除时自动添加动画效果的方式。它允许…

作者头像 李华
网站建设 2026/1/14 23:49:43

leetcode 1351. 统计有序矩阵中的负数 简单

给你一个 m * n 的矩阵 grid&#xff0c;矩阵中的元素无论是按行还是按列&#xff0c;都以非严格递减顺序排列。 请你统计并返回 grid 中 负数 的数目。示例 1&#xff1a;输入&#xff1a;grid [[4,3,2,-1],[3,2,1,-1],[1,1,-1,-2],[-1,-1,-2,-3]] 输出&#xff1a;8 解释&am…

作者头像 李华
网站建设 2025/12/28 22:31:25

生成何以智能?——基于六十四卦状态空间的原理认知新范式

作者&#xff1a;周林东 摘要&#xff1a;当前人工智能范式在可解释性与泛化能力上面临根本挑战&#xff0c;其根源在于基于“静态实体”的本体论预设。本文主张&#xff0c;智能的突破有赖于转向以“动态生成”为核心的新范式。为此&#xff0c;我们从融贯中国古典生成思想与…

作者头像 李华
网站建设 2026/1/10 14:24:21

Ruby 语法概览

Ruby 语法概览 引言 Ruby 是一种广泛使用的编程语言,以其简洁和优雅著称。它被设计为一种通用语言,适用于多种编程任务,从Web开发到脚本编写,再到数据科学。本文旨在提供一个全面的Ruby语法概览,帮助读者快速了解并掌握Ruby的基础知识。 标准库 Ruby的标准库非常丰富,…

作者头像 李华
网站建设 2026/1/15 9:45:52

C 函数指针与回调函数

C 函数指针与回调函数 引言 在C语言编程中,函数指针和回调函数是两个非常重要的概念。函数指针允许我们将函数作为参数传递给其他函数,而回调函数则是在特定事件发生时自动调用的函数。这两个概念在软件开发中有着广泛的应用,尤其是在编写插件、驱动程序以及各种中间件时。…

作者头像 李华