今天是学习《重学C++ 重构你的C++知识体系》 的第二天,重点在 C++基础句法。
三、C++基础句法
(一)自定义结构:枚举
enum 不仅能够创建符号变量,还能定义新的数据类型。
声明:
enum FinderWeekDayEnum{
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,
Sunday
}
定义:
FinderWeekDayEnum day
(二)结构体和联合体
使用 struct 定义的是结构体:
struct Student {
char name[6]; int age; Score s;
}
使用 union 定义的是联合体:
union Score {
double sc; char level;
}
结构体内存布局
(三)指向函数的指针
每一个函数都占用一段内存单元,它们有一个起始地址,指向函数入口地址的指针称为函数指针。
一般形式: 数据类型(*指针变量名)(参数表)
举例: int(*nameFuction)(int)
(四)命名空间
定义,使用 namespace
namespace wangWorld {
int test();
}
开始使用:
wangWorld::test()
或者
using namespace wangWorld
test()
using namespace wangWorld 在使用上等价 import wangWorld.
一般来说,如果你使用的是class,那么class一定程度上充当了namespace的效果,class和namespace互斥。
(五)内联函数
如果一个函数是内联的,那么在编译时,编译器会把该函数的代码副本放置在每个调用该函数的地方。
空间换时间。
引入内联函数的目的是为了解决程序中函数调用的效率问题。
注意:内联函数内部不能有太复杂的逻辑,编译器有时会有自己的优化策略,所以内联函数不一定起作用。
四、C++高级语法
(一)面向对象:类
C++使用struct、class来定义一个类:
- struct的默认成员权限是public;
- class的默认成员权限是private
除此之外,而这基本无差别。
(二)运算符重载
两个class 的 + 、- ,默认是不允许的,但我们可以通过运算符重载实现class的加减。
Complex operator+ (const Complex& x);
Complex Complex::operator+ (const Complex& x) {
Complex tmp; // 在栈中创建一个临时变量
tmp._real = _real + x._real;
tmp._image = _image + x._image;
return tmp;
}
上面代码是对的!
1. Comple tml 在栈上创建的变量是不是外面用不了?
你的代码片段是一个重载了加法运算符的成员函数。在这个函数中,你在栈上创建了一个临时变量tmp
来存储计算结果。然后,将该临时变量作为返回值返回。
这段代码是没有问题的。在函数中创建的临时变量tmp
是在栈上分配的,它的生命周期与函数的执行周期相同。当函数执行完毕后,临时变量tmp
会被自动释放。
当你使用这个重载的加法运算符时,它会返回一个临时创建的Complex
对象,你可以将其赋值给另一个Complex
对象,或者使用它进行其他操作。
例如:
1 | Complex a(1, 2); |
在上述示例中,c
是通过重载的加法运算符返回的临时对象。你可以在后续的代码中继续使用c
对象,它的生命周期与其他对象相同。
需要注意的是,虽然临时对象的生命周期是短暂的,但在其存在期间,你可以正常地使用它。只要你在临时对象的生命周期内进行操作,它是可用的。但是在临时对象的生命周期结束后,尝试访问该对象将导致未定义的行为。
总结起来,你的代码没有问题。临时对象在栈上创建,它在其所在的作用域结束后会被自动释放。你可以在临时对象的生命周期内使用它,但在其生命周期结束后,应避免访问该对象。
2. Complex c 在它的作用域外就会被自动回收是吧?
是的,你理解得很正确。在你的代码中,当你在函数内部创建一个临时变量Complex tmp
时,它的作用域仅限于该函数。当函数执行完毕后,临时变量tmp
会被自动释放,也就是说,它的生命周期结束了。
在你的示例中,通过重载的加法运算符,临时对象tmp
被创建并用于存储计算结果。然后,你将临时对象作为返回值返回,并将其赋值给Complex c
。在这个过程中,c
接收了临时对象的值,并成为了一个新的Complex
对象。
当函数执行完毕时,临时对象tmp
的作用域结束,它会被自动释放。然而,c
作为一个独立的对象,它的生命周期并不受临时对象tmp
的影响。c
会继续存在,你可以在后续的代码中继续使用它。
3. Complex tmp和Complex c的地址一样吗?
在你的代码中,Complex tmp
和Complex c
是两个不同的对象,它们在内存中分别有自己的地址。因此,tmp
和c
的地址是不一样的!!!!!!
当你在函数内部创建一个临时变量Complex tmp
时,它在栈上分配了一块内存,并拥有自己的地址。然后,通过赋值操作将临时对象的值传递给了另一个对象Complex c
。
虽然tmp
和c
的值可能相同,但它们是两个独立的对象,拥有不同的内存地址。
你可以通过打印它们的地址来验证:
1 | Complex tmp; |
输出结果会显示tmp
和c
的地址是不同的。
总结起来,Complex tmp
和Complex c
是两个不同的对象,它们在内存中有各自的地址。尽管它们的值可能相同,但它们是独立的对象。
(三)拷贝构造及临时对象的优化
在栈上的对象被销毁时,也会触发析构函数。
return 时如果返回了栈上的对象,那么就会触发:拷贝构造 ,拷贝完成后该栈上的对象就会被析构。
覆写拷贝对象函数:
Complex(const Complex& x); // 拷贝构造
Complex::Complex(const Complex& x) {
_real = x._real;
_image = x._image;
// 如果Complex包含堆上的对象,还会涉及深拷贝和浅拷贝,所以一般 都要重写类的拷贝构造函数。
}
章节延伸题思考
(一)C++中什么时候使用struct,什么时候使用union,什么时候使用class?
在C++中,struct、union和class都是用于定义自定义数据类型的关键字,它们之间的区别如下:
struct:struct用于定义一个包含多个成员变量的数据结构。它的成员变量默认是公有的(public),并且可以包含成员函数。通常情况下,struct用于表示一组相关的数据,可以有不同的访问权限。
union:union用于定义一种特殊的数据结构,它的所有成员共享同一块内存空间。union的成员变量可以有不同的数据类型,但是同一时间只能存储其中的一个成员。union通常用于节省内存,当你需要在不同的数据类型之间进行转换或共享内存时会很有用。
class:class用于定义一个包含数据成员和成员函数的对象。它的成员变量和成员函数默认是私有的(private),需要通过公有的接口(公有成员函数)来访问。class通常用于封装数据和行为,实现面向对象的编程。
选择使用struct、union或class取决于你的需求和设计目标:
- 如果你只需要简单地组织一些数据成员,可以使用struct。
- 如果你需要在不同的数据类型之间进行转换或共享内存,可以使用union。
- 如果你需要封装数据和行为,并且希望通过公有接口来访问和操作数据,可以使用class。
需要注意的是,struct、union和class之间的主要区别是默认的成员访问权限和默认的继承方式。但是在C++中,你可以使用访问修饰符(如public、private和protected)来显式地控制成员的访问权限,并且struct和class在语法上几乎是相同的,唯一的区别是默认的访问权限不同。
(二)结构体是struct和class有什么不同?
在C++中,struct和class在语法上几乎是相同的,唯一的区别是默认的成员访问权限和默认的继承方式。具体来说,struct和class之间的主要区别如下:
默认的成员访问权限:
- 在struct中,默认的成员访问权限是公有的(public),即结构体的成员变量和成员函数默认是公开的,可以被外部访问。
- 在class中,默认的成员访问权限是私有的(private),即类的成员变量和成员函数默认是私有的,只能在类的内部访问。
默认的继承方式:
- 在struct中,默认的继承方式是公有继承(public inheritance),即结构体默认是以公有继承方式派生的。
- 在class中,默认的继承方式是私有继承(private inheritance),即类默认是以私有继承方式派生的。
除了上述区别,struct和class在其他方面是相似的。它们都可以包含成员变量和成员函数,并且可以使用访问修饰符(如public、private和protected)来显式地控制成员的访问权限。
需要注意的是,这种区别只是默认行为,你可以在struct或class中使用访问修饰符来显式地指定成员的访问权限和继承方式,从而覆盖默认行为。因此,在实际使用中,struct和class可以互换使用,具体取决于你的需求和设计风格。
(三)class和struct的内存布局是连续的,还是不连续的?
在C++中,class和struct的内存布局是连续的。无论是class还是struct,它们都是用户定义的数据类型,其对象的内存布局是由成员变量和对齐规则共同决定的。
按照C++的规定,成员变量按照声明的顺序依次排列在对象的内存中。这意味着,成员变量在内存中是连续存储的,没有间隙或额外的填充字节。这样可以确保对象的内存布局是紧凑的,没有浪费空间。
需要注意的是,编译器可能会对对象的内存布局进行优化,例如进行字段重排或填充字节,以提高内存访问的效率。这些优化可能会导致对象的内存布局与代码中的声明顺序有所不同,但是成员变量仍然是连续存储的。
总结起来,class和struct的内存布局是连续的,成员变量按照声明的顺序依次排列在对象的内存中。这使得对象的内存布局是紧凑的,没有浪费空间。
(四)如果我有一个C++的类 Complex ,那么你建议我是使用 new Complex()来在堆中创建对象,还是使用Complex xx 在栈中创建对象?
在C++中,你可以选择在堆中使用new Complex()
或在栈中使用Complex xx
来创建对象,具体取决于你的需求和设计。
如果你需要在对象的生命周期超出当前作用域时仍然保持有效,或者需要在多个地方共享对象,那么使用堆内存创建对象可能更合适。使用new Complex()
会在堆中分配内存,并返回指向堆中对象的指针。你可以使用这个指针来访问和操作对象,并且可以在适当的时候使用delete
来释放堆内存。
1 | Complex* ptr = new Complex(); |
另一方面,如果你只需要在当前作用域内使用对象,并且对象的生命周期在作用域结束时自动结束,那么使用栈内存创建对象可能更方便。使用Complex xx
会在栈上分配内存,并在作用域结束时自动释放内存。
1 | void someFunction() { |
需要注意的是,使用堆内存创建对象需要手动管理内存,包括在适当的时候释放内存。而使用栈内存创建对象则由编译器自动管理内存,不需要手动释放。
综上所述,选择在堆中还是栈中创建对象取决于对象的生命周期和使用方式。如果对象需要在作用域外仍然有效或需要在多个地方共享,可以使用堆内存创建对象。如果对象只在当前作用域内使用,并且不需要手动管理内存,可以使用栈内存创建对象。
(五)如果我在C++中使用栈创建了一个对象,然后我把这个栈创建的对象使用一个指针进行引用,它会被自动释放吗?
在C++中,使用栈创建的对象的生命周期与其所在的作用域相关。如果你在栈中创建了一个对象,然后将其地址赋给指针进行引用,当对象所在的作用域结束时,对象会自动被释放,包括通过指针引用的对象。
考虑以下示例:
1 | void someFunction() { |
在这个示例中,Complex xx
在someFunction
函数中被创建,它是一个栈上的对象。然后,通过Complex* ptr = &xx
将对象的地址赋给指针ptr
。在ptr
指针所在的作用域结束时,即someFunction
函数结束时,栈上的对象xx
会自动被释放,包括通过指针ptr
引用的对象。
需要注意的是,尽管指针ptr
仍然保留着对象的地址,但是在对象所在的作用域结束后,访问指针ptr
所指向的对象将会导致未定义的行为。因此,在对象所在的作用域结束后,应该避免使用指针引用该对象。
总结起来,使用栈创建的对象在其所在的作用域结束时会自动被释放,包括通过指针引用的对象。因此,你不需要手动释放栈上的对象。