C++使用类的一些注意事项

这篇具有很好参考价值的文章主要介绍了C++使用类的一些注意事项。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

C++使用类的一些注意事项,#C++的学习与总结,c++,开发语言

前言:

本篇内容为前面的补充,介绍了我们使用类时需要注意些什么以及一些编译器的优化,可能有些理解不到位或者错误,请斧正。

目录

前言:

1.再谈构造函数

2.(c++98)隐式类型转换中的编译器的优化

3.explicit关键字

4.static成员

5.匿名对象

6.友元函数

7.内部类

8.编译器的一些场上的优化

总结:

若有歧义,请指出,感谢阅读!


1.再谈构造函数

我们在构造函数体中,给成员变量赋值能叫做成员变量的初始化吗?并不可以,这种行为只是给成员变量赋初值,在函数体中,我们可以多次赋值,而初始化只能初始化一次。

那该如何初始化呢?

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

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

private:
	int _year;
	int _month;
	int _day;
};

 那像const这样的必须在定义位置的初始化的,放在成员变量里面该怎么初始化呢?

可以给缺省值,但不是初始化:

class A
{
public:
	A()
		:_x(1)
	{
		_a1++;
	}

private:
	int _a1 = 1;
	int _a2 = 2;

	const int _x;//可以给缺省值,但不是初始化

};

另外,缺省值也会在初始化列表进行初始化:

C++使用类的一些注意事项,#C++的学习与总结,c++,开发语言

如上图我们可以看到, _a1的结果是2,虽然没有在初始化列表中显示的初始化,但是还是会走初始化列表初始化;其次_a2的结果是0,虽然_a2的缺省值是2,但是在初始化列表中显示的初始化为了1,所以再--就是0。而对于即不给缺省值也不给初始化的普通成员变量,经过测试,那就是随机值。

对于成员变量是引用的与成员变量是自定义类型的:

class B
{
public:
	B(int b)
		:_b(0)
	{
		cout << "B()" << endl;
	}

private:
	int _b;
};

class A
{
public:
	A()
		:_x(1)
		,_ref(_a1)
		,_bb(0)
	{
		_a1++;
	}

private:
	int _a1 = 1;
	int _a2 = 2;

	int& _ref;
	B _bb;

	const int _x;//可以给缺省值,但不是初始化

};

成员变量是引用的,跟const一样,本身引用就是要在定义的位置初始化,所以我们可以给缺省值,或者要在初始化列表初始化。

对于自定义类型的成员变量,_bb会去调用它的构造函数初始化吗?经过我的测试,_bb这个自定义类型的成员变量,如果不在A中的初始化列表初始化,就要去调用它的构造函数,但是一定要确保B中的构造函数一定是默认的,也就是说必须是全缺省的或者是不写编译器自动生成的。而上面的代码中B中的构造函数不是默认的构造函数,所以我们如果在A的类中不对_bb进行初始化列表的初始化,就会报错。

再看一个例子:

C++使用类的一些注意事项,#C++的学习与总结,c++,开发语言

 注释部分的构造函数可以,对两个自定义类型的成员进行了初始化列表的初始化,而内置类型由于没有显示的写就使用了缺省值,所以可以;如果Stack这个自定义类型中的构造函数是默认的,什么也没写的构造函数也是可以的,对于自定义类型初始化去调用它的默认构造函数,内置类型的初始化由于没有显示的写就使用它的缺省值。

其次还需要注意一个点:

class A1
{
public:
	A1(int a)
		:_a1(a)
		,_a2(_a1)
	{}

	void Print()
	{
		cout << _a1 << " " << _a2 << endl;
	}

private:
	int _a2;//声明的次序就是在初始化列表中的初始化的顺序
	int _a1;
};

int main()

{
	//A aa;
	A1 aa(1);
	aa.Print();
	return 0;
}

上面的结果应该是什么?结果是输出1和随机值。

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

总结:

首先不管在初始化列表中显示的写没写初始化,都会在初始化的列表中走一遍。

