C++学习笔记(7)

本文介绍了C++中处理多个返回值的方法,如使用结构体、数组、vector、tuple和pair。同时,讲解了模板template的使用,以及堆栈内存的区别。此外,还探讨了C++宏、auto关键字的功能,以及静态数组std::array的使用。最后,介绍了函数指针和lambda函数在C++中的应用。
摘要由CSDN通过智能技术生成

三十四、C++处理多返回值(还得是结构体/类)

若有一个函数需要返回两个字符串,有很多不同的方法可以实现。但在C++的默认情况下不能返回两种类型

1、结构体

若有个函数叫ParseShader需要返回两个字符串。可以选择的解决方法是:创建一个叫做ShaderProgramSource的结构体,它只包含这两个字符串。若还想返回一个整数或其他不同类型的东西,可以把它添加到结构体中并返回它。

struct ShaderProgramSource
{
	std::string VertexSource;
	std::string FragmentSource;
	int a;
}
static ShaderProgramSource ParseShader(const std::string& filepath)
{

}
return {vs,fs};

2、数组

我们可以返回一个数组,就像返回一个std::string*指针。这是一个2个元素的数组,我们可以传入VertexSource或者FragmentSource。这不通用,因为必须要同一种类型
在这里插入图片描述
还可以返回一个std::array,类型是string,大小是2。

#include <array>
static std::array<std::string,2> ParseShader(const std::string& filepath)
{
}
return std::array<std::string,2> (vs,fs);
///OR//
std::array<std::string,2>results;
results[0]=vs;
results[1]=fs;
return results;

3、vector

若返回两种以上则可以使用vector。它和array的区别是array在栈上创建,而vector会把它的底层存储在堆上,所以std::array会更快。

#include<vector>
static std::vector<std::string> ParseShader(const std::string& filepath)
{

}
std::vector<std::string>results;
results[0]=vs;
results[1]=fs;
return results;

4、tuple

vector和array只有在类型相同的情况下才有效。若类型发生变化,有两种方式,一种是tuple(元组),另一种是pair。

  • tuple基本上是一个类,它可以包含x个变量但它不关心类型;tuple在functional里面,utitly提供了make_tuple这样的工具。
#include<utitly>//utitly提供了make_tuple工具
#include<functional>//tuple在functional里面
static std::tuple<std::string,std::string,int> ParseShader(const std::string& filepath)//增添int
{

}
return std::make_pair(vs,fs);
std::tuple<std::string,std::string> sources=ParseShader("res/shaders/Basic.shader");
//auto sources=ParseShader("res/shaders/Basic.shader");

从tuple里获取数据需要使用std::get;

std::string vs=std::get<0>(sources);

另一个例子:

#include <iostream>
#include <tuple>

using namespace std;

int main()
{
    tuple<int, double, string> my_tuple(1, 2.3, "hbh");

    int my_int;
    double my_double;
    string my_string;

    cout << get<2>(my_tuple) << endl;

    tie(my_int, my_double, my_string) = my_tuple;
    cout << my_string << endl;

    auto [x, y, z] = my_tuple;
    cout << z << endl;

    return 0;
}

5、pair

std::pair ,它与tuple的区别是返回值是2个字符串,也可以使用std::get或者sources.first,sources.second。

static std::pair<std::string,std::string> ParseShader(const std::string& filepath)
{
}

三十五、模板template

当写一个函数里面使用模板,实际上创建了一个蓝本,因此当调用这个函数时,可以指定特定的参数,这个参数决定了放入到模板中的实际代码,也决定了如何使用这个函数。

#include<iostream>
#include<string>

template<typename T>//typename也可以写成class
void Print(T value)
{
	std::cout << value << std::endl;
}

int main()
{
	Print(5);//T替换为int
	Print("Hello");//T替换为string
	Print(5.5f);//T替换为float
	std::cin.get();
}
  • 模板函数不是一个真的函数,只有当我们实际调用它时,这些函数才会被真的创建,并作为源代码被编译。
  • 我们选择typename作为模板参数的类型,T是模板的参数名称。
  • 这里看上去是显示地指定类型,其实这个类型是隐式地从实际参数中得到的。
  • 若我们不写任何东西,完全没有使用模板函数,那么它就没有真正存在过。
Print<int>(5);//调用Print使用尖括号指定类型

另一种用法:

#include<iostream>
#include<string>

template<int N>//传入大小N
class Array
{
private:
	int m_Array[N];
public:
	int Getsize() const { return N; }
};

int main()
{
	std::cin.get();
}

当我们调用这个Array时,指定一个Array大小为5,命名为array,这意味将第9和11行的N改为5。若调用array.Getsize然后打印到控制台,按下F5运行。
在这里插入图片描述模板可以包罗一切可以改变的参数:
在这里插入图片描述

