【C++初阶】类与对象(三)

目录

  • 一、再谈构造函数
    • 1.1 初始化列表
      • 1.1.1 初始化列表写法
      • 1.1.2 哪些成员要使用初始化列表
    • 1.2 初始化列表的特点
      • 1.2.1 队列类问题解决
      • 1.2.2 声明顺序是初始化列表的顺序
    • 1.3 explicit关键字
      • 1.3.1 explicit关键字的作用
  • 二、static成员
    • 2.1 类的静态成员概念
    • 2.2 类里创建了多少个对象问题
  • 三、友元
    • 3.1 概念
    • 3.2 友元函数
    • 3.3 友元类
  • 四、内部类
  • 五、拷贝对象时的一些编译器优化

一、再谈构造函数

1.1 初始化列表

构造函数之前我们已经学过大部分内容,但是并没有学全,还有一个很重要的东西——初始化列表

1.1.1 初始化列表写法

初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式。

	Date(int year, int month, int day):_year(year),_month(month),_day(day){}//Date d1(2023, 11, 9);

运行结果:
在这里插入图片描述
括号里也可以是数值(没有传参的情况):

	Date():_year(2023), _month(11), _day(9){}

补充:声明给缺省值其实是给初始化列表的,但是在没显示写构造函数的情况;如果有写构造函数的话用构造函数中定义的值,构造函数没有参数或者具体数值就是随机值。(VS下随机值是0)

1.1.2 哪些成员要使用初始化列表

先总结下,再逐一分析

一定要初始化列表的成员有:
1.引用成员变量
2.const成员变量
3.没有默认构造的自定义类型成员变量

public:Date(int year, int month, int day):_year(year), _month(month), _day(day),t(year),a(5){}private:int _year;int _month;int _day;int& t;//引用成员变量const int a;//const成员变量
};

运行结果:
在这里插入图片描述

1️⃣使用引用有一个规定,那就是必须初始化。而在以上没有初始化是因为这些都是声明,接下来要定义这个引用成员变量,那么定义的地方在哪?就在初始化列表。

2️⃣const成员变量与引用也是一样的,在私有的区域里只是声明,要定义的话必须在初始化列表。

3️⃣上篇文章使用队列类的时候我们没有写队列类的构造函数,内置类型声明有缺省值使用缺省值,没有是随机值;自定义类型成员变量让编译器自动调用它的默认构造。

复习下什么是默认构造:
1.我们不显示写构造函数编译器默认自动生成的
2.没有传参的
3.全缺省的

如果我们要自己控制参数为多少,也就是说要传参给自定义类型的构造函数(或者自定义类型的构造函数不是默认构造,就要我们自己传参,否则我们既没有显示队列类的构造函数,也没有把它的自定义类型的构造为默认构造,结果就为随机值),就必须显示写构造函数初始化,构造函数要有初始化列表

总结一下:
引用成员变量和const成员变量必须要在定义的地方初始化,这个地方在初始化列表;自定义类型成员变量不写这个类的构造函数,自动调用这个自定义类型成员的默认构造;写这个类的构造函数,下面接着分析~~

1.2 初始化列表的特点

1.2.1 队列类问题解决

之前我们不写队列类的构造函数,这次就要写,构造函数里初始化自定义类型的成员变量,要用初始化列表解决。

以下代码:

class Stack
{
public:Stack(int capacity = 3){cout << "Stack(int capacity = 3)" << endl;_a = (int*)malloc(sizeof(int) * capacity);if (_a == NULL){perror("malloc fail");exit(-1);}_top = 0;_capacity = capacity;}~Stack(){cout << "~Stack()" << endl;free(_a);_a = NULL;_top = 0;_capacity = 0;}private:int* _a;int _top;int _capacity;
};
class Queue
{
public:
//   队列类的构造函数Queue():_st1(),_st2(),_size(){}
private:Stack _st1;Stack _st2;int _size;
};
int main()
{Queue q1;return 0;
}