其次记住一个原则:给初始化就在初始化列表中给初始化。

C++使用类的一些注意事项,#C++的学习与总结,c++,开发语言

2.(c++98)隐式类型转换中的编译器的优化

class A
{
public:
	A(int a)
		: _a1(a)
	{
		cout << "A(int a)" << endl;
	}

	A(const A& aa)
		:_a1(aa._a1)
	{
		cout << "A(const A& aa)" << endl;
	}

private:
	int _a1;
};


int main()

{
	A aa1(1);//构造函数
	A aa2 = 1;//隐式类型转换


	return 0;
}

根据我们之前的隐式类型转换的知识,我们可以知道这里对aa2这个对象赋值就是在进行隐式类型转换:首先1先构造一个临时对象,这个临时对象再拷贝给aa2,所以会去调用拷贝构造(注意拷贝构造也是构造,所以也有初始化列表)。

但是我们看到结果不是这样的:

C++使用类的一些注意事项,#C++的学习与总结,c++,开发语言

为什么呢?这就是编译器所做的优化,因为编译器觉得自定义类型的这种初始化写起来还要调用拷贝构造,所以直接就优化了,直接就优化为了一步构造,1直接构造aa2。(注意这里的优化只限定于c++98的单参数的构造)

C++使用类的一些注意事项,#C++的学习与总结,c++,开发语言

而对于这一种,编译器还能这样优化吗?答案是不能的,因为我们知道,10先构造出一个A类型的临时变量,而这个临时变量又具有常性且ref是这个临时变量的别名,所以需要加上const,而就是由于这个临时变量具有常性,编译器在这里就不会优化掉这个临时变量,所以10就没法直接构造ref了,而是先构造这个临时变量(这里使用的vs2022的编译器发现也没有调用拷贝构造,可能是编译器做的更极端了,也优化了,但是我们知道其后的原来即可)。

3.explicit关键字

C++使用类的一些注意事项,#C++的学习与总结,c++,开发语言

explicit的引入就是为了防止隐式类型转换,这里加上了explicit, A aa2=1和const A& ref=10的隐式类型转换就没有了,就编不过了。

但是上面的隐式类型转换都是对于单参数的构造,对于多参数的构造,c++11可以使用多参数的构造来进行隐式类型转换:

C++使用类的一些注意事项,#C++的学习与总结,c++,开发语言

C++使用类的一些注意事项,#C++的学习与总结,c++,开发语言

其实也都是先调用构造,然后再进行隐式类型转换,再经过编译器的优化, 会节省一次隐式类型转换产生的拷贝。

同样的,如果不想使用隐式类型转换,就在构造函数上加上explicit,防止构造函数的隐式类型转换,这时A aa(1,1)这样需要隐式类型转换的就编不过了。

如果想使用隐式类型转换,让编译器进行优化,节省一次拷贝,就可以不加explicit。 

4.static成员

我们将声明为static的类成员称为类的静态成员,用static修饰的成员变量,称为静态成员变量;用static修饰的成员函数,称之为静态成员函数。注意的是,静态成员函数没有this指针,静态成员变量在类中声明,在类外进行初始化。

统计程序中创建出了多少个类对象:

class A
{
public:
	A(int a = 0)
	{
		cout << "构造" << endl;
	}

	A(const A& aa)
	{
		cout << "拷贝构造" << endl;
		++count;
	}

	static int GetCount()//没有this指针
	{
		return count;//static函数没有this指针,访问不到成员count,只能读不能写
	}

private:
	static int count;//声明,属于所有对象,属于整个类

};

int A::count = 0;//定义初始化
void func(A a)
{

}