三十六、C++的堆与栈内存

我们将讨论C++的两种不同类型的内存:栈和堆。当程序开始的时候它被分成了一堆不同的内存区域(包括栈和堆),在应用程序启动后,操作系统将整个程序加载到内存并分配一堆物理RAM以便程序可以运行。堆和栈是RAM中实际存在的两个区域,栈通常是一个预定义大小(2兆字节左右)的内存区域,堆也是一个预定义默认值的区域,但是它可以随着应用程序的进行而改变

它们的不同之处在于如何为我们分配内存???
主要的区别是使用new关键字来分配内存。

#include<iostream>

struct Vector3
{
	float x, y, z;
};

int main()
{
   //栈分配
	int value = 5;
	int array[5];

  //堆分配
	int* hvalue = new int;
	//这里使用的是new关键字,然而如果使用智能指针,用make_unique或者make_shared等函数,这都是一样的,它会为你调用new。
	*hvalue = 5;
	int* harray = new int[5];
	Vector3* hvector = new Vector3();//圆括号可选
	//需要delete你使用new分配的内存,智能指针可以为你做这个(释放内存)
	delete hvalue;
	delete []harray;
	delete hvector;
	
	std::cin.get();
}
  • 栈上分配的内存都挨着的,因为就是栈顶指针移动这么多字节;
  • 栈只是把东西堆在一起,所以速度很快;
  • 对于堆分配,则不会是紧挨着的;
  • 在堆上分配内存是一堆的事情,而在栈上分配内存,就像一条CPU指令

三十七、C++宏

#include<iostream>

//发生在编译器的预处理阶段
#define WAIT std::cin.get() 

int main()
{
	WAIT;
}
#include<iostream>

#define PR_DEBUG 0//需要添加到vs预处理器中

#if PR_DEBUG == 1//在Debug和Release模式下选中不同代码有效
#define LOG(x) std::cout << x << std::endl
#else
#define LOG(x)
#endif

int main()
{
	LOG("Hello");
	std::cin.get();
}

还可以使用反斜杠来写多行的宏,因为宏必须在同一行。

#include<iostream>

#define MAIN int main() \
{\
   std::cin.get();\
}

MAIN

三十八、auto关键字

auto可以让C++自动推导出数据的类型,不管是在创建还是初始化变量数据时,或是将一个变量对另一个变量进行赋值。

要注意的就是,要传引用时要在后面加&,比如const auto& ...
如下代码,用迭代器打印vector中的所有元素。

#include<iostream>
#include<string>
#include<vector>

 char* GetName()
{
	return "Cherno";
}

int main()
{
	std::vector<std::string>strings;
	strings.push_back("Apple");
	strings.push_back("Orange");

	for (std::vector<std::string>::iterator it = strings.begin();it != strings.end();it++)
	{
		std::cout << *it << std::endl;
	}

	std::cin.get();
}

std::vectorstd::string::iterator是一个巨大的类型,我们可以写成auto让代码更具可读性。
在这里插入图片描述
若有DeviceManager和Device类,我们有一个从string到vector<Device*>map(映射),变量名叫m_Devices。

#include<iostream>
#include<string>
#include<vector>
#include<unordered_map>

 char* GetName()
{
	return "Cherno";
}
 
 class Device{};

 class DeviceManager
 {
 private:
	 std::unordered_map<std::string, std::vector<Device*>>m_Devices;
 public:
	 const std::unordered_map<std::string, std::vector<Device*>>& GetDevices()const
	 {
		 return m_Devices;
	 }
 };
int main()
{
	std::vector<std::string>strings;
	strings.push_back("Apple");
	strings.push_back("Orange");

	for (auto it = strings.begin();it != strings.end();it++)
	{
		std::cout << *it << std::endl;
	}

	DeviceManager dm;
	const std::unordered_map<std::string, std::vector<Device*>>&   //类型
		devices=dm.GetDevices();
	std::cin.get();
}

我们可以用typedef或者using取个别名,来简化代码。

int main()
{
	std::vector<std::string>strings;
	strings.push_back("Apple");
	strings.push_back("Orange");

	for (auto it = strings.begin();it != strings.end();it++)
	{
		std::cout << *it << std::endl;
	}
   
	using DeviceMap = std::unordered_map<std::string, std::vector<Device*>>;

	DeviceManager dm;
	const DeviceMap&   
		devices=dm.GetDevices();
	std::cin.get();
}

也可以使用auto;

int main()
{
	std::vector<std::string>strings;
	strings.push_back("Apple");
	strings.push_back("Orange");

	for (auto it = strings.begin();it != strings.end();it++)
	{
		std::cout << *it << std::endl;
	}

	DeviceManager dm;
	const auto&  devices=dm.GetDevices();//若去掉const和&,将会制造一次复制操作
	std::cin.get();
}

