类和对象(下篇)

再谈构造函数

构造函数体赋值

在之前的学习中我们知道,在创建一个对象时,我们的编译器就会自动调用构造函数将对象初始化,给对象中各个成员变量一个合适的初始值。
例如:

class Date
{
public:Date(int year, int month, int day){_year = year;_month = month;_day = day;}
private:int _year;int _month;int _day;
};

虽然上述构造函数调用之后,对象中已经有了一个初始值,但是不能将其称为对对象中成员变量的初始化,构造函数体中的语句只能将其称为赋初值,而不能称作初始化。因为初始化只能初始化一次,而构造函数体内可以多次赋值。
所以C++就引入了初始化列表的概念:

初始化列表

初始化列表的形式如下:
以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式。
废话不多说,直接上代码:
成员变量括号后面就放入合适的初始值

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. 类中包含以下成员,必须放在初始化列表位置进行初始化:
    1 引用成员变量
    2 const成员变量
    3 自定义类型成员(且该类没有默认构造函数时)

直接上代码:
可以看到,这段代码中A类中并没有无参或者全缺省的构造函数,所以没有默认构造函数,必须使用列表初始化
而int& ref则是引用变量
_a 的类型时const int
他们都必须使用列表初始化
所以这段代码中的A类和B类都是使用列表初始化

class A
{
public:A(int a):_a(a){}
private:int _a;
};
class B
{
public:B(int a, int ref):_aobj(a), _ref(ref), _n(10){}
private:A _aobj;  // 没有默认构造函数int& _ref;  // 引用const int _n; // const 
};
  1. 尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使用初始化列表初始化。(所以在之后的代码中我们初始化时可以尽量地去使用列表初始化)
  2. 成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关

下面看一段代码:
大家可以来思考一下结果

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();
}

答案是a1的输出值是1,a2的输出值时随机值
因为a2比a1 先声明,所以在列表初始化时先初始化a2,而此时a1也是未知的随机值,所以a2就被初始化为随机值,然后a1初始化为1

在这里插入图片描述

explicit关键字

构造函数不仅可以构造与初始化对象,对于单个参数或者除第一个参数无默认值其余均有默认值的构造函数,还具有类型转换的作用。
例如:
这段代码中我们直接用=赋值初始化,但是他实质是是一个类型转换
其实test函数里面的两句代码效果是一样的,但是第二句的类型转化会产生一个临时变量,调用构造函数初始化,然后在调用拷贝构造函数,但是我们的编译器将他直接优化成了直接调用构造函数

class Date
{
public:Date(int year):_year(year){}Date& operator=(const Date& d){if (this != &d){_year = d._year;_month = d._month;_day = d._day;}return *this;}
private:int _year;int _month;int _day;
};
void Test()
{
//这两句的用法都可,效果是一样的Date d1(2022);d1 = 2023;
}

下面看这段代码:
你就会发现编译错误
这就引出了重点:

用explicit修饰构造函数,将会禁止构造函数的隐式转换

class Date
{
public:explicit Date(int year):_year(year){}explicit Date(int year, int month = 1, int day = 1): _year(year), _month(month), _day(day){}Date& operator=(const Date& d){if (this != &d){_year = d._year;_month = d._month;_day = d._day;}return *this;}
private:int _year;int _month;int _day;
};
void Test()
{Date d1(2022);d1 = 2023;
}

static成员

概念

声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用static修饰的成员函数,称之为静态成员函数
这里要注意:

静态成员变量一定要在类外进行初始化

还有:
静态成员函数和变量不属于某个对象,而是属于整个类

例如:
这种初始化方式就会发生编译错误

class Date
{static int _year=0;
};int main()
{Date d1;
}

应该这样初始化:

class Date
{static int _year;
};
static int _year = 0;
int main()
{Date d1;
}
特性
  1. 静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区
  2. 静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明
  3. 类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问
  4. 静态成员函数没有隐藏的this指针,不能访问任何非静态成员
  5. 静态成员也是类的成员,受public、protected、private 访问限定符的限制

下面请看一个经典的面试题:
实现一个类,计算程序中创建出了多少个类对象。
这里我们就需要来解释一下,我们讲解放入注释