int main()
{
	A aa1;//调用构造

	A aa2(aa1);//调用拷贝构造

	func(aa1);//函数传参,且参数是A类型的,所以调用拷贝构造

	A aa3 = 1;//隐式类型转换,经过编译器的优化,优化掉了拷贝构造,只有构造
	
	A aa4[10];//调用10次构造

	cout << aa3.GetCount() << endl;//如果GetCount是个static修饰的,只能接受返回的count,不能改

	//类中的静态成员也受访问限定符的限制,如果我们不让静态成员count设为私有,如何访问count?
	//A::count;
	//aa2.count或者aa3.count      count属于所有对象
	//A* ptr=nullptr;ptr->count   这里不会去解引用,会直接去静态区中找

	return 0;
}

 首先上面代码需要注意的是,GetCount是一个静态成员函数,所以访问它可以通过对象访问即aa3.GetCount(),或者指定内域访问,A::GetCount(),但是由于静态成员函数没有this指针,所以函数内不能访问非静态成员变量(这里返回count就是因为count是个静态成员变量,换成是普通的变量就不行了):

C++使用类的一些注意事项,#C++的学习与总结,c++,开发语言

其次类中的静态成员也是受类的访问限定符的限制的,像这里count为私有,虽然是静态的全局的变量,但是在类外面还是访问不到的。

如果我们不将count设为私有,那怎么访问这个静态成员变量呢?

1.A::count  直接指定内域访问

2.aa2.count或者aa3.count    因为static成员是属于所有对象的,所以可以

3.A* ptr=nullptr;ptr->count    这里不会解引用,直接去静态区找

同时我们上方的代码也复习之前的知识:

C++使用类的一些注意事项,#C++的学习与总结,c++,开发语言

分析结果:

前两个分别调用构造与拷贝构造,没什么说的;第三个是函数传参,由于参数也是A,也就是类类型的,所以会调用拷贝构造;然后是下面的隐式类型转换,编译器优化掉了拷贝,直接就是构造;然后可以看到如果自定义对象是数组,可以看到调用了10次。

总结:

C++使用类的一些注意事项,#C++的学习与总结,c++,开发语言

5.匿名对象

当我们需要调用某个类的成员函数时需要先创建一个对象,所以我们引入了一个匿名对象,可以直接不创建对象直接去调用,写法为:类名()

C++使用类的一些注意事项,#C++的学习与总结,c++,开发语言

通过析构函数可知,匿名对象的周期只在它出现的这一行,到下一行就会销毁。

C++使用类的一些注意事项,#C++的学习与总结,c++,开发语言

返回值也可以使用匿名对象,更加简洁。

6.友元函数

友元函数在我们之前提到过,现在再来细看一下。

C++使用类的一些注意事项,#C++的学习与总结,c++,开发语言

7.内部类

内部类c++很少用,隔壁Java常用。

C++使用类的一些注意事项,#C++的学习与总结,c++,开发语言

首先,如果B这个类在A中是公有的,可以直接在外面指明内域访问例如A::B bb;

其次,如果B是私有的,那就不能通过A来访问了,所以B这个内部类既受A的类域的

限制(因为B为公有在外面需要指定在A的内域),又受到A的访问限定符的限制

然后,B这个内部类天生就是A的友元,所以可以通过内部类访问外部类的成员(静态的也可以)

补充:一个类里面,公有可以访问私有

总结:C++使用类的一些注意事项,#C++的学习与总结,c++,开发语言

8.编译器的一些场上的优化

首先先来分析3个优化:

class A
{
public:
	A(int a = 0)
	{
		cout << "构造" << endl;
	}

	A(const A& a)
	{
		cout << "拷贝构造" << endl;
	}

	~A()
	{
		cout << "~A" << endl;
	}
};

void func1(A aa)
{

}

void  func2(A& aa)
{
	
}

A func3()
{
	return A();
}

int main()
{
	A aa1 = 1;
	
	func1(aa1);

	func1(2);

	func1(A(3));
	return 0;
}

第一个就是隐式类型转换编译器会将隐式类型转换产生的拷贝优化掉,所以就只有一个构造。

第二个是传参调用拷贝构造,但是这个拷贝构造不会被优化(大部分情况下只有c++98中的那个单参数构造和c++11的多参数构造会优化),但是可以使用传引用传参来减少拷贝。由于拷贝构造产生了临时变量,所以在func1函数结束时,会调用析构销毁这个临时变量。