参数是谁,有几种情况:
1️⃣只有构造函数,没有参数、括号后没有具体值
通过调试查看允许结果:
在这里插入图片描述
我们发现是0、3、0、3、0,所以在没有传任何值的情况下,初始化列表里对于内置类型是随机值(VS下变成0),自定义类型调用它的默认构造

2️⃣没有参数、括号后有具体值

	Queue():_st1(10), _st2(20), _size(30){}

调试结果:
在这里插入图片描述

3️⃣有参数为全缺省、括号后有具体值

	Queue(Stack st1 = 44, Stack st2 = 66, int size = 88):_st1(1), _st2(2), _size(3){}

调试结果:
在这里插入图片描述
4️⃣有参数为全缺省、括号后为参数

	Queue(Stack st1 = 44, Stack st2 = 66, int size = 88):_st1(st1), _st2(st2), _size(size){}

调试结果:
在这里插入图片描述

5️⃣自己传参

	Queue(Stack st1 = 44, Stack st2 = 66, int size = 88):_st1(st1), _st2(st2), _size(size){}///Queue q1(11, 77, 99);

调试结果:
在这里插入图片描述
6️⃣没有构造函数,声明给缺省值

private:Stack _st1 = 7;Stack _st2 = 8;int _size = 9;
};

调试结果:
在这里插入图片描述

总结一下:
1.当自定义类型成员的构造函数不是默认构造,有以下
自己传参5️⃣;有参数为全缺省、括号后为参数4️⃣;有参数为全缺省、括号后有具体值3️⃣;没有参数、括号后有具体值2️⃣;声明给缺省值6️⃣。总之就是有给自定义类型成员的构造函数传参
有具体值就用具体值,没有具体值自己传参优先,其次缺省值
为了方便控制参数,所以使用5️⃣较好。
2.当自定义类型成员的构造函数是默认构造:
只有构造函数、没有参数、没有具体值1️⃣
还有第七种7️⃣不写构造函数(上篇文章的写法)
这两种其实没有区别,所以干脆不写构造函数。

1.2.2 声明顺序是初始化列表的顺序

直接用例子展示:

class Date
{
public:Date(int year, int month, int day):_year(year), _month(month), _day(day){}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}
private:int _day;//day放在最前面int _year;int _month;
};
int main()
{Date d1(2023, 11, 9);d1.Print();return 0;
}

调试结果:
在这里插入图片描述
所以天最先被初始化,其次是年和月。

1.3 explicit关键字

1.3.1 explicit关键字的作用

对于只有内置类型的单参数的构造函数,具有类型转换的功能
看以下代码:

class A
{
public:A(int a){_a = a;}void Print(){cout << _a << endl;}
private:int _a;
};
int main()
{A aa(1);aa = 3;aa.Print();return 0;
}

aa是自定义类型,3是整型,运行结果:
在这里插入图片描述
如果不想转换发生,构造函数前加explicit,此时编译器会有报错提示。

补充:加explicit关键字可以阻止隐式类型转换,但不能阻止强转。

没有使用explicit修饰,多参数且是半缺省也支持。

class Date
{
public:Date(int year, int month = 2, int day = 3){_year = year;_month = month;_day = day;}
private:int _year;int _month;int _day;
};
int main()
{Date d1 = (2023, 11, 10);return 0;
}

调试结果:
在这里插入图片描述
注意:小括号是逗号表达式,只算最后一个数,其他的是缺省值。

修改一下,把小括号变成花括号,并且不要缺省值:

Date d1 = { 2023, 11, 10 };

调试结果:
在这里插入图片描述

二、static成员

2.1 类的静态成员概念

声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用static修饰的成员函数,称之为静态成员函数。

2.2 类里创建了多少个对象问题

定义一个变量count用来计数,每创建(构造)一个对象,或者拷贝构造一个对象,count++。我们先用全局变量count来计数,看会发生什么:
1️⃣

int count = 0;
class A
{
public:A() { ++count; }A(const A& t) { ++count; }~A() {  }
private:};A Func()
{A aa;return aa;
}
int main()
{A aa;Func();cout << count << endl;return 0;
}

