学习Vulkan的过程中,跟着教程写了两千行代码,都是C++的。可是工作后C++本来就没用多少,原来的一点基础也都忘得差不多了,所以还是要对一些基础知识整理下,方便以后翻看。
1 const
比如Vulkan中的一段代码:
std::vector<VkPhysicalDevice> devices(deviceCount);
vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data());
for (const auto& device : devices) {
if (isDeviceSuitable(device)) {
physicalDevice = device;
msaaSamples = getMaxUsableSampleCount();
break;
}
}
auto是自动推断变量类型的,和C#中的var差不多,这里不多解释。那么加const是为什么?因为我觉得const是定义常量用的,这里看着device是在循环中要变的,为什么加了const?
先用简单的来替换下,把类型当成int。引出一个问题,const int&和int const&有区别吗?根据查到的资料,二者是写法上的区别,使用起来没有任何区别。且有人解释了,引用一旦初始化了就不能改变了,相当于带了const性质。那么这一句解释该如何理解呢?参考:
https://blog.csdn.net/abc785442154abc/article/details/48011413
这篇博客就是解释“引用被初始化后,不能再指向其他对象”这句话的。C++中使用引用的规则:
引用被创建的同时必须初始化(指针可以任何时候初始化);
不能有NULL引用,引用必须与合法的存储单元关联(指针可以是NULL);
一旦引用被初始化,就不能改变引用关系,即不能再指向其他对象(指针可以随时改变所指对象)。
看一段测试代码:
int main()
{
std::string str1 = "a";
std::string str3 = "b";
std::string& str2 = str1;
str2 = str3;
}
运行后:
str2 = str3之后,str2的地址还是str1的地址。如果你想把这句改为&str2 = str3那么编译器会报错说左边必须是可修改的值。所以说,“引用初始化后,不能再指向其他对象”就是说str2作为str1的别名,已经映射到0x00affdfc了,不能再映射到别的地址。
这里还没牵扯到const呢。对于const,最简单的理解就是它指定的值是不可变的:
const int a = 1;
a = 2;
比如这个例子,编译会报错的。带了&后相当于别名,那么:
int a = 1;
const int& b = a;
b = 2;
这里b就是a,想要重新赋值为2也会报错。但是呢,因为a没用const标记,所以修改a是可以的:
int a = 1;
const int& b = a;
a = 2;
a = 3;
这里b也会变成最后的3,实际上最开始a赋值为1的初始化也可以不用,const int& b = a;也不会报错。这个操作相当于你单独给引用设置了门槛,它本身不能被修改,只能被动接受原始引用者(这里的a对象)的修改。
回到前面的Vulkan代码,对于之前的疑问,可以这样解释:
不是device这个被标记了const的引用要变,因为我们前面解释了,引用初始化后就不能再指向其他对象了,这里要变的只是device的内容而已。
这里面还提到了for循环带冒号进行循环的操作,就和python的for循环对列表遍历一样,C#中类似也有foreach。这是C++11中的新添加的语法,一般也是在里面写const auto& entry这样的形式,便于迭代。
2 参数传递const int*与const int&等
比如这段Vulkan代码:
VkResult CreateDebugUtilsMessengerEXT(
VkInstance instance,
const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo,
const VkAllocationCallbacks* pAllocator,
VkDebugUtilsMessengerEXT* pDebugMessenger) {
auto func = (PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT");
if (func != nullptr) {
return func(instance, pCreateInfo, pAllocator, pDebugMessenger);
} else {
return VK_ERROR_EXTENSION_NOT_PRESENT;
}
}
VkFormat findSupportedFormat(const std::vector<VkFormat>& candidates, VkImageTiling tiling,
VkFormatFeatureFlags features) {
for (VkFormat format : candidates) {
VkFormatProperties props;
vkGetPhysicalDeviceFormatProperties(physicalDevice, format, &props);
if (tiling == VK_IMAGE_TILING_LINEAR &&
(props.linearTilingFeatures & features) == features) {
return format;
} else if (tiling == VK_IMAGE_TILING_OPTIMAL &&
(props.optimalTilingFeatures & features) == features) {
return format;
}
}
throw std::runtime_error("failed to find supported format!");
}
用到了几种各不相同的参数传递方法。
先复习下C++中的三种参数传递方式,参考:
https://blog.csdn.net/cocohufei/article/details/6143476
分别是:
按值传递(pass by value);
地址传递(pass by pointer);
引用传递(pass by reference);
按值传递是最简单的了,这里不多说。按地址传递会把实参的地址传递给形参,这样形参指针和实参指针指向同一个地址。如下:
#include <pch.h>
#include <iostream>
using namespace std;
void swap(int*, int*);
int main()
{
int a = 3, b = 4;
cout << "a = " << a << ", b = " << b << endl;
swap(&a, &b);
cout << "a = " << a << ", b = " << b << endl;
system("pause");
return 0;
}
void swap(int *x, int *y)
{
int t = *x;
*x = *y;
*y = t;
}
传递的是&a和&b,也就是a和b的地址,用的时候用*提取对应地址的值,然后交换。
如果以引用为参数,则既可以使对形参的操作能改变相应数据,又让函数调用方便自然。引用传递在函数定义时在形参前面加上&,如下:
#include <pch.h>
#include <iostream>
using namespace std;
void swap(int&, int&);
int main()
{
int a = 3, b = 4;
cout << "a = " << a << ", b = " << b << endl;
swap(a, b);
cout << "a = " << a << ", b = " << b << endl;
system("pause");
return 0;
}
void swap(int &x, int &y)
{
int t = x;
x = y;
y = t;
}
这里还没牵扯到const呢。参考:
https://blog.csdn.net/ccblogger/article/details/77752659
当你的参数作为输入参数时,你总不希望你的输入参数被修改,否则可能产生逻辑错误。这时可以在声明函数时在参数前加上const关键字,防止在实现时意外修改函数输入。对于使用你的代码的程序员也可以告诉他们这个参数是输入,而不加const关键字的参数也可能是输出。例如strlen,你可以这样声明:
int strlen(char str);
功能上肯定没有什么问题,但是你想告诉使用该函数的人,参数str是一个输入参数,它指向的数据是不能被修改的,这也是他们期望的。他们希望有一个保证,说该函数不会破坏你的任何数据,声明按如下方式便可让他们放心:
int strlen(const char str);
最后,通过另一篇博客来总结下:
https://blog.csdn.net/luhao19980909/article/details/89932100
回到Vulkan的代码上,VkInstance instance就是按值传递了,const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo就是按地址传递了,这个就和const int& a类似,是const int* a的形式。这个就是常指针了,意思就是这个指针指向对象的内容是不能改动的,不加的话我们是可以修改这个指针指向对象的内容的。比如:
int a = 1;
int c = 2;
int* b = &a;
b = &c;
*b = 3;
这里b可以重新指向c,还能修改它的值,这里c也被修改为了3。但是:
int a = 1;
int c = 2;
const int* b = &a;
b = &c;
*b = 3;
这里b是可以改为指向c的,但是不能修改*b,不过可以修改c=3从而自动让*b的值变成了3,这点就和前面的const &类似了。
好像又回到了const的内容学习上,可以另外参考:
https://www.cnblogs.com/lanjianhappy/p/7298427.html
对const的总结很详细,比如几种常用方法:
常变量:const 类型说明符 变量名;
常引用:const 类型说明符 &引用名;
常对象:类名 const 对象名;
常成员函数: 类名::fun(形参) const;
常数组: 类型说明符 const 数组名[大小];
常指针:const 类型说明符* 指针名 ,类型说明符* const 指针名。