第三个与第四个也都是传参调用拷贝构造,但是结果发现,拷贝构造被优化为了构造?!理想的结果不应该是拷贝构造,然后跟析构吗???当时我也疑惑了好久,其实这是因为编译器的处理,在我的vs2022的编译器下,可能由于编译器太新,优化的比较极端,编译器看到你func1函数什么也没写,干嘛要在拷贝构造一次,干脆直接就优化为了拿形参构造实参,可以看到构造后紧跟的就是析构,这也表明还是存在拷贝产生的临时变量需要销毁。

最后主函数结束aa1销毁调用析构。

C++使用类的一些注意事项,#C++的学习与总结,c++,开发语言

另外,如果我们使用func2的传引用传参,后两个传参会直接报错,因为它们是临时变量,使用引用后,传过去后函数作用域销毁,这个变量就找不到了;而aa1的作用域在main函数中,尽管fun2的函数结束,但是还是能找到aa1。

再看一种优化:

C++使用类的一些注意事项,#C++的学习与总结,c++,开发语言

在这个场景下, A aa先构造,返回aa再调用拷贝构造(因为没有创造对象,所以这个拷贝构造有可能被优化掉),拷贝的临时变量销毁调用一次析构,aa这个局部对象销毁再调用一次析构,那这两个析构谁先调用的呢?拷贝构造后面经跟的就是拷贝时创建的临时变量销毁调用的析构

此时还没有优化,但是当我们来接受它的返回值时:

 C++使用类的一些注意事项,#C++的学习与总结,c++,开发语言

aa拷贝给一个临时变量,临时变量再拷贝给给aa1,这里就会被优化为一个拷贝构造。 

如果我们在将赋值重载再写出来(下面的称为赋值接受):

C++使用类的一些注意事项,#C++的学习与总结,c++,开发语言

就会得到下面的结果:

C++使用类的一些注意事项,#C++的学习与总结,c++,开发语言

 如果先定义一个对象,再接收返回值,就是aa2先构造,func3里aa构造,返回时一个拷贝构造(这个拷贝构造有可能会被优化,因为没有创造新的对象来接收返回值,看编译器,这里就是被优化了)然后没有被优化,那会多出来一对拷贝构造+析构。结果中的两个析构分别是func3函数结束aa销毁调用的析构和main函数结束aa2销毁的析构。

如果是下面的场景(下面的称为拷贝构造接受):

C++使用类的一些注意事项,#C++的学习与总结,c++,开发语言

如果直接调用fun2,返回的匿名对象调用一次构造,匿名对象出了fun2析构一次。

如果接收fun2的返回值,匿名对象A()先构造一次,返回时拷贝构造一次,返回给aa2再拷贝一次编译器会优化用 匿名对象直接构造aa2,直接就是构造一次,所以最后一次析构是aa2销毁的析构

总结:

优化的场景很多,我们只要记住:

C++使用类的一些注意事项,#C++的学习与总结,c++,开发语言文章来源地址https://www.toymoban.com/news/detail-846228.html

总结:

若有歧义,请指出,感谢阅读!