在这里插入图片描述
编译器报错了,提示count为不明确的符号,说明命名冲突了。

命名冲突的解决办法——命名空间
2️⃣

namespace yss
{int count = 0;
}
class A
{
public:A() { ++yss::count; }A(const A& t) { ++yss::count; }~A() {  }private:};A Func()
{A aa;return aa;
}
int main()
{A aa;Func();cout << yss::count << endl;return 0;
}

运行结果:
在这里插入图片描述
这个结果确实是我们要的答案,但是这里却有一个问题:如果再多一个类,使用全局变量就不能把两个类区分开来,导致计数完一个类再去计数另一个类count的值没有更新,也就是说两个类的对象是合并在一起的。

我们换一种方式,定义类的成员变量
3️⃣

class A
{
public:A() { ++count; }A(const A& t) { ++count; }~A() {  }//private:int count = 0;
};A Func()
{A aa;return aa;
}
int main()
{A aa;Func();cout << aa.count << endl;return 0;
}

我们暂时把私有的权限注释掉,看看运行结果如何:
在这里插入图片描述
为什么会是1呢?因为每创建一个对象,count+1,但是又创建一个对象,count清零再+1。说明类里一个对象一个count,而我们要的结果是类里的所有对象的个数,可是每个对象有自己的count。

要让类里的对象都是一个count怎么办,使用静态成员变量,static修饰count。
静态成员变量一定要在类外进行初始化
4️⃣

class A
{
public:A() { ++count; }A(const A& t) { ++count; }~A() {  }//private:static int count;
};
int A::count = 0;
A Func()
{A aa;return aa;
}
int main()
{A aa;Func();cout << aa.count << endl;return 0;
}

运行结果:
在这里插入图片描述
结果没问题了,但是,没有私有的权限,类的封装性就不能很好了。

改进:可通过一个成员函数来返回count的值
5️⃣

class A
{
public:A() { ++count; }A(const A& t) { ++count; }~A() {  }int Getcount(){return count;}
private:static int count;
};
int A::count = 0;
A Func()
{A aa;return aa;
}
int main()
{A aa;Func();cout << aa.Getcount() << endl;return 0;
}

运行结果:
在这里插入图片描述
但是如果我们想通过调用Func函数来计数对象创建了几个,main函数里实例化的对象不算该怎么办?

假设调用两次Func函数,先别注释对象的实例化,得到的结果应该为5

int main()
{A aa;Func();Func();cout << aa.Getcount() << endl;return 0;
}

在这里插入图片描述
然后把对象注释掉,发现编译器报错:未定义标识符aa。
在这里插入图片描述

可以使用静态成员函数来解决
6️⃣

	static int Getcount(){return count;}

注意:要通过类名::静态成员来访问

运行结果:
在这里插入图片描述

总结:
静态成员变量和静态成员函数与全局变量和全局函数很像,只是受作用域限定符和点成员操作符限制
补充:
静态成员函数不可以调用非静态成员函数,因为静态成员函数没有this指针; 非静态成员函数可以调用类的静态成员函数,因为它们属于同一个类。

三、友元

3.1 概念

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

友元分为:友元函数和友元类

3.2 友元函数

当一个函数不是类的成员函数,但又要能访问到类里的成员变量,必须使用友元函数

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

