C++类和对象(下部曲)

news/2024/12/2 21:44:07/

构造函数
 

1 构造函数体赋值


在创建对象时,编译器通过调用构造函数,给对象中各个成员变量一个合适的初始值
虽然对象中已经有了一个初始值,但是不能将其称为对对象中成员变量的初始化

构造函数体中的语句只能将其称为赋初值,而不能称作初始化。因为初始化只能初始
化一次,而构造函数体内可以多次赋值
 

2 初始化列表
 

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

初始化列表是每个成员定义的地方

注意:
1. 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
2. 类中包含以下成员,必须放在初始化列表位置进行初始化
    引用成员变量:因为引用必须初始化,那就需要在定义时初始化
    const成员变量:const成员不能被修改,在定义的时候就要给值,否则就不能给值了
    自定义类型成员(且该类没有默认构造函数时):

尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,
一定会先使用初始化列表初始化
4. 成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后
次序无关
 

3 explicit关键字
 

构造函数不仅可以构造与初始化对象,对于单个参数或者除第一个参数无默认值其余均有默认值
的构造函数
,还具有类型转换的作用
 

1. 单参构造函数没有使用explicit修饰,具有类型转换作用

    explicit修饰构造函数,禁止类型转换
2. 虽然有多个参数,但是创建对象时只有第一个参数没有默认值,那么若没有使用explicit修饰,       具有类型转换作用

class Date
{
public: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;
};int main()
{Date d(2);d = 2023;//发生类型转换return 0;
}

用整型2023去赋值给日期类对象,实际上会用2023去构造一个临时对象,再用这个临时对象去赋值日期类对象

但若用explicit修饰构造函数后,禁止了类型转换,会报错

static成员
 

1 概念
 

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

2 特性
 

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

累积创建了多少个对象:调用构造函数的次数+调用拷贝构造函数的次数

正在使用的还有多少个对象:调用构造函数的次数+调用拷贝构造函数的次数-调用析构函数的次数

对象创建会自动调用构造函数,若是对象是用已存在对象初始化地创建,那就会自动调用拷贝构造函数,对象生命周期结束了会自动调用析构函数,所以以上两种问题可以通过统计构造函数、拷贝构造函数、析构函数的调用次数轻松解决

class A
{
public:A(){n++;m++;}A(const A& a){n++;m++;}~A(){m--;}static int GetN()//没有了this指针,在类外可以用类名::函数名直接调用,不用通过对象.函数名调用{return n;}static int GetM()//没有了this指针{return m;}
private:static int n;//累计创建的对象  静态成员变量属于所有A对象,属于整个类static int m;//正在使用的对象
};int A::n = 0;//在类外定义
int A::m = 0;int main()
{A a1;A a2;A a3(a1);A();//匿名对象,生命周期只在这一行cout << A::GetN() <<" "<<A::GetM()<< endl;cout << a1.GetN() << " " << a1.GetM() << endl;return 0;
}

友元
 

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

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

1 友元函数
 

在实际中若要重载operator<<,但是我们会发现没办法将operator<<重载成成员函数,因为ostream类型的对象要是第一个参数,但是在类的成员函数中,第一个参数是this指针,要是硬重载成为成员函数也是ok的,但是使用起来会非常别扭,比如:

class A
{
public:ostream& operator<<(ostream &out){out << _a << endl;return out;}
private:int _a=0;
};int main()
{A a;a << cout;return 0;
}

 是可以运行起来的,但是使用很别扭,所以我们要将operator<<重载成全局函数,这样两个参数的位置就可以由我们来分配了,但又会导致类外没办法访问成员,此时就需要友元来解决
 

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

class A
{friend ostream& operator<<(ostream& out, const A& a);friend istream& operator>>(istream& in, A& a);private:int _a=0;
};int main()
{A a;cin >> a;cout << a << endl;return 0;
}istream& operator>>(istream& in, A& a)
{in >> a._a;return in;
}ostream& operator<<(ostream& out,const A&a)
{out << a._a << endl;return out;
}