三十九、C++静态数组(std array)

静态数组是指不增长的数组。如下代码,std::array中,第一个参数是array内数据的类型,第二个参数是array里面有多少个元素。

#include<iostream>
#include<array>

int main()
{
	std::array<int, 5> data;
	data.size()
	data[0] = 2;
	data[4] = 1;
	std::cin.get();
}

使用std::array的好处是可以访问它的大小,它是一个类,并且它是在栈上创建的。

#include<iostream>
#include<array>

void PrintArray(const std::array<int, 5>& data)
{
	for (int i = 0;i < data.size();i++)
	{
		std::cout << data[i] << std::endl;
	}
}
int main()
{
	std::array<int, 5> data;
	data[0] = 0;
	data[1] = 1;
	data[2] = 2;
	data[3] = 3;
	data[4] = 4;
	PrintArray(data);
	std::cin.get();
}
//如何传入一个标准数组作为参数,但不知道数组的大小?
#include<iostream>
#include<array>

template<typename T>
void PrintArray(const T& data)
{
	for (int i = 0;i < data.size();i++)
	{
		std::cout << data[i] << std::endl;
	}
}

int main()
{
	std::array<int, 5> data;
	data[0] = 0;
	data[1] = 1;
	data[2] = 2;
	data[3] = 3;
	data[4] = 4;
	PrintArray(data);
	std::cin.get();
}

四十、 C++的函数指针

原始风格的函数指针,来自于C语言。

函数指针,是将一个函数赋值给一个变量的方法。

#include <iostream>
#include <string>

void foo(std::string testStr)
{
	std::cout << testStr << std::endl;
}


int main()
{
	std::string testStr = "Hello!";

	// 第一种,auto
	auto foo1 = foo;
	foo1(testStr);

	// 第二种,函数指针变量
	void(*FuncPtrVariable)(std::string);
	FuncPtrVariable = foo;
	FuncPtrVariable(testStr);

	// 第三种,改变第二种的形式,表现的更自然
	typedef void(*FuncPtrType)(std::string);
	FuncPtrType FuncPtrTest = foo;
	FuncPtrTest("Test");

	std::cin.get();
}

一个更实际一点的例子:这相当于把func重命名为PrintValue,然后调用函数;
在这里插入图片描述
还可以用lambda函数:
在这里插入图片描述

四十一、C++的lambda函数

lambda本质上是我们定义一种叫做匿名函数的方式,我们用这种方式创建函数不需要实际创建一个函数,像是一个快速的一次性函数。lambda是我们不需要通过函数定义就可以定义一个函数的方法。

#include<iostream>
#include<vector>

void ForEach(const std::vector<int>& values, void(*func)(int))
{
	for (int value : values)
		func(value);
}

int main()
{
	std::vector<int> values = { 1,5,4,2,3 };

	ForEach(values, [](int value) {std::cout << " Value:" << value << std::endl;}); //lambda
	
	std::cin.get();
}

这里的函数指针定义了lambda需要做成什么样子,我们知道它会返回void(空),也知道它会接受一个int参数。这就是为什么这个函数什么也没有返回,它只是打印了一行文本。然后我们定义了int value参数,因为前面的函数指针需要一个int参数。

可以把lambda赋值给一个auto类型变量,然后将lambda变量传入函数。

#include<iostream>
#include<vector>

void ForEach(const std::vector<int>& values, void(*func)(int))
{
	for (int value : values)
		func(value);
}

int main()
{
	std::vector<int> values = { 1,5,4,2,3 };
	
	auto lambda = [](int value) {std::cout << " Value:" << value << std::endl;};//hear

	ForEach(values, lambda);
	
	std::cin.get();
}

若想把外部变量a放到lambda函数内部的指令中,我们可以值传递或者可以通过引用传递。[]就代表如何传递变量,若值传递则写上=,引用传递写上&,或者单独写上变量a。

auto lambda = [=](int value) {std::cout << " Value:" << a << std::endl;};//值传递
auto lambda = [&](int value) {std::cout << " Value:" << a << std::endl;};//引用
auto lambda = [a](int value) {std::cout << " Value:" << a << std::endl;};//值传递
auto lambda = [&a](int value) {std::cout << " Value:" << a << std::endl;};//引用

当我们试图传入某些变量时,不管是通过值还是引用来捕获变量,这里的ForEach都会出错,因为我们正在使用原始函数指针。若转变成std::function,返回void,有一个int参数叫做func就可以了。
在这里插入图片描述我们有一个可选的修饰符mutable它允许函数体修改通过拷贝传递捕获的参数。若我们在lambda中给a赋值会报错,需要写上mutable。
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值