C++及计算机基础知识 - 01

学习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 指针名。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值