class A
{
public:A() //这是一个构造函数,创建一个对象就会调用一次,所以_scount++{ ++_scount;}A(const A& t) //构造函数的重载{ ++_scount; }~A() //析构函数,清理一个对象_scount就--{ --_scount; }static int GetACount() //静态成员函数{ //只能访问静态成员变量return _scount; //最后返回这个静态成员变量即可}
private:static int _scount; //静态成员变量的声明
};
int A::_scount = 0; //需要访问私有成员变量,所以要用类名,并且在类外初始化
void TestA()
{cout << A::GetACount() << endl;A a1, a2;A a3(a1);cout << A::GetACount() << endl;
}

可以看到,输出结果如下,刚开始没有创建对象输出0
创建a1 a2 a3 后输出3
在这里插入图片描述

友元

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

友元函数

在之前的运算符重载中我们就可以用友元来结局全局函数不可以调用私有成员变量的问题:
友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声明,声明时需要加friend关键字。
例如:

class Date
{friend ostream& operator<<(ostream& _cout, const Date& d);friend istream& operator>>(istream& _cin, Date& d);
public:Date(int year = 1900, int month = 1, int day = 1): _year(year), _month(month), _day(day){}
private:int _year;int _month;int _day;
};
ostream& operator<<(ostream& _cout, const Date& d)
{_cout << d._year << "-" << d._month << "-" << d._day;return _cout;
}
istream& operator>>(istream& _cin, Date& d)
{_cin >> d._year;_cin >> d._month;_cin >> d._day;return _cin;
}
int main()
{Date d;cin >> d;cout << d << endl;return 0;
}

这里还需要注意几个点:

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

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

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;
};

下面请看友元类的特点:

1 友元关系是单向的,不具有交换性。
比如上述Time类和Date类,在Time类中声明Date类为其友元类,
那么可以在Date类中直接
2 访问Time类的私有成员变量,
但想在Time类中访问Date类中私有的成员变量则不行。
友元关系不能传递
3 如果C是B的友元, B是A的友元,则不能说明C时A的友元。
友元关系不能继承,在继承位置再给大家详细介绍。

内部类

概念:如果一个类定义在另一个类的内部,这个内部类就叫做内部类。

内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越的访问权限。

这里需要注意:
内部类就是外部类的友元类,所以内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的友元。

特性:

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

用代码看看就懂了

class A
{
private:static int k;int h;
public:class B // B天生就是A的友元{public:void foo(const A& a){//B是A的友元,所以可以访问A的非公有成员变量cout << k << endl;cout << a.h << endl;}};
};
int A::k = 1;
int main()
{A::B b;b.foo(A());return 0;
}

匿名对象

我们要注意:
匿名对象的生命周期只有一行:
请看代码:

class A
{
public:void print(){cout << _a << endl;}~A(){cout << "~A()" << endl;}
private:int _a;
};int main()
{A().print();
}

输出结果可以看到这一行就直接调用了析构函数清理自己,所以他的生命周期只有一行
在这里插入图片描述

匿名对象的使用有时候可以起到简化代码和节省空间的作用
不做过多的讲解,大家看代码的里面的注释即可

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 aa2(2);// 匿名对象在这样场景下就很好用,当然还有一些其他使用场景,这个我们以后遇到了再说Solution().Sum_Solution(10);return 0;
}

好了,今天的分享到这里就结束了,谢谢大家的支持!

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

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

相关文章

大一C语言作业题目34

7-3 计算职工工资 给定N个职员的信息&#xff0c;包括姓名、基本工资、浮动工资和支出&#xff0c;要求编写程序顺序输出每位职员的姓名和实发工资&#xff08;实发工资基本工资浮动工资-支出&#xff09;。 输入格式&#xff1a; 输入在一行中给出正整数N。随后N行&#xff0c…

2024年,消费品零售企业如何规划大模型和数据技术落地?

导读&#xff1a;品牌商和零售商目前都在做2024年的规划&#xff0c;本次分享基于爱分析过往的研究&#xff0c;带来消费品零售行业2024年宏观趋势和方向&#xff0c;以及如何落地大模型和数据技术。 分享嘉宾&#xff5c;张扬 爱分析联合创始人兼首席分析师 内容来源于爱分析…

