- 1.P52 处理多返回值
- 2.P53 模板
- 2.1.模板函数
- 2.2.模板类
- 2.2.1.将数组大小作为模板参数
- 2.2.2.将数组大小和数组类型都作为模板参数
- 2.3.何时使用模板
- 3.P54 堆和栈内存的比较
参考:视频 笔记
这一节没有什么实质有用的内容,可以大致总结为处理多返回值的两大方式。
一个是使用引用或者指针把多个返回值作为形参传入函数;另一个是返回的结果是一个特殊的数据结构,比如结构体、数组、元组等等。
视频中主要讲解了返回结果是一种特殊数据结构的例子,包括结构体、原始数组、array、vector,类型不同的时候可以返回tuple、pair。
关于这些内容的具体讲解可以去看上面的参考笔记。
参考:视频 笔记
2.1.模板函数C++模板类似于其他语言的泛型,模板有点像宏然而泛型却非常受制于类型系统。
我们可以根据不同的用途来定义编译的模板,模板就是让编译器基于我们给编译器的规则来为我们写代码。
当写一个函数里面使用模板,实际上创建了一个蓝本,因此当调用这个函数时,可以指定特定的参数,这个参数决定了放入到模板中的实际代码,也决定了如何使用这个函数。
比如创建一个模板Print函数打印不同类型的数据,注意模板中的类型声明typename
也可以写成class
,在这里二者是同一个意思,但是为了于类区分习惯上用typename
。
#include
#include
template<typename T> //typename也可以写成class,在这里二者是同一个意思,但是习惯上用typename
void Print(T value)
{
std::cout << value << std::endl;
}
int main()
{
Print(5);//T替换为int
Print("Hello");
Print(5.5f);
std::cin.get();
}
我们通过单词template
定义一个模板,这时的Print函数不是一个真的函数,只有当我们实际调用它时,这些函数才会被真的创建,并作为源代码被编译。
若我们不写任何东西,完全没有使用Print函数那么它就没有真正存在过。
这个Print函数只是一个模板,只有当调用时才会被实际创建。
我们选择typename
作为模板参数的类型,T
是模板的参数名称。
这里看上去是显示地指定类型,其实这个类型是隐式地从实际参数中得到的,是基于编译器推断出来的。
我们还可以调用Print
使用尖括号显式地指定传入的模板参数的类型。
Print<int>(5);
2.2.模板类
2.2.1.将数组大小作为模板参数
注意模板不仅可以设置数据类型(比如int
、float
),还可以设置其他参数,比如数组的大小。
因为模板是在编译期间被评估的,也就是模板的参数是在编译期间被转换成实际代码的。
所以对于在栈上分配的数组,要求必须在编译时就知道其大小,此时使用模板参数指定这个大小非常合适。
这里模板参数的作用和宏定义非常相似。
实例:我们创建一个在栈上的Array
类,其成员变量是一个数组,数组大小是在编译时确定的,不能直接输入一个变量size
之类。
class Array
{
private:
int m_Array[size];
};
因为这是一个栈分配的数组,所以在编译时就需要知道它。
显然我们可以使用动态分配栈内存(alloca)
或者其他方法,但我们只想在栈上创建一个普通的C语言风格的数组。
因为size
值要在编译时就要知道,而模板会在编译期被评估处理。
所以正好可以将数组大小转换成一个模板参数,并且这里我们明确知道存储的数组是int
类型,因此不用typename
作为模板参数来制定数据类型。
代码如下,声明一个模板参数N
,其类型是int
,然后在类中数组大小就用这个模板参数N
来指定。
当我们调用这个Array
时,指定一个Array
大小为5,命名为array
,这意味将第9和11行的N
改为5。
若不是显示地指定数组类型是int
,想让这个类型也是可变的。
因此希望能够在编译时指定这个数组实际的类型,可以添加另一个模板参数T
,在数字面前添加这个参数。
template<typename T,int N>
用T来替换原数组中的int
,然后得到一个类型为T
的数组,它在编译时会被模板指定,模板参数N
同样也会在编译时会被模板指定。
当实际调用这个Array
时,需要指定int
作为数组类型,5作为数组大小,也可以把它们变成任何我们想要的东西。
总结:实际上,我们在这里创建的Array
与标准数组类std::array
在C++标准模板库中的工作方式非常相似,标准模板库有两个模板参数:type
和size
。
模板是很有用的一个方法,比如在日志系统中,要记录不同类型的数据,那么使用模板是非常合适的。
但是模板的使用应该适度,不能太过分。
假设使用了很多模板,多到需要自己挨个模板函数去看哪个被编译哪个没有被编译,把实际的数据类型往模板参数里填空,那么说明此时使用模板就有点过分了。
参考:视频 笔记
当程序开始的时候内存被分成了一堆不同的内存区域(包括栈和堆),在应用程序启动后, *** 作系统将整个程序加载到内存并分配一堆物理RAM以便程序可以运行。
堆和栈是RAM中实际存在的两个区域,注意他们都是存储在相同的物理设备上的,都是在RAM内存上。
栈通常是一个预定义大小(2M字节左右)的内存区域,堆也是一个预定义默认值的区域,但是它可以随着应用程序的进行而改变。
- 堆和栈的不同,主要在于内存分配方式的不同:
-
栈分配内存:在栈中分配变量时,是栈顶部的指针移动字节,大多数的栈都是倒着来的,即第一个变量存储在更高的内存地址上。
栈是把东西堆在一起所以栈分配速度很快。
而在栈中分配内存的这个作用域(函数作用域或者空作用域或者循环等都可以)一旦结束,在栈中分配的所有内存都会被d出,会被释放。
释放内存没有任何开销,栈释放内存与分配内存一样不需要将栈指针反向移动然后返回栈指针地址,只要d出栈中的东西,栈指针自然就回到了作用域开始之前。
一条CPU的删除指令就可以释放所有东西。
-
堆分配内存:
new
关键字实际上调用了一个叫做malloc(memory allocate的缩写)
的函数,这样做通常会调用底层 *** 作系统或平台的特定函数,将在堆上为我们分配内存。当启动程序时会得到一定数量的RAM,程序会维护一个叫做空闲列表的东西,它跟踪哪些内存块是空闲的还有在什么位置。
在堆上分配内存更容易出现cache misses(缓存不命中)。
- 堆栈分配对比:
在栈上分配就是一条CPU命令:
在堆上的分配调用了 *** 作符new,new *** 作符又调用了malloc,然后要去到空闲列表去检查是否有足够的内存,然后记录分配了多少内存,使用完后还要delete它。
所以我们应尽量在栈上分配,在堆上分配的唯一原因是不能在栈上分配。
比如需要这个生命周期比函数的作用域更长,或者需要特别大的数据。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)