class Date
{
public:Date(int year, int month, int day){_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(2023, 11, 10);cout << d1;return 0;
}

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

3.3 友元类

创建一个时间类,声明日期类为时间类的友元类,则在日期类中就直接访问Time类中的私有成员变量

class Time
{// 友元类friend class Date; 
public:Time(int hour = 2020, int minute = 6, int second = 5): _hour(hour), _minute(minute), _second(second){}private:int _hour;int _minute;int _second;
};
class Date
{
public:Date(int year = 2023, int month = 11, int day = 10): _year(year), _month(month), _day(day){}void SetTimeOfDate(int hour, int minute, int second){// 直接访问时间类私有的成员变量_t._hour = hour;_t._minute = minute;_t._second = second;Print();}void Print(){cout << _t._hour << "-" << _t._minute << "-" << _t._second << endl;}
private:int _year;int _month;int _day;Time _t;
};
int main()
{Date d1;d1.Print();return 0;
}

在这里插入图片描述

特点:
友元关系是单向的,不具有交换性
在Time类中声明Date类为其友元类,那么可以在Date类中直接访问Time类的私有成员变量,但想在Time类中访问Date类中私有的成员变量则不行。
友元关系不能传递
如果C是B的友元, B是A的友元,则不能说明C时A的友元。
友元关系不能继承

四、内部类

一个类定义在另一个类的内部,这个内部类就叫做内部类。内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类的成员。

内部类就是外部类的友元类。内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的友元。

class A
{
public:class B{public:void Func(){cout << _a << endl;}};private:int _b;
private:static int _a;
};
int A::_a = 10;
int main()
{A::B b1;b1.Func();return 0;
}

在这里插入图片描述

特点:
内部类可以定义在外部类的public、protected、private都是可以的
注意内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名
sizeof(外部类)=外部类,和内部类没有任何关系

五、拷贝对象时的一些编译器优化

在传参和传返回值的过程中,一般编译器会做一些优化,减少对象的拷贝。不同的编译器优化的方式可能会不同。

1️⃣隐式类型,连续构造+拷贝构造->优化为直接构造

class A
{
public:A(int a = 0):_a(a){cout << "A(int a)" << endl;}A(const A& aa):_a(aa._a){cout << "A(const A& aa)" << endl;}
private:int _a;
};
void Func(A aa)
{}
int main()
{Func(1);return 0;
}

在这里插入图片描述
1是整型,发生隐式类型转换通过调用构造函数产生临时变量,临时变量再拷贝给形参调用拷贝构造函数。

2️⃣一个表达式中,连续构造+拷贝构造->优化为一个构造

class A
{
public:A(int a = 0):_a(a){cout << "A(int a)" << endl;}A(const A& aa):_a(aa._a){cout << "A(const A& aa)" << endl;}
private:int _a;
};
void Func(A aa)
{}
int main()
{Func(A(1));return 0;
}

在这里插入图片描述
在一个表达式中,A(1)调用构造函数产生临时变量,临时变量再拷贝给形参调用拷贝构造函数,优化成一次构造。

3️⃣一个表达式中,连续拷贝构造+拷贝构造->优化一个拷贝构造

class A
{
public:A(int a = 0):_a(a){cout << "A(int a)" << endl;}A(const A& aa):_a(aa._a){cout << "A(const A& aa)" << endl;}
private:int _a;
};
A Func()
{A aa;return aa;
}
int main()
{A aa2 = Func();return 0;
}

在这里插入图片描述
实例化aa2调用一次构造,进入Func()函数,创建aa对象调用一次构造,两次构造优化为一次构造;局部变量拷贝给返回值调用拷贝构造,返回值拷贝给aa2再调用拷贝构造,两次拷贝构造优化成一次拷贝构造。

4️⃣一个表达式中,连续拷贝构造+赋值重载->无法优化

class A
{
public:A(int a = 0):_a(a){cout << "A(int a)" << endl;}A(const A& aa):_a(aa._a){cout << "A(const A& aa)" << endl;}
private:int _a;
};
A Func()
{A aa;return aa;
}
int main()
{A aa3;aa3 = Func();return 0;
}

在这里插入图片描述
实例化aa3调用构造,此时aa3已存在,调用Func()的返回值是赋值,Func()函数里创建aa对象调用构造,局部变量拷贝给返回值调用拷贝构造,最后赋值。

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

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

相关文章

List中的迭代器实现【C++】

List中的迭代器实现【C】 一. list的结构二. 迭代器的区别三. 迭代器的实现i. 类的设计ii. 重载iii. !重载iiii. begin()iiiii. end()iiiii. operator* 四.测试五. const迭代器的实现i. 实现5.2 优化实现 一. list的结构 其实按照习惯来说&#xff0c;应该要专门出一篇博客来写…

C语言精华题目锦集1

第一题 test.c文件中包括如下语句&#xff0c;文件中定义的四个变量中&#xff0c;是指针类型的是&#xff08;&#xff09;【多选】 #define INT_PTR int* typedef int* intptr; INT_PRT a,b; int_ptr c,d;A:a  B:b  C:c  D:d #define是宏定义&#xff0c;此时在程序中IN…

微信小程序:仅前端实现对象数组的模糊查询

效果 核心代码 //对数组进行过滤&#xff0c;返回数组中每一想满足name值包括变量query的 let result array.filter(item > { return item.name.includes(query); }); 完整代码 wxml <input type"text" placeholder"请输入名称" placeholder-styl…

ADFS 高可用配置 + NLB配置(Windows网络负载均衡)

ADFS 高可用配置 NLB配置&#xff08;Windows网络负载均衡&#xff09; ADFS安装配置NLB配置节点 TEST-ADFS-01 网络负载平衡配置节点 TEST-ADFS-02 网络负载平衡修改CRM配置 ADFS实现高可用负载均衡有两种&#xff0c;主要是在数据库的选择方式&#xff1a; windows自带的内…

如何安装Node.js? 创建Vue脚手架

1.进入Node.js官网&#xff0c;点击LTS版本进行下载 Node.js (nodejs.org)https://nodejs.org/en 2.然后一直【Next】即可 3.打开【cmd】,输入【node -v】注意node和-v中间的空格 查看已安装的Node.js的版本号&#xff0c;如果可以看到版本号&#xff0c;则安装成功 创建Vue脚手…

世微 升压恒压IC dc-dc转换器 充电器手持设备便携式产品 AP8660

AP8660是一款升压dc-dc转换器&#xff0c;内置MOS调节器&#xff0c;内部补偿&#xff0c;还可以最小6个外部组件&#xff0c;内部的软识启动功能可以降压涌入电流 AP8660 SOT23-6封装&#xff0c;可以为PCB提供节省空间 特点 可调输出&#xff0c;最高达到24W 内部固定PWM频…

leetcode-链表经典题

1.反转单链表 206. 反转链表https://leetcode.cn/problems/reverse-linked-list/这里我们使用创建一个变量cur来遍历原链表&#xff0c;再创建一个新节点newnode&#xff0c;首先使用一个循环来遍历原链表&#xff0c;cur为NULL是循环结束&#xff0c;每次进入循环将cur的下一…

2023数据结构期中测验-2023秋-计算机+未来网络专业

数据结构期中测验 选择题函数题6-1 求链式表的表长6-2 逆序数据建立链表6-3 删除单链表偶数节点6-4 求二叉树高度6-5 先序输出叶结点 为了防止不自觉的朝答案看去&#xff0c;特意用了明黄色字体&#xff0c;如下查看答案&#xff1a; 选择题 2-1 下述程序段的时间复杂度为&am…

Linux 部署Sentinel控制台

Sentinel 是面向分布式、多语言异构化服务架构的流量治理组件&#xff0c;主要以流量为切入点&#xff0c;从流量路由、流量控制、流量整形、熔断降级、系统自适应过载保护、热点流量防护等多个维度来帮助开发者保障微服务的稳定性。 1.版本选择 SpringCloudAlibaba SpringClo…

若依侧边栏添加计数标记效果

2023.11.13今天我学习了如何对若依的侧边栏添加技术标记的效果&#xff0c;如图&#xff1a; 我们需要用到两个页面&#xff1a; 先说子组件实现计数标记效果 1.item.vue <script> export default {name: MenuItem,functional: true,props: {icon: {type: String,defau…

深度学习 python opencv 实现人脸年龄性别识别 计算机竞赛

文章目录 0 前言1 项目课题介绍2 关键技术2.1 卷积神经网络2.2 卷积层2.3 池化层2.4 激活函数&#xff1a;2.5 全连接层 3 使用tensorflow中keras模块实现卷积神经网络4 Keras介绍4.1 Keras深度学习模型4.2 Keras中重要的预定义对象4.3 Keras的网络层构造 5 数据集处理训练5.1 …

【源码复现】图神经网络之PPNP/APPNH

目录 1、论文简介2、论文核心介绍2.1、现有方法局限2.2、PageRank&Personalized PageRank2.3、PPNP&APPNP 3、源码复现3.1、模型总体框架3.2、PPNP3.3、APPNP3.4、MLP(两层) 1、论文简介 论文题目——《PREDICT THEN PROPAGATE: GRAPH NEURAL NETWORKS MEET PERSONALI…

Spring Task定时任务框架

二十四、Spring Task 24.1 介绍 Spring Task 是Spring框架提供的任务调度工具&#xff0c;可以按照约定的时间自动执行某个代码逻辑。 定位&#xff1a;定时任务框架 作用&#xff1a;定时自动执行某段Java代码 为什么要在Java程序中使用Spring Task&#xff1f; 应用场景…

ACM练习——第一天

因为最近要去农大参加他们的算法邀请赛&#xff0c;然后赛制是ACM赛制的&#xff0c;所以我就直接很迷茫。 然后我就找到了牛客的ACM练习题&#xff0c;好好的练习一下ACM写法&#xff0c;而且我还要被迫写C&#xff0c;哭了。 开始钻研 1.从Java过度到C 题目源于牛客网&…

[工业自动化-14]:西门子S7-15xxx编程 - 软件编程 - STEP7 TIA博途是全集成自动化软件TIA portal快速入门

目录 一、TIA博途是全集成自动化软件TIA portal快速入门 1.1 简介 1.2 软件常用界面 1.3 软件安装的电脑硬件要求 1.4 入口 1.5 主界面 二、PLC软件编程包含哪些内容 2.1 概述 2.2 电机运动控制 一、TIA博途是全集成自动化软件TIA portal快速入门 1.1 简介 Siemens …

Java中的7大设计原则

在面向对象的设计过程中&#xff0c;首先需要考虑的是如何同时提高一个软件系统的可维护性和可复用性。这时&#xff0c;遵从面向对象的设计原则&#xff0c;可以在进行设计方案时减少错误设计的产生&#xff0c;从不同的角度提升一个软件结构的设计水平。 1、单一职责 一个类…

用于强化学习的置换不变神经网络

一、介绍 如果强化学习代理提供的输入在训练中未明确定义&#xff0c;则通常表现不佳。一种新方法使 RL 代理能够正常运行&#xff0c;即使受到损坏、不完整或混乱的输入的影响也是如此。 “大脑能够使用来自皮肤的信息&#xff0c;就好像它来自眼睛一样。我们不是用眼睛看&…

重磅发布 OpenAI 推出用户自定义版 ChatGPT

文章目录 重磅发布 OpenAI 推出用户自定义版 ChatGPT个人简介 重磅发布 OpenAI 推出用户自定义版 ChatGPT OpenAI 首届开发者大会 (OpenAI DevDay) 于北京时间 11 月 7 日凌晨 02:00 开始&#xff0c;大会上宣布了一系列平台更新。其中一个重要更新是用户可以创建他们自己的自定…

Spring Cloud学习(七)【Docker 容器】

文章目录 初识 DockerDocker 介绍Docker与虚拟机Docker架构安装 Docker Docker 基本操作镜像相关命令容器相关命令数据卷 Dockerfile 自定义镜像镜像结构Dockerfile DockerComposeDockerCompose介绍安装DockerCompose Docker镜像仓库常见镜像仓库服务私有镜像仓库 初识 Docker …

里氏代换原则

package com.jmj.principles.dmeo2.after;/*** 四边形接口*/ public interface Quadrilateral {double getLength();double getWidth();}package com.jmj.principles.dmeo2.after;/*** 长方形类*/ public class Rectangle implements Quadrilateral{private double length;priv…