当前位置: 首页 > news >正文

C++类和对象(三)

希望文章能对你有所帮助,有不足的地方请在评论区留言指正,一起交流学习!

目录

1.构造函数

1.1.构造函数体赋值

1.2 初始化列表

1.3 explicit关键字

2.Static成员

2.1.概念

2.2.静态成员函数

2.3.总结

3. 友元

3.1.友元函数

3.2.友元类

4. 内部类

5.匿名对象


1.构造函数

1.1.构造函数体赋值

        如下,在对象实例化的过程中,成员变量的创建时机是在进入构造函数之前;构造函数体中的代码是在成员变量已经创建后执行的,所以属于赋初值,不是初始化。
// 构造函数Date(int year,int month,int day){_year = year;_month = month;_day = day;}

        上述代码中的成员变量都是整型在创建的时候可以不用赋予初始值。 

1.2 初始化列表

       C++规定通过初始化列表(而非构造函数体),才能真正实现成员变量的初始化。初始化列表在成员变量创建时直接赋值,且仅执行一次。所以初始化列表就是成员变量定义的地方。

        其构成为以一个冒号开始,以逗号分隔的数据成员列表,每成员变量后面跟一个放在括号中的初始值或表达式。如下:

//初始化列表Date(int year, int month, int day):_year(year), _month(month), _day(day){  }

需要注意的是:

  1. 每个成员变量在初始化列表中最多只能出现一次(初始化只能初始化一次)
  2. 类中包含以下成员,必须放在初始化列表位置进行初始化:引用成员变量 ,const成员变量,自定义类型成员(且该类没有默认构造函数时)。
  3. 尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使用初始化列表初始化。 也就说,不管写不写初始化列表,在对象创建的时候,都会经过初始化列表。
  4. 成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关。
  5. 函数体和初始化列表都是构造函数的一部分,初始化列表的存在可以分担一些初始化的操作,但是在一些情况下不能独立完成初始化操作。

注意1 说明:

        初始化的核心含义是:为变量分配内存空间,并赋予其初始值的过程。这个过程具有 一次性的特性,一旦变量完成初始化,其内存空间就已确定,后续对它的操作只能是 “赋值”(修改已有内存中的值),而不是 “再次初始化”(重新分配内存)。

        总之:初始化就是:内存分配 + 赋初值

class Stack 
{
public:Stack(int capacity = 10):_a((int*)malloc(sizeof(int)*capacity)), _size(0), _capacity(capacity){}private:int* _a;size_t _size;size_t _capacity;};
class MyQueue
{
public:MyQueue(){}MyQueue(int capacity):_pushst(capacity),_popst(capacity){}
private:Stack _pushst;Stack _popst;
};int main()
{MyQueue mq1;MyQueue mq2(100);return 0;
}

        上述程序的结果说明,在具有默认构造函数的自定义类型,可以不用在初始化列表中写出。

注意2 说明:

        引用成员变量 ,const成员变量其必须在定义的时候初始化;因此必须采用初始化列表的方式初始化;对于有默认构造函数的自定义成员变量,初始化可以不传参数,但是没有的情况下,必须传递参数是自定义类型成员初始化。

class A
{
public:A(int a = 4):_a(a){}
private:int _a;
};class B 
{
public:B(int& ref, int a):_ref(ref), _a(a), _x(1){}private:A _aobj;int& _ref;const int _a;int _x;
};

        上述代码中含有内置类型和自定义类型,int _x=1;其中的1是缺省值,但是其可以在初始化的时候使用,虽然没有调用A类的初始化,但是程序会自动调用其初始化列表。测试程序:

int main()
{int n = 3;B bb1(n, 2);return 0;
}

运行结果

注意4 说明

class Stack
{
public:Stack(int capacity = 10): _size(0), _capacity(capacity),_a((int*)malloc(sizeof(int)* _capacity)){if (_a == nullptr){perror("malloc fail");return;}}
private:int* _a;size_t _size;size_t _capacity;};int main()
{Stack st1;return 0;
}

        上述代码将_a的定义放到最后,其采用的初始化内存空间的值,是_capacity;在初始化列表中,按照是声明的顺序来初始化的,因此,在初始化_a的时候,_capacity仍然是一个随机值,开辟的空间大小也是一个随机值。

class Test
{
public:Test(int a):_a1(a), _a2(_a1){}void Print() {cout << _a1 << " " << _a2 << endl;}
private:int _a2;int _a1;
};
int main() {Test aa(1);aa.Print();
}

上述的输出:

        证明初始化是有顺序的,先初始化的a1再初始化的a2。