注意:

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

2 友元类
 

1  友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的私有成员
2  友元关系是单向的,不具有交换性
 比如现在有Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类可以直接
访问Time类的私有成员变量,但不可以在Time类中访问Date类中私有成员变量
3 友元关系不能传递
 如果C是B的友元, B是A的友元,不能说明C是A的友元
4 友元关系不能继承

class Time
{friend class Date;
public:Time(int hour = 1, int minute = 1, int second = 1):_hour(hour),_minute(minute),_second(second){}
private:int _hour;int _minute;int _second;
};class Date
{
public:Date(int year = 1, int month = 1, int day = 1):_year(year),_month(month),_day(day){}void TimeSet(int hour, int minute, int second){_t._hour = hour;//直接访问Time类的私有成员_t._minute = minute;_t._second = second;}
private:int _year;int _month;int _day;Time _t;
};

3 内部类
 

如果一个类定义在另一个类的内部,这个内部类就叫做内部类。内部类是一个独立的类,
不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越
的访问权限

内部类就是外部类的友元类

特性:
 

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

class A
{
public:class B//B天生就是A的友元{public:void Print(const A&a){cout << i << endl;cout << a._m << endl;}};
private:static int i;int _m = 3;
};int A::i = 4;int main()
{A::B b;b.Print(A());return 0;
}

匿名对象
 

匿名对象的生命周期只有一行

匿名对象这样定义:

class A
{
private:int _a;
};int main()
{A();//匿名对象return 0;
}

平常我们要调用类的成员函数,需要先写一行创建类对象,然后再写一行用对象去调用

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

此时匿名对象的作用就体现出来了:
 

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

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

 在传参和传返回值的过程中,一般编译器会做一些优化,以减少对象的拷贝

以下面代码为示例:


class A
{
public:A(int a = 0):_a(a){cout << "A(int a = 0)" << endl;}A(const A& a){_a = a._a;cout << "A(const A& a)" << endl;}A& operator=(const A& a){cout << "A& operator=(const A& a)" << endl;if (this != &a){_a = a._a;}return *this;}~A(){cout << "~A()"<<endl;}private:int _a;
};void f1(A aa)
{}A f2()
{A aa;return aa;
}

1 传值传参

int main()
{// 传值传参A aa1;//调用构造函数f1(aa1);//传值传参,调用拷贝构造(用已存在的对象去初始化地创建新对象)return 0;
}

 2 传值返回

A f2()
{A aa;//调用构造函数return aa;//aa作为局部变量出了函数作用域就销毁了,那么会生成一个临时变量(用aa去拷贝构造生成),返回的不是aa,是临时对象
}
int main()
{// 传值返回f2();return 0;
}

 

 

3 隐式类型

void f1(A aa)
{}
int main()
{// 隐式类型,连续构造+拷贝构造->优化为直接构造f1(1);return 0;
}

用整型1给A类对象aa传参,会发生隐式类型转换:

首先,会生成一个临时对象(用1去构造这个临时对象)

其次,用这个临时对象去拷贝构造aa

连续的步骤是:构造+拷贝构造

连续的构造,编译器会优化,直接合并成一步:构造

4 表达式传参

void f1(A aa)
{}
int main()
{// 一个表达式中,连续构造+拷贝构造->优化为一个构造f1(A(2));return 0;
}

 用匿名对象给A类对象aa传参:

首先,对匿名对象调用构造函数,初始化为2

其次,用匿名对象拷贝构造生成对象aa

连续的步骤:构造+拷贝构造

编译器做优化:直接合并为调用一次构造

 

 5

A f2()
{A aa;//调用构造函数return aa;//aa作为局部变量出了函数作用域就销毁了,那么会生成一个临时变量(用aa去拷贝构造生成),返回的不是aa,是临时对象
}
int main()
{// 一个表达式中,连续拷贝构造+拷贝构造->优化一个拷贝构造A aa2 = f2();return 0;
}

1 调用f2函数,首先创建对象aa,调用了构造函数

2  f2调用结束,要返回aa对象,要生成一个临时对象,用aa拷贝构造生成

3 临时对象带回返回值,临时对象拷贝构造对象aa2

连续的步骤:步骤2+3 即拷贝构造+拷贝构造

编译器优化:直接合并为调用一次拷贝构造

在aa销毁前,用aa拷贝构造aa2

A f2()
{A aa;//调用构造函数return aa;//aa作为局部变量出了函数作用域就销毁了,那么会生成一个临时变量(用aa去拷贝构造生成),返回的不是aa,是临时对象
}
int main()
{A aa1;// 一个表达式中,连续拷贝构造+赋值重载->无法优化aa1 = f2();return 0;
}

1 创建aa2,调用构造

2 调用f2函数,创建aa,调用析构

3 返回局部变量aa,先生成临时变量,aa拷贝构造临时对象

4 临时对象赋值给对象aa1

连续步骤:3+4 即拷贝构造+赋值重载

由于不属于一个派系,所以编译器无法优化

7

int main()
{A aa2 = 1;//类型转换return 0;
}

首先会用1去构造一个临时对象,再用这个临时对象去拷贝构造aa2

连续的构造+拷贝构造,编译器直接优化成:构造

 

 

nt main()
{A aa3 = A(2);return 0;
}

首先用2去构造匿名对象,然后用它去拷贝构造aa3

连续的构造+拷贝构造,编译器直接优化为:构造

然后再调用析构函数,析构aa3对象 


http://www.ppmy.cn/news/997103.html

相关文章

scrapy框架简单实现豆瓣评分爬取案例

豆瓣网址&#xff1a;https://movie.douban.com/top250 1.创建scrapy框架 scrapy startproject 项目名(scrapy_test_one)创建好以后的目录是这样的 2.创建spider文件 在spiders目录下创建一个spider_one.py文件&#xff0c;可以随意命名&#xff0c;该文件主要是让我们进行数…

如何训练最终机器学习模型 How to Train a Final Machine Learning Model

The machine learning model that we use to make predictions on new data is called the final model. 我们用来对新数据进行预测的机器学习模型称为最终模型。 There can be confusion in applied machine learning about how to train a final model. 在应用机器学习中,关…

CAS操作——Java并发编程

CAS&#xff08;Compare and swap&#xff09;比较并交换操作是一种常见的并发控制方法&#xff0c;用于实现原子性更新共享变量的值。 Java并发编程中CAS操作需要输人两个数值&#xff0c;一个旧值(期望操作前的值)和一个新值&#xff0c;在操作期间先比较旧值有没有发生变化…

BES 平台 SDK之LED的配置

本文章是基于BES2700 芯片&#xff0c;其他BESxxx 芯片可做参考&#xff0c;如有不当之处&#xff0c;欢迎评论区留言指出。仅供参考学习用&#xff01; BES 平台 SDK之代码架构讲解二_谢文浩的博客-CSDN博客 关于SDK 系统框架简介可参考上一篇文章。链接如上所示&#xff01…

DID以及社交网络中的ZKP

1. 引言 本文关键术语为&#xff1a; Decentralized Identity (DID&#xff0c;去中心化身份) or self-sovereign identity (SSI&#xff0c;自治身份) &#xff1a;是一个基于开放标准的框架&#xff0c;使用自主、独立的标识符和可验证证书&#xff0c;实现可信的数据交换。…

谷歌Tsunami(海啸)扫描器搭建扩展使用教程

目录 介绍 下载地址 功能总结 原理 服务探测 漏洞检测 安装

VGN N75pro说明书

VGN N75pro说明书 1、封面和最后一页 2、第01、02、03 3、第04 4、第05

详解顺序表功能

前言 随着我们C语言的不断深入学习&#xff0c;我们要开始学习一点数据结构来增加我们的内功了&#xff0c;虽说现在很多高级语言的顺序表&#xff0c;链表等可以不用自己实现&#xff0c;但在C语言中是需要我们自己来实现的&#xff0c;这并不能说明C语言和其他语言比C语言很…