<C++>类和对象-下

 


目录

一、构造函数的初始化

1. 构造函数体赋值

2. 初始化列表 

2.1 概念

2.2 隐式类型转换式构造

2.3 explicit关键字

二、static静态成员

1. 概念

2. 特性

三、友元

1. 友元函数

2.友元类

四、内部类

1. 概念

五、匿名对象

1. const引用匿名对象

2. 匿名对象的隐式类型转换 

总结


一、构造函数的初始化

1. 构造函数体赋值

在创建对象时,编译器都过调用构造函数,给对象中各个成员变量一个合适的初始值

class Date
{
public:Date(int year, int month, int day){_year = year;_month = month;_day = day;}private:int _year;int _month;int _day;
};
        虽然上述构造函数调用之后,对象中已经有了一个初始值,但是不能将其称作为类对象成员的初始化,构造 函数体中的语句只能将其称作为赋初值 ,而不能称作初始化。因为 初始化只能初始化一次,而构造函数体内 可以多次赋值

        那么真正的初始化在哪里呢?

2. 初始化列表 

初始化列表是构造函数的一部分

2.1 概念
        初始化列表:以一个 冒号开始 ,接着是一个以 逗号分隔的数据成员列表 ,每个 " 成员变量 " 后面跟一个 放在括 号中的初始值或表达式。
class Date
{
public:Date(int year, int month, int day): _year(year), _month(month), _day(day){}private:int _year;int _month;int _day;
};
【注意】
1. 每个成员变量在初始化列表中最多只能出现一次(初始化只能初始化一次)
2. 类中包含以下成员,必须放在初始化列表位置进行初始化
  • 引用成员变量
  • const成员变量
  • 自定义类型成员(该类没有默认构造函数)

const 与 &(引用)共同特征就是都必须在定义时初始化。在类内,成员变量只是声明,在对象实例化时,才对对象整体定 义开空间,而对象的成员定义的地方就在构造函数处——初始化列表(构造函数内不是定义,是赋值,真正的定义在初始化列表)

对于自定义类型,如果没有在初始化列表初始化,那么编译器会自动调用其自身的构造函数进行初始化,而对于内置类型,如果没有在初始化列表初始化,那么其值是随机的,编译器对其不做处理,所以C++11新增:对类内成员函数的声明处,允许给缺省值,就是为了初始化时给初始化列表;如果在初始化列表处对内置类型初始化,那么缺省值就不管用了

回顾:

        默认构造函数:编译器自动生成的、自己写的全缺省的、无参数的,共三种

当该类没有默认构造函数,就意味着自己手写的构造函数不是全缺省参数,需要传参,所以如果没有在初始化列表初始化,那么编译器就会不知道如何初始化该类的成员变量,这就是为什么如果该类没有默认构造函数就必须要在初始化列表初始化的原因

class A
{
public:A(int a):_a(a){cout << "A(int a = 0)" <<endl;}
private:int _a;
};class B
{
public:// 初始化列表:对象的成员定义的位置B(int a, int& ref):_ref(ref),_n(1),_x(2),_aobj(10){//_n = 0;//_ref = ref;}private:// 声明A _aobj;	  // 没有默认构造函数// 特征:必须在定义的时候初始化int& _ref;	  // 引用const int _n; // constint _x = 1;  // 这里1是缺省值,缺省值是给初始化列表的
};int main()
{int n = 10;// 对象整体定义B bb1(10, n); //B bb2(11, 2);return 0;
}

那么,我们是否能使用初始化列表完全取代构造函数体内赋值的操作取代了呢?

        答:不能,因为如果我们malloc空间后,返回的是指针,我们是需要进行判空的,该操作在初始化列表中,是很不方便的编写的,此外memset等函数或操作都不能很方便的写在初始化列表中,总有一些操作是初始化列表做不了的

