<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.mzph.cn/news/93696.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

获取网卡上的IP、网关及DNS信息,获取最佳路由,遍历路由表中的条目(附源码)

VC常用功能开发汇总&#xff08;专栏文章列表&#xff0c;欢迎订阅&#xff0c;持续更新...&#xff09;https://blog.csdn.net/chenlycly/article/details/124272585C软件异常排查从入门到精通系列教程&#xff08;专栏文章列表&#xff0c;欢迎订阅&#xff0c;持续更新...&a…

Linux进程控制

文章目录 前言一、进程创建1、fork函数2、写时拷贝3、子进程从哪里开始执行父进程代码 二、进程终止1、进程终止时&#xff0c;操作系统做了什么2、进程终止的常见方式2.1 main函数退出码 3、在代码中终止进程3.1 使用return语句终止进程3.2 使用exit函数终止进程3.3 使用_exit…

c#设计模式-结构型模式 之 组合模式

&#x1f680;简介 组合模式又名部分整体模式&#xff0c;是一种 结构型设计模式 &#xff0c;是用于把一组相似的对象当作一个 单一的对象 。组合模式 依据树形结构来组合对象 &#xff0c;用来表示部分以及整体层&#xff0c;它可以让你将对象组合成树形结构&#xff0c;并且…

leetCode 45.跳跃游戏 II 贪心算法

45. 跳跃游戏 II - 力扣&#xff08;LeetCode&#xff09; 给定一个长度为 n 的 0 索引整数数组 nums。初始位置为 nums[0]。 每个元素 nums[i] 表示从索引 i 向前跳转的最大长度。换句话说&#xff0c;如果你在 nums[i] 处&#xff0c;你可以跳转到任意 nums[i j] 处: 0 &…

大语言模型之十四-PEFT的LoRA

在《大语言模型之七- Llama-2单GPU微调SFT》和《大语言模型之十三 LLama2中文推理》中我们都提到了LoRA&#xff08;低秩分解&#xff09;方法&#xff0c;之所以用低秩分解进行参数的优化的原因是为了减少计算资源。 我们以《大语言模型之四-LlaMA-2从模型到应用》一文中的图…

已解决 Bug——IndexError: index 3 is out of bounds for axis 0 with size 3问题

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页: &#x1f405;&#x1f43e;猫头虎的博客&#x1f390;《面试题大全专栏》 &#x1f995; 文章图文并茂&#x1f996…

格点数据可视化(美国站点的日降雨数据)

获取美国站点的日降雨量的格点数据&#xff0c;并且可视化 导入模块 from datetime import datetime, timedelta from urllib.request import urlopenimport cartopy.crs as ccrs import cartopy.feature as cfeature import matplotlib.colors as mcolors import matplotli…

JAVA学习(2)-全网最详细~

&#x1f308;write in front&#x1f308; &#x1f9f8;大家好&#xff0c;我是Aileen&#x1f9f8;.希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流. &#x1f194;本文由Aileen_0v0&#x1f9f8; 原创 CSDN首发&#x1f412; 如…

选择排序算法:简单但有效的排序方法

在计算机科学中&#xff0c;排序算法是基础且重要的主题之一。选择排序&#xff08;Selection Sort&#xff09;是其中一个简单但非常有用的排序算法。本文将详细介绍选择排序的原理和步骤&#xff0c;并提供Java语言的实现示例。 选择排序的原理 选择排序的核心思想是不断地从…

CCF CSP认证 历年题目自练 Day20

题目一 试题编号&#xff1a; 201903-1 试题名称&#xff1a; 小中大 时间限制&#xff1a; 1.0s 内存限制&#xff1a; 512.0MB 问题描述&#xff1a; 题目分析&#xff08;个人理解&#xff09; 常规题目&#xff0c;先看输入&#xff0c;第一行输入n表示有多少数字&am…

axb_2019_brop64

axb_2019_brop64 Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000)64位&#xff0c;只开了NX __int64 repeater() {size_t v1; // raxchar s[208]; // [rsp0h] [rbp-D0h] BYREFprintf("…

小谈设计模式(15)—观察者模式

小谈设计模式&#xff08;15&#xff09;—观察者模式 专栏介绍专栏地址专栏介绍 观察者模式核心思想主要角色Subject&#xff08;被观察者&#xff09;ConcreteSubject&#xff08;具体被观察者&#xff09;Observer&#xff08;观察者&#xff09;ConcreteObserver&#xff0…

HTML的学习 Day02(列表、表格、表单)

文章目录 一、列表列表主要分为以下三种类型&#xff1a;1. 无序列表&#xff08;Unordered List&#xff09;&#xff1a;2. 有序列表&#xff08;Ordered List&#xff09;&#xff1a;将有序列表的数字改为字母或自定义内容li.../li 列表项标签中value属性&#xff0c;制定列…

【简单的留言墙】HTML+CSS+JavaScript

目标&#xff1a;做一个简单的留言墙 1.首先我们用HTML的一些标签&#xff0c;初步构造区域 样式。 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>留言墙</title><style>/* ...... */ …

FFmpeg 命令:从入门到精通 | ffmpeg filter(过滤器 / 滤镜)

FFmpeg 命令&#xff1a;从入门到精通 | ffmpeg filter&#xff08;过滤器 / 滤镜&#xff09; FFmpeg 命令&#xff1a;从入门到精通 | ffmpeg filter&#xff08;过滤器 / 滤镜&#xff09;ffmpeg fliter 基本内置变量视频裁剪文字水印图片水印画中画视频多宫格处理 FFmpeg 命…

使用 cURL 发送 HTTP 请求: 深入探讨与示例

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…

美丽的图论

**美丽的图论 ** Prf &#x1f609; 对于 n 个顶点上的树的数量 n^(n-2)&#xff0c;这是凯莱公式&#xff0c;用于计算 n 个顶点上的树的数量&#xff0c;被放置在一个由 4 个标记顶点组成的圆圈中。 使用 Figma 制作 在图论中&#xff0c;树只是一个没有环的图。 树在离散…

【MATLAB-基于直方图优化的图像去雾技术】

【MATLAB-基于直方图优化的图像去雾技术】 1 直方图均衡2 程序实现3 局部直方图处理 1 直方图均衡 直方图是图像的一种统计表达形式。对于一幅灰度图像来说&#xff0c;其灰度统计直方图可以反映该图像中不同灰度级出现的统计情况。一般而言&#xff0c;图像的视觉效果和其直方…

javaWeb学生信息管理

一、引言 学生信息管理系统是基于Java Web技术开发的一个全栈应用&#xff0c;用于管理学生的基本信息。本系统采用Eclipse作为开发工具&#xff0c;Navicat用于MySQL数据库管理&#xff0c;运行在JDK1.8、Tomcat9.0、MySQL8.0环境下。前端采用JavaScript、jQuery、Bootstrap4…

五款可替代163邮箱的电子邮件服务

在众多邮箱品牌中&#xff0c;163邮箱作为中国最早的邮箱服务提供商之一&#xff0c;其出海之路并不顺利。本文将探讨163邮箱出海的劣势&#xff0c;并介绍一些替代品&#xff0c;以帮助用户更好地选择适合自己的邮箱服务。 “163邮箱的替代品有哪些&#xff1f;外贸行业适合选…