2022xctf-final hole

这个题是做到的第一个利用hole和map来制造oob的题目&#xff0c;挺有意思的记录一下 首先根据题目给出的信息可知涉及到此漏洞 https://crbug.com/1263462 poc如下&#xff1a; let theHole %TheHole(); m new Map(); m.set(1, 1); m.set(theHole, 1); m.delete(theHole);…

linux 串口测试指令和测试程序

一、串口设备查看 查看串口 (/dev) ls /dev/tty*查看串口&#xff08;或串口终端&#xff09;属性 ( /proc) cat /proc/tty/driver/serial 或 cat /proc/tt…

虾皮跨境电商物流:打造高效便捷的全球供应链解决方案

随着全球化的推进和电子商务的蓬勃发展&#xff0c;跨境电商物流成为了越来越多商家和消费者关注的焦点。虾皮&#xff08;Shopee&#xff09;作为一家领先的电商平台&#xff0c;不仅提供了丰富多样的商品选择&#xff0c;还致力于为卖家和消费者提供高效便捷的跨境电商物流服…

【C语言】指针详解(二)

目录 1.指针变量类型的意义 1.1指针的解引用 1.2指针 - 整数 1.3void*指针 2.const修饰指针 2.1const修饰变量 2.2const修饰指针变量 1.指针变量类型的意义 1.1指针的解引用 指针变量的大小和类型无关&#xff0c;只要是指针变量&#xff0c;在同一个平台下&#xff0…

每日一题——LeetCode844

方法一 暴力法&#xff1a; 对两个字符串分别从头到尾遍历一遍&#xff0c;遇到#就删除#和它之前的那个字符&#xff0c;如果遇到#在字符串的第一位则只用删除#&#xff0c;最后将删除后的不含#的两个字符串进行比较是否一样 var backspaceCompare function(s, t) {for(var …

《Python Advanced Programming + Design Patterns + Clean Code》

清洁代码 — 学习如何编写可读、可理解且可维护的代码 高级Python编程知识 Python之常用设计模式 Advanced Programming装饰器 decorators生成器 & 迭代器with 上下文管理器面向对象Mixin 模式反射机制并发编程 Design Patterns设计模式分类简单工厂模式工厂模式 √抽象工厂…

用C#也能做机器学习?

前言✨ 说到机器学习&#xff0c;大家可能都不陌生&#xff0c;但是用C#来做机器学习&#xff0c;可能很多人还第一次听说。其实在C#中基于ML.NET也是可以做机器学习的&#xff0c;这种方式比较适合.NET程序员在项目中集成机器学习模型&#xff0c;不太适合专门学习机器学习&a…

GNSS技术在城市规划中的革新:精准定位引领智慧城市发展

随着城市化的快速推进&#xff0c;城市规划愈发关键&#xff0c;而全球导航卫星系统&#xff08;GNSS&#xff09;技术的广泛应用正为城市规划带来一场前所未有的变革。本文将深入探讨GNSS模块在城市规划中的多重应用&#xff0c;以及如何通过精准定位推动智慧城市的发展。 城市…

【Python小知识 - 6】:QLabel设置图片

文章目录 QLabel设置图片 QLabel设置图片 from PyQt5.QtWidgets import * from PyQt5.QtGui import * import sysapp QApplication(sys.argv)window QWidget()hbox QHBoxLayout(window)# 设置标签图片 lable QLabel() lable.setPixmap(QPixmap(./img/window.png).scaled(1…

【ARM Cortex-M 系列 5 -- RT-Thread renesas/ra4m2-eco 移植编译篇】

文章目录 RT-Thread 移植编译篇编译os.environ 使用示例os.putenv使用示例python from 后指定路径 编译问题_POSIX_C_SOURCE 介绍编译结果 RT-Thread 移植编译篇 本文以瑞萨的ra4m2-eco 为例介绍如何下载rt-thread 及编译的设置。 RT-Thread 代码下载&#xff1a; git clone …