到了这里,关于C++使用类的一些注意事项的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • ES 一些简单 的查询注意事项

    term query 不分词字段 带分数 where name=xxx filter 分词字段 不分词字段 不带分数 Terms query 所有类型 带分数 where name in(xxx) Range query where name between xxx and xxx Exists Regexp Match query 分词字段/基础字段 Multi-match query 多个分词字段/基础字段 Boolean query 复合查询 must should 带分数 filter must

    2024年02月12日
    浏览(71)
  • Elasticsearch 一些异常报错、注意事项(1)

    系统支持通过参数(op_type=create)强制执行创建索引操作。只有当系统中不存在此文档的时候才会创建成功。如果不指定此操作类型,如果存在此文档则会进行更新操作。 bulk 默认op_type 是index 当创建文档的时候,如果不指定id,系统则会默认创建id。自动生成的id是一个不会重

    2023年04月08日
    浏览(20)
  • 手机怎么剪视频?分享一些剪辑工具和注意事项

    视频剪辑是一种将多个视频片段进行剪切、合并和编辑的技术,它可以帮助我们制作出精彩的视频作品。如今,随着智能手机的普及,我们可以随时随地使用手机进行视频剪辑。本文将为大家介绍一些手机剪辑工具和注意事项,帮助大家更好地进行视频剪辑。   首先,我们来

    2024年02月10日
    浏览(36)
  • Spring Data Elasticsearch 一些异常报错、注意事项(1)

    记录一:批量更新数据saveAll 引入maven依赖  saveAll批量新增,如果数据存在则会更新数据 记录二:批量更新数据Script脚本更新字段 参考:Script query | Elasticsearch Guide [8.5] | Elastic 记录三:空字段查询处理 如果查询字段createTime在ES数据中不存在,直接用.must(QueryBuilders.rangeQuery(

    2024年02月11日
    浏览(34)
  • Unity发布抖音小游戏的一些注意事项

    使用 webgl模式 发布抖音小游戏的一些注意事项 1.打包 使用webgl模式打包抖音小游戏,会因为找不到aapt工具导致打包失败 提示: aapt检查失败: sdk/build-tools/版本号。 解决方法:再unity hub里添加Android Build Support  2.黑屏问题 相机的HDR设为off 3.Text字体不见的问题 不能用unity自带的

    2024年02月11日
    浏览(30)
  • git常规操作流程(纯命令行操作)和一些注意事项

    当你在单位拿到了git仓库,并利用公司给你的OA账号和邮箱完成了你的git基础配置,下面就是使用命令行的无错固定操作流程 如果你很着急,你可以直接跳到最后的 总结部分 1.从仓库克隆代码到本地 这里的[codeUrl]就是你仓库的地址,当你在仓库点击图中绿色位置时,剪贴板复制的就

    2024年02月03日
    浏览(30)
  • 关于 Go 协同程序(Coroutines 协程)、Go 汇编及一些注意事项。

    参考:  Go 汇编函数 - Go 语言高级编程 Go 嵌套汇编 - 掘金 (juejin.cn) 前言: Golang 适用 Go-Runtime(Go 运行时,嵌入在被编译的PE可执行文件之中)来管理调度协同程式的运行。 Go 语言没有多线程(MT)的概念,在 Go 语言之中,每个 Go 协程就类似开辟了一个新的线程,效率上,肯

    2024年01月25日
    浏览(59)
  • MySQL5 和 MySQL8 的配置区别 & 一些注意事项

    先保证你的mysql正在运行,假如用户名是 root ,密码是 123456 ,运行下边的代码可以查看mysql的版本号。 这里我的版本是5.7.19。也就是5版本的。 注意:下边的url区别不用管。看到这就可以了。 当然8版本的后边不写也没事。 不建议在没有服务器身份验证的情况下建立SSL连接。

    2024年02月05日
    浏览(28)
  • yolov8使用C++推理的流程及注意事项  undefined reference to `xxxxx‘

    1.下载yolov8项目源码GitHub - ultralytics/ultralytics: NEW - YOLOv8 🚀 in PyTorch ONNX OpenVINO CoreML TFLite  2.下载opencvReleases - OpenCV,建议版本=4.7.0,选择下载源码, windows版本由于使用的编译器与我们所使用的mingw不一致,后续会出现错误,所以我们选择自己编译 3.下载cmake编译工具,添加到环

    2024年02月11日
    浏览(25)
  • Unity学习笔记--File.ReadAllLines和File.ReadAllText的使用以及注意事项(一定要看到最后!!!)

    最近在做文件存储以及读取的时候,需要用到C#给我们提供的类: File 具体使用方法可以看官方文档:C# File 类 这篇文章只会说 File.ReadAllLines 和 File.ReadAllText 的使用以及注意事项 Tips:(一定要看到最后!!!) 一、File.ReadAllLines 重载 操作 ReadAllLines(String) 打开一个文本文件

    2024年02月02日
    浏览(26)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包