1.3 explicit关键字

        构造函数不仅可以构造与初始化对象,对于接收单个参数的构造函数,还具有类型转换的作用。接收单个参数的构造函数具体表现:
1. 构造函数只有一个参数
2. 构造函数有多个参数,除第一个参数没有默认值外,其余参数都有默认值
3. 全缺省构造函数
总结:在创建新的对象的时候,仅仅传递一个参数就可以完成对象的初始化。

引入:

class D
{
public:D(int a):_a(a){ cout << "Init" << endl;}D(const D& d) {cout << "Copy" << endl;_a = d._a;}private:int _a;
};
int main()
{D dd1(5);D dd2 = 7;//隐式类型转换 7以D类构造函数创建新的临时对象,然后根据临时对象拷贝构造生成dd2//但是编译器会优化为 dd2以7直接优化的。//反例//D& dd3 = 9;const D& dd3 = 9;   //证明会产生临时变量 会调用一次 构造函数 D dd4(dd3);         //内置类型转换int a = 10;double d = a;//隐式类型转换 a转换为中间变量 double的类型然后再赋值给d}

        在上述代码中,为了提高效率,连续的调用构造,编译器一般都会优化,将连续的构造整合为一个。

        根据反汇编,可以看出,仅仅调用了一次构造,但是其特性不能忘记,中间变量具有常性,为了方式隐式类型转换,

可以在构造函数中加上explicit ,不让其构造支持隐式转换。

class D
{
public:explicit D(int a):_a(a){ cout << "Init" << endl;}D(const D& d) {cout << "Copy" << endl;_a = d._a;}private:int _a;
};

2.Static成员

引入:计算程序中有多少的对象。

int _count = 0;
class B
{
public:B(){_count++;}B(const B& b) {_count++;}~B(){_count--;}
private:};B& Func(B bb) // 进入调用拷贝构造一次创建新的对象  // 4
{B bb4;   // 5cout << __LINE__ << ": " << _count << endl; // 5return bb4; // 销毁 bb bb4    3
}
B bb1;   // 1int main()
{cout << __LINE__ << ": "<<_count << endl;B bb2;  // 2static B bb3; // 3cout << __LINE__ << ": " << _count << endl;B bb5 = Func(bb3);    //4cout << __LINE__ << ": " << _count << endl;return 0;
}

        由于全局变量的修改在程序的任何地方都可以修改,因此在C++中将全局变量封装在类中,只供一个类的使用,给静态变量增加一个约束。

2.1.概念

        声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用static修饰成员函数,称之为静态成员函数静态成员变量一定要在类外进行初始化。

class B
{
public:B(){_count++;}B(const B& b){_count++;}~B(){_count--;}
private:// 普通成员变量int _b1 = 1;int _b2 = 2;// 静态成员变量static int _count;
};
// 静态成员变量支持类外定义
int B::_count = 0;

        静态成员变量属于类本身,并非类的某个对象,所以它需要在类外进行单独定义,从而为其分配内存空间。

定义位置不受访问限定符的限制

       无论静态成员变量在类中被声明为privateprotected还是public,其定义都能在全局作用域内完成。这是因为:

  •  声明时的访问限定符,是针对访问行为的限制,而非定义行为
  •  定义静态成员变量属于类的实现细节,只要在编译时能访问到类的完整定义,就可以进行定义。

2.2.静态成员函数

class B
{
public:B(){_count++;}B(const B& b){_count++;}~B(){_count--;}// 静态成员函数没有传递 this指针,指定类域或者修改访问限定符就可以访问static int GetCount(){return _count;}
private:// 普通成员变量int _b1 = 1;int _b2 = 2;// 静态成员变量static int _count;
};
// 静态成员变量支持类外定义
int B::_count = 0;int main()
{B bb1;cout << B::GetCount() << endl;return 0;
}

        私有的成员变量访问的时候可以通过GetCount函数,但是一般的成员函数访问需要又对象,传递this指针,为了方便调用,static函数诞生了,这种函数是没有外部对象的条件下调用成员函数。静态变量和静态成员函数一般会成对存在。

引例:

// 设计一个类,在类外面只能在栈上创建对象
// 设计一个类,在类外面只能在堆上创建对象
class C
{
public:static C GetStackObj(){C cc;return cc;}//在成功分配内存后,new会返回一个指向所分配类型对象的指针。static C* GetHeapObj(){return new C;}// 上述两个函数是在类内创建的对象,想要在类外不用对下个直接调用,就加上static
private :int _a1 = 1;int _a2 = 2;};int main()
{C::GetHeapObj;C::GetStackObj;return 0;
}

2.3.总结

静态成员变量:

        静态成员所有类对象所共享,不属于某个具体的对象,存放在静态区;所以在创建的类的时候静态成员就已经初始化了;因此不要将其和普通的成员变量混淆,静态成员变量输出类的本身,其他的成员变量是蓝图。所以其定义的时候要在类外定义,不参加对象的初始化。当然静态成员函数也是受访问限定符限制的。

静态成员函数:

        静态成员函数没有this指针,不能访问任何非静态成员,包括函数非静态成员的函数以及普通的成员函数。如下:随便创建的空函数,也是不可以访问或者复用的。,

3. 友元

3.1.友元函数

        友元函数可以直接访问类的私有成员,它是定义在类外部普通函数,不属于任何类,但需要在类的内部声明,声明时需要加friend关键字。

特点

  • 非成员函数:友元函数不属于类,定义时不需要加类作用域前缀(如 MyClass::)。
  • 访问权限:可以直接访问类的私有和保护成员,但必须通过对象实例之后才可以访问(如 obj.privateVar)。
  • 声明位置:友元声明可以放在类的任意位置(public、protected 或 private),效果相同。

举例:运算符号 <<  >> 的重载

class Date
{
public:Date(int year = 2025, int month = 7, int day = 26): _year(year), _month(month), _day(day){}ostream& operator<<(ostream& _cout){_cout << _year << "-" << _month << "-" << _day << endl;return _cout;}
private:int _year;int _month;int _day;
};
int main()
{Date d1;d1 << cout;// d1 << cout; -> d1.operator<<(&d1, cout); 不符合常规调用// 因为成员函数第一个参数一定是隐藏的this,所以d1必须放在<<的左侧return 0;
}

        上述代码中的<< 的第一个操作数是d1也就是this指针;第二个操作数cout,相反了。为了转过来,只能写到类的外部,但是还要访问函数内部私有的成员变量;所以有了 friend关键字。

class Date
{
public:Date(int year = 2025, int month = 7, int day = 26): _year(year), _month(month), _day(day){}friend	ostream& operator<<(ostream& out, Date& d);private:int _year;int _month;int _day;
};
ostream& operator<<(ostream& out,Date& d)
{out << d._year << "-" << d._month << "-" << d._day << endl;return out;
}
int main()
{Date d1;cout << d1;return 0;
}

        上述代码中,对流输出的运算符重载函数写在类的外面。

friend	ostream& operator<<(ostream& out, Date& d);

        让重载函数突破访问限制;类外函数可以访问类内私有成员

友元函数不推荐多用,除去友元函数 Get Set函数,因为友元增加了耦合;让关联度更加紧密,修改类中成员不好修改。

流输入

istream& operator>>(istream& cin, Date& d)
{cin >> d._year;// 空格代表这一次输入命令的结束cin >> d._month;cin >> d._day;return cin;
}

3.2.友元类

        友元类是一种允许一个类访问另一个类私有(private)和保护(protected)成员的机制。通过友元类,被授权的类可以突破封装限制,直接操作另一个类的内部数据。        

        特性

  • 单向性:如果 A 声明 B 为友元类,B 可以访问 A 的私有成员,但 A 不能访问 B 的私有成员,除非 B 也显式声明 A 为友元。
  • 非继承性:友元关系不能被继承。即使 B 是 A 的友元,B 的派生类也不会自动成为 A 的友元。
  • 非传递性:如果 A 是 B 的友元,B 是 C 的友元,C 不会自动成为 A 的友元
class Time
{friend class Date; // 声明日期类为时间类的友元类,则在日期类中就直接访问Time类中的私有成员变量
public:Time(int hour = 0, int minute = 0, int second = 0): _hour(hour), _minute(minute), _second(second){}private:int _hour;int _minute;int _second;
};
class Date
{
public:Date(int year = 1900, int month = 1, int day = 1): _year(year), _month(month), _day(day){}void SetTimeOfDate(int hour, int minute, int second){// 直接访问时间类私有的成员变量_t._hour = hour;_t._minute = minute;_t._second = second;}private:int _year;int _month;int _day;Time _t;
};

4. 内部类

        内部类(嵌套类) 是定义在另一个类内部的类。

特性

  1. 内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越的访问权限。
  2. 内部类就是外部类的友元类;
  3. 内部类可以在外部类的public、protected、private任何部分。
  4. 内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名。
  5. sizeof(外部类)=外部类,和内部类没有任何关系。内部类独立存在:外部类和内部类的对象在内存中是分离的,彼此不包含对方。
例子:
 
class A
{
private:static int k;int h;
public:class B // B天生就是A的友元{public:void Func(const A& a){cout << k << endl;//OKcout << a.h << endl;//OK}};
};
int A::k = 1;
int main()
{A::B b;b.Func(A());return 0;
}

5.匿名对象

       匿名对象(Temporary Object) 是一种未被命名的临时对象,它在表达式中创建后立即使用,通常在当前语句结束后销毁。是一种的临时值传递方式,常用于简化代码或作为函数参数。

实例:匿名在同一行创建,在同一行销毁。

class C
{public:C(int c = 1) :_c(c){cout << "Init" << endl;}//拷贝构造函数C(C& cc){_c = cc._c;}void Func() const{cout << _c << endl;}~C(){_c = 0;cout << "Destroy" << endl;}private:int _c;};
int main()
{// 普通对象C cc1(10);cc1.Func();//匿名对象C(20); C(20).Func();//匿名对象具有常性 const C& rc = C(30);rc.Func();return 0;
}

总结

1.匿名对象可以调用一次成员函数,普通对象可以调用多次;

2.匿名对象在不被使用的赋值的情况下,用完即销毁;

3.在被赋值的情况下,匿名对象会和被赋值的对象的生命周期一样;

匿名对象的使用

void push_back(const string& s)
{cout << "push_back:" << s << endl;
}int main()
{//传递命名对象(左值)string str("11111");push_back(str);//传递匿名对象(右值)push_back(string("222222"));//传递字面量(隐式类型转换)push_back("222222");return 0;}
 push_back(str):传递命名对象(左值)
  • 过程str 是一个命名对象(左值),通过常量左值引用const string&)传递给 push_back
  • 特点
    • 不创建临时对象,直接绑定引用到 str
    • 避免拷贝,高效且安全(const 确保不修改原对象)。
 //传递匿名对象(右值)push_back(string("222222"));
  • 过程
    1. string("222222") 创建一个匿名 string 对象(右值)。
    2. 该匿名对象通过常量左值引用绑定到 push_back 的参数 s
  • 特点
    • 匿名对象在语句结束后销毁(但因被 const 引用绑定,生命周期延长至函数调用结束)。
    • 仅需一次构造(string 的构造函数),无拷贝。
push_back("222222"):传递字面量(隐式类型转换)
  • 过程
    1. "222222" 是 const char* 类型的字符串字面量。
    2. 隐式转换push_back 期望 const string&,编译器自动调用 string 的构造函数 string(const char*) 创建一个临时 string 对象。
    3. 临时对象通过常量左值引用绑定到 s
  • 特点
    • 隐式创建临时 string 对象,可能导致性能开销(构造 + 析构)。
    • 若 push_back 声明为 push_back(string s)(值传递),会额外触发一次拷贝构造。

http://www.xdnf.cn/news/1195201.html

相关文章:

  • Coze 与 Dify 深度对比:2025 年 AI 智能体平台选型指南
  • VMware Workstation17下安装Ubuntu20.04
  • JVM-GC 相关知识
  • 利用RAII与析构函数避免C++资源泄漏
  • Linux进程替换
  • Pinia快速入门
  • C++20 协程
  • 联表实现回显功能
  • 【Canvas与旗帜】条纹版大明三辰旗
  • 一文速通《多元函数微分学》
  • 从0到1学Pandas(七):Pandas 在机器学习中的应用
  • ART配对软件使用
  • Netty中DefaultChannelPipeline源码解读
  • Python编程:初入Python魔法世界
  • Android ADB命令之内存统计与分析
  • 暑期算法训练.9
  • flink查看taskManager日志
  • 多模态大模型与 AI 落地:从技术原理到实践路径的深度解析
  • Flutter实现Retrofit风格的网络请求封装
  • oracle数据库表空间碎片整理
  • 宏观杠杆率及其数据获取(使用AKShare)
  • 【DM数据守护集群搭建-读写分离】
  • Dify开发教程笔记(一): 文件及系统参数变量说明及使用
  • 消息缓存系统
  • 2025中国GEO优化白皮书:AI搜索优化趋势+行业数据报告
  • 【LLM】Kimi-K2模型架构(MuonClip 优化器等)
  • CSP2025模拟赛2(2025.7.26)
  • 【C/C++】explicit_bzero
  • C++核心编程学习--对象特性--友元
  • [C/C++内存安全]_[中级]_[再次探讨避免悬垂指针的方法和检测空指针的方法]