3. 尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使用初始化列表初始化。

        所以,我们在初始化时尽量用初始化列表,当遇到额外的不方便的操作,放在构造函数体内即可 

4. 成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关  

class A
{
public:A(int a):_a1(a),_a2(_a1){}void Print() {cout<<_a1<<" "<<_a2<<endl;}
private:int _a2;int _a1;
}int main() 
{A aa(1);aa.Print();
}A. 输出1 1
B.程序崩溃
C.编译不通过
D.输出1 随机值

        答案为D, 初始化列表中的初始化顺序与成员变量在类内的声明顺序相同,此题中先初始化_a2,再初始化_a1,所以_a2为随机值,_a1为1

        在构造函数体内,赋值顺序与语句有关,不受此影响

        如果我们在类中的变量声明处给成员变量一缺省值,那么该成员变量会在构造函数的初始化列表中被初始化为缺省值(缺省值已经给出,在初始化列表处必然会被初始化为缺省值,这是我们不能改变的),但是当我们实例化对象的时候不想用这一缺省值时,我们可以在构造函数内进行赋值修改这一成员变量的值 

2.2 隐式类型转换式构造

我们来看一个隐式类型转换的构造

class A
{
public:A(int a):_a(a){} 
private:int _a;
};int main()
{A aa1(1);A aa2 = 2;//int i = 10;//double d = i;return 0;}

        ·这里的 A aa2 = 2隐式类型转换,将2整形转换成自定义类型A,类似于int型变量i赋值给double型变量d,这其中会发生隐式类型转换,产生一个临时变量存储double型i,再将double型i赋值给d;同理,A aa2 = 2意思为:2构造一个A的临时变量,临时变量再拷贝构造aa2,但是编译器不能“容忍”效率低下的类型转换(一般对于连续的构造、拷贝构造,编译器都会优化),编译器优化为将 2 作为参数直接构造aa2

        再来看另一种情况:

A& aa3 = 2;

 错误:无法从“int” 转换为A&由于这里不是连续的构造和拷贝构造,它仅仅是构造,因为引用&不是拷贝,所以这里还是需要临时变量,又因为2在构造临时对象时,该临时对象具有常性,直接用引用类型接收,权限放大,出现错误,所以aa3要用const修饰

const A& aa3 = 2;
2.3 explicit关键字

有没有办法能不允许其隐式类型转换呢?这里有关键字explicit

class A
{
public:explicit A(int a):_a(a){cout << "A(int a)" << endl;} 
private:int _a;
};

使用explicit关键字加在构造函数前,主函数内的 A aa2 = 2 将不再允许临时类型转换,程序报错 

二、static静态成员

1. 概念

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

面试题:实现一个类,计算程序中创建出了多少个类对象

        我们正常想法可能是定义全局变量记录构造函数、拷贝构造函数调用次数,当调用析构函数时,全局变量值减一,最后输出大小

int _scount = 0;class A
{
public:A() { ++_scount; }A(const A& t) { ++_scount; }~A() { --_scount; }/*static int GetACount() { return _scount; }*/
private:static int _scount;
};A aa0;void Func()
{static A aa2;cout << __LINE__ << ":" << _scount << endl;// 全局变量的劣势:任何地方都可以随意改变_scount++;
}int main()
{cout <<__LINE__<<":"<< _scount << endl;  // 1A aa1;Func();  // 3Func();  // 3return 0;
}

        __LINE__宏定义,输出当前代码执行到的行数 ,我们在22行的Func函数中添加了_scount++,最终的输出变为了4(由于静态变量存储在静态区,aa2第一次定义后变量没有销毁,在第二次调用该行代码时,并没有重复定义,所以正确答案_scount应为3,但是在认为修改了一小步后,结果出错),这就直接体现了全局变量的劣势所在:任何地方都可以随意改变。

        所以我们将_scount封装在类中,使用static修饰,成为静态成员变量,所谓静态成员变量与成员变量的区别在于成员变量属于每一个类对象,储存在对象里面,而静态成员变量属于类,属于类的每个对象共享,存储在静态区,所以不能在初始化列表中对静态成员变量进行初始化,因为它不属于某一对象的成员变量。

        对于静态成员变量,在类外的全局位置进行初始化,虽然它是私有的,但是在全局初始化静态变量时,这一操作是被允许的。

        且静态成员变量不能给缺省值,缺省值是给初始化列表的

int A::_scount = 0;

        但是对于在类外使用 A::_scount 进行访问时就不允许了,会受到private的保护,如果是public那么就可以在类外使用 A::_scount 进行访问。

        那么如何在类外访问private保护的静态成员变量呢?

  • 构造一个成员函数 GetACount( )  ,将静态成员变量作为返回值

        可是如果没有实例化对象,那么就不能使用成员函数进行访问,这时我们可以将该成员函数使用static修饰,使其成为静态成员函数,就可以在没有对象的情况下在类外进行访问静态成员函数,这是因为静态成员函数没有this指针,在类外指定了类域和访问限定符后就可以访问该函数,并且不能在静态成员函数内访问成员变量,因为想要访问成员变量都要用到 this 指针。

        由于静态成员函数没有this指针,所以在其函数内不能调用其余成员函数,因为没有函数调用地址,但是其余的成员函数可以调用静态成员函数,因为有this指针且都在类中,相当于在一个“大家庭”,所以普通成员函数可以访问静态成员函数。

        在类外能访问到静态成员变量是因为它属于类,不属于某一单独的对象。

cout << A::GetACount() << endl;

    

例题:

思路:使用n次构造函数并结合静态成员变量,即可巧妙的解决该问题

class Sum
{
public:Sum(){_ret += _i;_i++;}static int Get_ret(){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) {//这里涉及到了变长数组的概念,C++11允许变长数组Sum a[n];return Sum::Get_ret();}
};

例题二:

        设计一个类,使得在类外只能在栈或堆上创建对象

        在类外,我们可以实例化栈、静态区、堆上的对象。但是此时我们加上了限制,如只能在栈上实例化对象,那么为了避免在其他内存空间创建对象,我们直接用private修饰类的构造函数相当于“封死”外部任何形式的实例化,使得只能在类内实例化之后再返回实例化对象但此时又出现一个问题,普通成员函数参数列表内默认有一个类的对象,this指向该对象,可是我们还没有创建对象,我们的目的就是创建对象,这就矛盾了,成为了“先有鸡还是先有蛋”的问题

        此时,静态成员函数就登场了,由于静态成员函数没有this指针,就代表了它不需要参数,这就完美解决了上面的问题

class A
{
public:static A GetStackObj(){A aa;return aa;}static A* GetHeapObj(){return new A;}
private:A(){}private:int _a1 = 1;int _a2 = 2;
};int main()
{//static A aa1;   //  静态区//A aa2;          //  栈 //A* ptr = new A; //  堆A::GetStackObj();A::GetHeapObj();return 0;
}

2. 特性

1. 静态成员 为所有 类对象所共享,不属于某个具体的实例
2. 静态成员变量 必须在类外定义 定义时不添加 static关键字
3. 类静态成员即可用类名  ::  静态成员或者对象 .静态成员来访问
4. 静态成员函数 没有隐藏的this 指针,不能访问任何非静态成员
5. 静态成员和类的普通成员一样,也有 publicprotectedprivate3种访问级别,也可以具有返回值

三、友元

友元分为:友元函数 友元类
友元提供了一种突破封装的方式,有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多用。

1. 友元函数

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

  1. 友元函数可访问类的私有和保护成员,但不是类的成员函数
  2. 友元函数不能用const修饰
  3. 友元函数可以在类定义的任何地方声明,不受类访问限定符限制
  4. 一个函数可以是多个类的友元函数
  5. 友元函数的调用与普通函数的调用和原理相同

2.友元类

友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。

  1. 友元关系是单向的,不具有交换性。
  2. 友元关系不能传递
如果 B A 的友元, C B 的友元,则不能说明 C是 A 的友元。

四、内部类

1. 概念

        如果一个类定义在另一个类的内部,这个内部类就叫做内部类。注意此时这个内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去调用内部类外部类对内部类没有任何优越的访问权限。
注意:内部类就是外部类的友元类。注意友元类的定义,内部类可以通过外部类的对象参数来访问外部类中的所有成员但是外部类不是内部类的友元 内部类天生是外部类的友元
特性:
1. 内部类定义在外部类的 publicprotectedprivate都是可以的。
2. 注意内部类可以直接访问外部类中的 static、枚举成员,不需要外部类的对象/ 类名
3. sizeof( 外部类)= 外部类,和内部类没有任何关系
class A
{
private:static int k;int h;
public:class B{public:void foo(const A& a){cout << k << endl;//OKcout << a.h << endl;//OK}};
};int A::k = 1;int main()
{A::B b;b.foo(A());return 0;
}

内部类受外部类的访问限定符限制公有内部类、私有内部类

五、匿名对象

有名对象——生命周期在当前函数的局部域

class A
{
public:A(int a = 0):_a(a){cout << "A(int a)" << endl;}~A(){cout << "~A()" << endl;}
private:int _a;
};int main()
{//有名对象A aa(1);//匿名对象A(2);return 0;
}

匿名对象即用即销毁,生命周期在当前行。

        一般来说,我们要访问类的成员函数,都要先有对象,再利用对象访问成员,(用类型是不能访问成员的!) 当我们没有实例化对象时,我们就可以利用匿名对象来访问,例

class Solution {
public:int Sum_Solution(int n) {cout << "Sum_Solution" << endl;//...return n;}
};int main()
{Solution s1;s1.Sum_Solution(10);Solution().Sum_Solution(20);return 0;
}

         若构造函数内需要传参,那么在匿名对象()内加上参数即可,匿名对象与有名对象的区别就在于有没有名字

1. const引用匿名对象

        一般情况,匿名对象生命周期在当前行。因为匿名对象具有常性,我们用引用接收是错误的,权限放大,我们需要用const引用接收,这时我们再运行程序,会发现该临时对象生命周期改变,在局部域。这是为了防止野引用

//A& ra = A(1);    //匿名对象具有常性//const引用延长了匿名对象的生命周期
const A& ra = A(1);

2. 匿名对象的隐式类型转换 

void push_back(const string& s)
{cout << "push_back:" << s << endl;
}int main()
{//1string str("11111");push_back(str);//2push_back(string("222222"));//3push_back("222222");return 0;
}

2是匿名对象,3是临时对象,两者相同都具有常性,所以形参都要有const修饰

void Func1(A aa)
{}A Func5()
{A aa;return aa;
}int main()
{A ra1 = Func5(); // 拷贝构造+拷贝构造 ->优化为拷贝构造cout << "==============" << endl;A ra2;ra2 = Func5();//A aa1;//Func1(aa1); // 不会优化//Func1(A(1)); // 构造+拷贝构造 ->优化为构造//Func1(1);    // 构造+拷贝构造 ->优化为构造//A aa2 = 1;  // 构造+拷贝构造 ->优化为构造return 0;
}

        对于ra1,由两次的连续的拷贝构造(返回临时遍历),编译器自动优化为一次拷贝构造

        对于ra2,由于是两行代码,构造不连续,编译器不会优化,会成为构造、构造、拷贝构造、析构、赋值运算符重载、析构 

        所以,对于能一行写完的对象之间的操作,尽量不写成两行,这样不仅繁琐,还会干扰编译器


总结

        本节补充了类和对象的剩余细节,至此,我们较为完善的学习了C++类和对象。

        类和对象的细节繁多,较为难学,根据文章内容的理解再查阅C++对应书籍,就会更加明了。

最后,如果小帅的本文哪里有错误,还请大家指出,请在评论区留言(ps:抱大佬的腿),新手创作,实属不易,如果满意,还请给个免费的赞,三连也不是不可以(流口水幻想)嘿!那我们下期再见喽,拜拜!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/149386.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

postgresql实现单主单从

实现步骤 1.主库创建一个有复制权限的用户 CREATE ROLE 用户名login # 有登录权限的角色即是用户replication #复制权限 encrypted password 密码;2.主库配置开放从库外部访问权限 修改 pg_hba.conf 文件 &#xff08;相当于开放防火墙&#xff09; # 类型 数据库 …

Swing程序设计(5)绝对布局,流布局

文章目录 前言一、布局管理器二、介绍 1.绝对布局2.流布局总结 前言 Swing窗体中&#xff0c;每一个组件都有大小和具体的位置。而在容器中摆放各种组件时&#xff0c;很难判断其组件的具体位置和大小。即一个完整的界面中&#xff0c;往往有多个组件&#xff0c;那么如何将这…

Unity如何实现TreeView

前言 最近有一个需求,需要实现一个TreeView的试图显示,开始我一直觉得这么通用的结构,肯定有现成的UI组件或者插件可以使用,结果,找了好久,都没有找到合适的插件,有两个效果差强人意。 最后在回家的路上突然灵光一闪,想到了一种简单的实现方式,什么插件都不用,仅使用…

基于虚拟同步发电机控制的双机并联Simulink仿真模型

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

1024 科学计数法

一.问题&#xff1a; 科学计数法是科学家用来表示很大或很小的数字的一种方便的方法&#xff0c;其满足正则表达式 [-][1-9].[0-9]E[-][0-9]&#xff0c;即数字的整数部分只有 1 位&#xff0c;小数部分至少有 1 位&#xff0c;该数字及其指数部分的正负号即使对正数也必定明确…

kafka集群工作机制

一、kafka在zookeeper上的元数据解释 kafka中的broker要选举Controller角色来管理整个kafka集群中的分区和副本状态。一个Topic下多个partition要选举Leader角色和客户端进行交互数据 Zookeeper客户端工具&#xff1a; prettyZoo。 下载地址&#xff1a;https://github.com/vr…

2023年R1快开门式压力容器操作证模拟考试题库及R1快开门式压力容器操作理论考试试题

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2023年R1快开门式压力容器操作证模拟考试题库及R1快开门式压力容器操作理论考试试题是由安全生产模拟考试一点通提供&#xff0c;R1快开门式压力容器操作证模拟考试题库是根据R1快开门式压力容器操作最新版教材&#…

润滑油泵控制(博途SCL源代码)

有关博途PLC定时器的各种使用方法请参考下面文章链接: 博途PLC IEC定时器编程应用(SCL语言)_博图 定时器-CSDN博客博途PLC定时器支持数据类型TIME 类型 ,写法支持T#2M10S 、T#10S等,时基是MS所以如果设置1M用 DINT数据类型就是60000,大部分HMI上数据类型很多不支持IEC的…

buuctf-[GXYCTF2019]禁止套娃 git泄露,无参数rce

用dirsearch扫一下&#xff0c;看到flag.php 访问一下没啥东西&#xff0c;使用githack python2 GitHack.py http://8996e81f-a75c-4180-b0ad-226d97ba61b2.node4.buuoj.cn/.git/查看index.php <?php include "flag.php"; echo "flag在哪里呢&#xff1f;…

【iptables 实战】9 docker网络原理分析

在开始本章阅读之前&#xff0c;需要提前了解以下的知识 阅读本节需要一些docker的基础知识&#xff0c;最好是在linux上安装好docker环境。提前掌握iptables的基础知识&#xff0c;前文参考【iptables 实战】 一、docker网络模型 docker网络模型如下图所示 说明&#xff1…

【算法|动态规划No.9】leetcodeLCR 091. 粉刷房子

个人主页&#xff1a;兜里有颗棉花糖 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 兜里有颗棉花糖 原创 收录于专栏【手撕算法系列专栏】【LeetCode】 &#x1f354;本专栏旨在提高自己算法能力的同时&#xff0c;记录一下自己的学习过程&#xff0c;希望…

Bee2.1.8支持Spring Boot 3.0.11,active命令行选择多环境,多表查改增删(bee-spring-boot发布,更新maven)

天下大势&#xff0c;分久必合&#xff01; Hibernate/MyBatis plus Sharding JDBC Jpa Spring data GraphQL App ORM (Android, 鸿蒙) Bee Spring Cloud 微服务使用数据库更方便&#xff1a;Bee Spring Boot; 轻松支持多数据源&#xff0c;Sharding, Mongodb. 要整合一堆的…

牛客网国庆赛day3

B&#xff1a; 给定一个由小写字母组成的字符串S。你要逐个执行Q个操作。每个操作可以是以下两种类型之一&#xff1a; 修改&#xff1a;给定一个整数x。根据x的值修改字符串S。如果x是正数&#xff0c;则将S中最左边的x个字母移到S的右侧&#xff1b;如果x是负数&#xff0c;…

最短路径专题6 最短路径-多路径

题目&#xff1a; 样例&#xff1a; 输入 4 5 0 2 0 1 2 0 2 5 0 3 1 1 2 1 3 2 2 输出 2 0->1->2 0->3->2 思路&#xff1a; 根据题意&#xff0c;最短路模板还是少不了的&#xff0c; 我们要添加的是&#xff0c; 记录各个结点有多少个上一个结点走动得来的…

JS-Dom转为图片,并放入pdf中进行下载

1、将dom转换为图片 这里我们使用html2canvas工具插件先将dom转为canvas元素然后canvas拥有一个方法可以将绘制出来的图形转为url然后下载即可注意&#xff1a;如果元素使用了渐变背景并透明的话&#xff0c;生成的图片可能会有点问题。我下面这个案例使用了渐变背景实现元素对…

【进程管理】初识进程

一.何为进程 教材一般会给出这样的答案: 运行起来的程序 或者 内存中的程序 这样说太抽象了&#xff0c;那我问程序和进程有什么区别呢&#xff1f;诶&#xff1f;这我知道&#xff0c;书上说&#xff0c;动态的叫进程&#xff0c;静态的叫程序。那么静态和动态又是什么意思…

typescript: Builder Pattern

/*** file: CarBuilderts.ts* TypeScript 实体类 Model* Builder Pattern* 生成器是一种创建型设计模式&#xff0c; 使你能够分步骤创建复杂对象。* https://stackoverflow.com/questions/12827266/get-and-set-in-typescript* https://github.com/Microsoft/TypeScript/wiki/…

想要精通算法和SQL的成长之路 - 验证二叉树

想要精通算法和SQL的成长之路 - 验证二叉树 前言一. 验证二叉树1.1 并查集1.2 入度以及边数检查 前言 想要精通算法和SQL的成长之路 - 系列导航 并查集的运用 一. 验证二叉树 原题链接 思路如下&#xff1a; 对于一颗二叉树&#xff0c;我们需要做哪些校验&#xff1f; 首先…

(二)正点原子STM32MP135移植——TF-A移植

目录 一、TF-A概述 二、编译官方代码 2.1 解压源码 2.2 打补丁 2.3 编译准备 &#xff08;1&#xff09;修改Makfile.sdk &#xff08;2&#xff09;设置环境变量 &#xff08;3&#xff09;编译 三、移植 3.1 复制官方文件 3.2 修改电源 3.3 修改TF卡和emmc 3.4 添…