首页 > 编程语言 >C++14指北:花里胡哨的C++

C++14指北:花里胡哨的C++

时间:2023-07-17 21:36:02浏览次数:41  
标签:指北 std return 14 int auto C++ func include

类型!

在最经典的 C++ 代码中,我们使用类似 类型名 变量名 = 表达式; 的形式声明并初始化变量,例如

int x = 1;
int y = x;

在上面代码中,我们知道 y 理应与 x 的类型相同,但是在上面代码中,如果我们后来把 x 的类型修改为 int64_t,而忘记对应地修改 y 的类型,则可能导致灾难性的后果。对此,我们最简单的做法是使用类型别名。在 C++ 中,可以使用 using 声明类型别名:

using my_int = int;
my_int x = 1;
my_int y = x;

我们也可以使用 decltype 说明符,这是在 C++11 中添加的新关键字:

int x = 1;
decltype(x) y = x;

decltype 关键字接受一个表达式,得到该表达式的类型。与 sizeof 类似,decltype 的操作数仅作为类型推导使用,并不会在运行时真正被求值。

[-] 验证 decltype 不会进行求值

考虑下面代码:

#include <cstdio>

int func() {
    std::puts("called!");
    return 0;
}

int main() {
    decltype(func()) a = 1;
    return 0;
}

执行后无输出,说明 decltype 的操作数不会在运行时进行求值。

[-] decltype 可以保留 void、指针、值类型和类型限定符

[+] 查看类型推导结果
我们可以使用声明但未定义的模板函数来查看类型推导结果:

template<typename T>
void func();

该函数会阻止程序正确链接,链接器会按顺序汇报所缺失的函数特化,根据其报错信息即可得到 T 的类型。

考虑如下代码:

#include <utility>

template<typename T>
void func();

void void_func();

int main() {
	int y;
	int &y2 = y;
	int *y3;
	const int * volatile *y4;
	func<decltype(y)>();
	func<decltype(std::move(y))>();
	func<decltype(y2)>();
	func<decltype(y3)>();
	func<decltype(y4)>();
	return 0;
}

链接时,你会得到类似于如下的报错信息:

a.cpp:(.text+0x16): undefined reference to `void func<int>()'
a.cpp:(.text+0x1b): undefined reference to `void func<int&&>()'
a.cpp:(.text+0x20): undefined reference to `void func<int&>()'
a.cpp:(.text+0x25): undefined reference to `void func<int*>()'
a.cpp:(.text+0x2a): undefined reference to `void func<int const* volatile*>()'
a.cpp:(.text+0x2f): undefined reference to `void func<void>()'
collect2: error: ld returned 1 exit status

这说明,decltype 推导可以正确保留 void、指针、引用和类型限定符(constvolatile)。

值得注意的是,被括号包裹的标识符表达式或类成员访问表达式将被推导为左值:

template<typename T>
void func();

int main() {
	int y0 = 1;
	func<decltype(y0)>();
	func<decltype((y0))>();
	return 0;
}

这将产生类似于如下的链接错误:

a.cpp:(.text+0x15): undefined reference to `void func<int>()'
a.cpp:(.text+0x1a): undefined reference to `void func<int&>()'
collect2: error: ld returned 1 exit status

同时使用类型别名和 decltype 推导,我们可以把一些较长的类型名称存储下来,例如:

std::vector<int> a;
using It = decltype(a.begin());

[-] 不经过构造函数直接使用成员函数

std::declval 可以将任意类型 T 转换成引用类型,使得在 decltype 的操作数中不必经过构造函数就能使用成员函数。考虑下面代码,由于结构体 A 没有构造函数,于是不能通过编译:

struct A {
	A() = delete;
	int foo();
};

int main() {
	decltype(A().foo()) x;
	return 0;
}

使用 std::declval 即可解决这个问题:

#include <utility>

struct A {
	A() = delete;
	int foo();
};

int main() {
	decltype(std::declval<A>().foo()) x;
	return 0;
}

当然,许多时候 decltype 也是不方便的,大部分时候所需的类型是与初始化表达式一致的,而使用 decltype 会导致大量重复,考虑下面代码:

int x = 1;
decltype(func(x) * 2LL) y = func(x) * 2LL;

这时,我们就可以使用大名鼎鼎的占位类型说明符 auto 来简化代码:

int x = 1;
auto y = func(x) * 2LL;

自 C++14 起,函数的返回值(如果可以自动推导)也可以是 auto

auto add(int x, int y) {
	return x + y;
}

[-] 不能进行返回类型推导的几种常见情况

函数在形式上有多个 return,且这些 return 返回的类型不相同(即使可以隐式类型转换):

auto func(int x) {
	if (x) return 1LL;
	return 0;
}

函数返回初始化列表:

auto func() {
	return {1, 2};
}

要求 auto*,但是返回值不是指针:

auto* func(int x) {
	return x;
}

在递归前不存在可推导的返回语句:

auto func(int x) {
 return x == 0 ? 0 : func(x - 1);
}

作为对比,下面写法是可以的:

auto func(int x) {
	if (x == 0) return 0;
	return func(x - 1);
}

虚函数不能使用返回类型推导:

struct F {
	virtual auto f() {
		return 2;
	}
};

占位符 auto 本身会丢失类型限定符,因此占位符 auto 可伴随如 const& 这样的修饰符,它们参与类型推导。考虑下面代码:

template<typename T>
void func();

int main() {
	int y0 = 1;
	const int &y1 = y0;
	auto y2 = y1;
	func<decltype(y1)>();
	func<decltype(y2)>();
	return 0;
}

链接时得到的报错信息类似于:

a.cpp:(.text+0x26): undefined reference to `void func<int const&>()'
a.cpp:(.text+0x2b): undefined reference to `void func<int>()'
collect2: error: ld returned 1 exit status

使用在 C++14 中加入的 decltype(auto) 即可避免这个问题,decltype(auto) 会完全忠实于初始化表达式右侧的类型,如同将其带入 decltype 说明符一般:

template<typename T>
void func();

int main() {
	int y0 = 1;
	const int &y1 = y0;
	decltype(auto) y2 = y1;
	func<decltype(y2)>(); // undefined reference to `void func<int const&>()'
	return 0;
}

迭代器!

迭代器是一种广义化的指针,它使得 C++ 程序可以通过统一的方式处理不同的数据结构。迭代器库提供了迭代器的定义,同时还提供了迭代器特征、适配器及相关的工具函数。

因为迭代器是指针的抽象,所以它们的语义是 C++ 的指针的大多数语义的泛化。这确保指针能够用于所有接受迭代器的函数模板。

最基本的迭代器用法看起来是这样的:

#include <vector>
#include <cstdio>
 
int main() {
	std::vector<int> a{1, 2, 3, 4};
	for (auto it = a.begin(); it < a.end(); ++it) std::printf("%d\n", *it);
	return 0;
}

我们的代码并不需要修改这个 std::vector,因此可以使用不可变迭代器,这在需要遍历的数组是不可变时格外有用:

#include <vector>
#include <cstdio>
 
int main() {
	const std::vector<int> a{1, 2, 3, 4};
	for (auto it = a.cbegin(); it < a.cend(); ++it) std::printf("%d\n", *it);
	return 0;
}

使用范围 for 循环的语法糖,上面代码可以改写成这样:

#include <vector>
#include <cstdio>
 
int main() {
	std::vector<int> a{1, 2, 3, 4};
	for (int x : a) std::printf("%d\n", x);
	return 0;
}

如果需要使用范围 for 循环的语法糖的同时强调其不可变性,则可以使用 std::as_const

#include <vector>
#include <cstdio>

// std::as_const 需要 C++17,下面是一个可用的低版本替代实现
// template<typename T> struct add_const_t { using type = const T; };
// template<typename T> typename add_const_t<T>::type& as_const(T& t) noexcept { return t; }
// template<typename T> void as_const(const T&&) = delete;

using std::as_const;

int main() {
	std::vector<int> a{1, 2, 3, 4};
	for (int x : as_const(a)) std::printf("%d\n", x);
	return 0;
}

如果我们想要反向遍历容器,可以使用反向迭代器(可以使用 rcbegin()rcend 表示反向不可变迭代器):

#include <vector>
#include <cstdio>
 
int main() {
	std::vector<int> a{1, 2, 3, 4};
	for (auto it = a.rbegin(); it < a.rend(); ++it) std::printf("%d\n", *it);
	return 0;
}

通过上面初步认识,我们意识到迭代器和指针很相像。但是其比指针更加灵活,例如我们可以用迭代器遍历内部是树形结构而非顺序结构的 std::set

#include <set>
#include <cstdio>

int main() {
	std::set<int> a{1, 2, 3, 4};
	for (int x : a) std::printf("%d\n", x);
	return 0;
}

std::vector 允许随机访问,因此写出 a.begin() + 5 是合理的,时间复杂度为 \(O(1)\),但是 std::set 并不支持这一点,它只支持迭代器自增和自减,且时间复杂度为 \(O(\log n)\)。这说明,迭代器有不同的类型,有些可以支持更多的功能。

标准库中 std::advancestd::distancestd::nextstd::prev 几个函数提供了对迭代器的基本操作。具体而言,

  • std::advance:接受一个迭代器 it 和一个距离 d(可为负值)作为参数,将增加给定的迭代器 itd 个元素的步长;
  • std::distance:接受两个迭代器 firstlast 作为参数,返回从 firstlast 的路程(若 last 不可从 first 通过若干次自增 first 抵达,则行为未定义);
  • std::next:接受一个迭代器 it 和一个距离 n (默认值为 \(1\))作为参数,返回迭代器 it 的第 n 个后继;
  • std::prev:接受一个迭代器 it 和一个距离 n (默认值为 \(1\))作为参数,返回迭代器 it 的第 n 个前驱。

标准库中有大量的函数接受迭代器作为参数,

除了用于访问元素的迭代器,一些容器还支持插入迭代器 std::inserter_iterator,下面是一个使用 std::back_inserter 的例子(需要容器支持 push_back 操作):

#include <vector>
#include <cstdio>

int main() {
	std::vector<int> a{1, 2, 3, 4};
	auto it = std::back_inserter(a);
	*it = 1;
	*it = 2;
	for (int x : a) {
		printf("%d ", x);
	}
	return 0;
}

上面的代码输出:

1 2 3 4 1 2 

搭配使用需要输出迭代器的函数会很方便:

#include <bits/stdc++.h>

int main() {
	std::vector<int> a{1, 2, 3, 4};
	std::fill_n(std::back_inserter(a), 3, 5);
	for (int x : a) {
		printf("%d ", x);
	}
	return 0;
}

上面的代码输出:

1 2 3 4 5 5 5  

类似的还有 std::front_inserter 用于在容器开头插入(需要支持 push_front)以及 std::inserter 用于在容器的特定位置插入(需要支持 insert),下面例子指出了 std::inserter 可以很好地搭配 std::set

#include <bits/stdc++.h>

int main() {
	std::set<int> a{1, 2, 3, 4};
	std::vector<int> b{7, 8, 9};
	std::copy(b.begin(), b.end(), std::inserter(a, a.end()));
	for (int x : a) {
		printf("%d ", x);
	}
	return 0;
}

上面的代码输出:

1 2 3 4 7 8 9  

输入输出迭代器(std::istream_iteratorstd::ostream_iterator)赋予了将输入输出流当做迭代器的能力:

#include <bits/stdc++.h>

int main() {
	std::istringstream in("5 1 2 3 4 5");
	int n;
	in >> n;
	std::vector<int> a;
	std::copy_n(std::istream_iterator<int>(in), n, std::back_inserter(a));
	std::copy(a.begin(), a.end(), std::ostream_iterator<int>(std::cout, " "));
	return 0;
}

上面的代码输出:

1 2 3 4 5  

也可以将上面代码的 in 改为 std::cin,这将改为由标准输入流输入数据。

函数!

函数作为在内存中的代码片段,我们可以取其地址存放在变量中,这个过程是隐含的:

#include <cstdio>

int add(int x, int y) {
	return x + y;
}

int main() {
	using add_type = int (*)(int, int);
	add_type a = add; // 隐式取址,等价于 &add
	std::printf("%d\n", a(1, 2));
	return 0;
}

当然,上面代码也可以直接使用 auto 而不必显式地定义 add_type 类型,这里只是为了说明一个函数指针的类型是:

返回类型 (*) (参数类型列表)

一个具有实践意义的例子是 std::sort 的比较函数:

#include <cstdio>
#include <algorithm>

bool cmp(int x, int y) {
	return x > y;
}

int main() {
	std::vector<int> a{1, 2, 3, 4};
	std::sort(a.begin(), a.end(), cmp);
	for (auto x : a) std::printf("%d\n", x);
	return 0;
}

我们给 std::sort 传入作为第三个参数的比较函数就是使用函数指针的方式传入的。当然,你可能知道标准库中的 std::greater,这将大幅简化代码:

#include <cstdio>
#include <functional>
#include <algorithm>

int main() {
	std::vector<int> a{1, 2, 3, 4};
	std::sort(a.begin(), a.end(), std::greater<>());	// C++ 14 允许省略可推导的模板参数
														// 在此之前则需要在尖括号中指明 int
	for (auto x : a) std::printf("%d\n", x);
	return 0;
}

那么 std::greater<>() 到底是什么?事实上,std::greater 是一个仿函数,他本质上是一个实现了 operator() 的结构体:

#include <cstdio>
#include <algorithm>

struct greater {
	bool operator()(int x, int y) {
		return x > y;
	}
};

int main() {
	std::vector<int> a{1, 2, 3, 4};
	std::sort(a.begin(), a.end(), greater());
	for (auto x : a) std::printf("%d\n", x);
	return 0;
}

在 C++11 中,我们迎来了一个新的语法(糖),使用 Lambda 表达式来创建一个“函数”:

#include <cstdio>
#include <algorithm>

int main() {
	std::vector<int> a{1, 2, 3, 4};
	std::sort(a.begin(), a.end(), [](int x, int y) -> bool {
		return x > y;
	});
	for (auto x : a) std::printf("%d\n", x);
	return 0;
}

你可能意识到了,Lambda 表达式本质上就是创建仿函数结构体的一个语法糖而已,因此即使两个 Lambda 拥有相同的返回类型和参数列表,它们的类型也不是相同的,下面代码验证了这一点:

#include <cstdio>

template<typename T, typename U>
struct is_same_helper {};

template<typename T>
struct is_same_helper<T, T> {
	using type = int;
};

template<typename T, typename U>
void _is_same(char x) {
	std::puts("not same!");
}

template<typename T, typename U>
void _is_same(typename is_same_helper<T, U>::type x) {
	std::puts("same!");
}

template<typename T, typename U>
void is_same() {
	_is_same<T, U>(0);
}

int main() {
	auto func1 = [](int, int) {};
	auto func2 = [](int, int) {};
	is_same<decltype(func1), decltype(func2)>(); // 输出 not same!
}

一般而言,Lambda 表达式可以省略其不需要的部分,下面均为合法的 lambda 表达式:

[] {}
[] (int x) {}
[sum = 0] (int x) mutable noexcept -> void {}

Lambda 表达式最开始的方括号是不可省略的,它被称为 Lambda 表达式的捕获列表,它可以以一个默认捕获符开始:

  • &(以引用隐式捕获被使用的自动变量);
  • =(以复制隐式捕获被使用的自动变量)。

随即是要捕获的变量的列表,若变量名以 & 作为前缀,则其是引用捕获的,否者是值捕获的。这两种捕获模式的区别在于:

  • 引用捕获可用于修改外部变量,而值捕获却不能实现此操作;
  • 引用捕获会反映外部变量的更新,而值捕获不会。

[-] Lambda 默认不可变

Lambda 表达式的值捕获变量默认是不可变的,下面代码将产生编译错误:

int main() {
	int x = 1;
	auto func1 = [x] {
		x = 2;
	};
	return 0;
}

如果将 Lambda 修饰为 mutable,则允许修改这些变量的副本:

#include <cstdio>

int main() {
	int x = 1;
	auto func1 = [x]() mutable {
		x = 2;
		std::printf("%d\n", x);
	};
	func1();
	std::printf("%d\n", x);
	return 0;
}

上面代码输出为:

2
1

[-] Lambda 与悬空引用

Lambda 表达式并不会延长所引用捕获的变量的声明周期:

#include <cstdio>

auto make() {
	int x = 1;
	return [&x](int y) {
		return x = x + y;
	};
}

int main() {
	auto func = make();
	printf("%d\n", func(1));
	return 0;
}

上面代码在 make 函数返回后,其中的 x 生命周期结束,但是其返回的 func 还保有对 x 的引用,这时这个引用变为悬空引用,调用 func 的行为是未定义的。

一个值得注意的例外是,如果 Lambda 使用了以引用捕获的引用,那么它使用原引用所指代的对象,而非被捕获的引用自身,因此下面代码是可行的:

> #include <cstdio>
 
auto make_function(int& x) {
    return [&]{ std::printf("%d\n", x); };
}
 
int main() {
    int i = 3;
    auto f = make_function(i); // f 中对 x 的使用直接绑定到 i
    i = 5;
    f(); // 输出 5
}

容易看出,Lambda 表达式的捕获列表,本质上是仿函数结构体中的成员变量,例如下面 Lambda 表达式:

#include <cstdio>

int main() {
	int x = 1, y = 2;
	auto func = [&x, y] {
		x += y;
	};
	func();
	std::printf("%d\n", x); // 输出 3
	return 0;
}

可以使用仿函数结构体重写为:

#include <cstdio>

int main() {
	int x = 1, y = 2;
	struct _lambda_func {
		int &x;
		const int y;
		_lambda_func(int &x, int y) : x(x), y(y) {}
		void operator()() {
			x += y;
		}
	} func(x, y);

	func();
	std::printf("%d\n", x); // 输出 3
	return 0;
}

在 C++14 后,我们可以进行带初始化器的捕获,即在捕获子句中引入和初始化新变量,而无需将这些变量声明于 Lambda 函数的封闭范围内。初始化可以任何任意表达式表示,它的行为如同它声明并显式捕获一个以类型 auto 声明的变量,该变量的声明区是 Lambda 表达式体,但:

  • 如果以复制捕获,那么闭包对象的非静态数据成员是指代这个 auto 变量的另一种方式。
  • 如果以引用捕获,那么引用变量的生存期在闭包对象的生存期结束时结束。

下面代码将输出 1

#include <cstdio>

int main() {
	int x = 1;
	auto func = [x = x]() {};
	std::printf("%d\n", func.x);
}

在 C++14 中,如果参数类型是泛型,则可以使用 auto 关键字作为类型说明符。 此关键字将告知编译器将函数调用运算符创建为模板。 参数列表中的每个 auto 实例等效于一个不同的类型参数。例如:

#include <cstdio>
#include <algorithm>

int main() {
	std::vector<int> a{1, 2, 3, 4};
	std::sort(a.begin(), a.end(), [](auto x, auto y) {
		return x > y;
	});
	for (auto x : a) std::printf("%d\n", x);
	return 0;
}

下面除非特殊说明,我们提及“函数”这个词实际上是指任何可调用的东西,包括函数指针,仿函数和 Lambda。为了方便起见,我们把返回类型为 bool 的函数称为谓词,只接受一个参数的函数称为一元函数,只接受一个参数的谓词被称为一元谓词。

下面介绍几个需要一元谓词的标准库函数:

std::all_ofstd::any_ofstd::none_ofstd::count_ifstd::find_ifstd::find_if_not 六个函数接受两个迭代器 firstlast 以及一个一元谓词 p 作为参数,它们的功能分别是:

  • std::all_of:检查一元谓词 p 是否对范围 [first, last) 中所有元素返回 true
  • std::any_of:检查一元谓词 p 是否对范围 [first, last) 中至少一个元素返回 true
  • std::none_of:检查一元谓词 p 是否不对范围 [first, last) 中任何元素返回 true
  • std::count_if:计数一元谓词 p 对范围 [first, last) 中多少个元素返回 true
  • std::find_if:返回一元谓词 p 对范围 [first, last) 中首个返回 true 的元素的迭代器,未找到则返回 last
  • std::find_if_not:返回一元谓词 p 对范围 [first, last) 中首个不返回 true 的元素的迭代器,未找到则返回 last

std::for_each 函数接受两个迭代器 firstlast 以及一个一元函数 f 作为参数,它按顺序应用给定的函数对象 f 到解引用范围 [first,last) 中每个迭代器,返回 f 的右值引用。特别地,如果迭代器类型是可变的,那么 f 可以通过解引用后的迭代器修改范围的元素。忽略 f 返回的结果。

我们可以使用 std::for_each 来将一个函数应用到一个序列的每个元素上,下面代码会把 a 中的每个元素翻倍:

#include <cstdio>
#include <vector>
#include <algorithm>
 
int main() {
	std::vector<int> a{1, 2, 3, 4};
	std::for_each(a.begin(), a.end(), [](int &x) {
		x *= 2;
	});
	for (int x : a) std::printf("%d\n", x);
	return 0;
}

由于 std::for_each 返回 f 的右值引用,结合带有有初始化器的捕获的 Lambda,可以完成一些复杂的统计,下面代码计算 a 中元素的平方和:

#include <cstdio>
#include <vector>
#include <algorithm>
 
int main() {
	std::vector<int> a{1, 2, 3, 4};
	std::printf("%d\n", std::for_each(a.begin(), a.end(), [sum = 0](int &x) mutable {
		sum += x * x;
	}).sum);
	return 0;
}

std::transform 函数接受三个迭代器 s_firsts_lastd_first 以及一个一元函数 f 作为参数,应用给定的函数到范围 [s_first, s_last) 并将结果存储到始于 d_first 的范围。下面代码将字符串 a 转化为大写字母:

#include <bits/stdc++.h>
 
int main() {
	std::string a{"hello!"};
	std::transform(a.cbegin(), a.cend(), a.begin(), [](auto x) { return std::toupper(x); });
	std::printf("%s\n", a.c_str());
	return 0;
}

标签:指北,std,return,14,int,auto,C++,func,include
From: https://www.cnblogs.com/szdytom/p/fancy-cpp.html

相关文章

  • beginnersbook C++ 教程·翻译完成 | ApacheCN
    译者:飞龙协议:CCBY-NC-SA4.0基础HelloWorld-第一个C++程序C++中的变量C++中的数据类型C++中的运算符控制语句C++中的if语句C++中的switch-case语句C++中的for循环C++中的while循环C++中的do-while循环C++中的continue语句C++中的break语句C++中的goto语句函数C++......
  • C/C++八大排序
    排序排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存。按照难易程度排序,八大排序算法可以从简单到复杂依次排列如下:冒泡排序(BubbleSort)选择排序(SelectionSort)插入排序(Inser......
  • c++ size_t类型
    在c++中,sizeof()返回的是size_t类型的数据,size_t可以理解为unsignedint(或者unsignedlong),作用是提高可移植性,这个类型在各个平台上都可以使用。64位下char1字节shortint2字节int4字节(unsignedint)long4字节(unsignedlong)longlong8字节(unsignedlonglong)指针统......
  • C++的多态性
    C++面向对象中的多态性是指同一种类型的对象在不同的情况下表现出不同的行为。所谓消息是指对类成员函数的调用,不同的行为是指不同的实现,也就是调用了不同的函数,从广义上说,多态性是指一段程序能够处理多种类型对象的能力。在C++中,虚函数是指在基类中声明的函数,在派生类中可以被重......
  • C++笔记(2)——函数
    六.函数6.1函数基础一个典型的函数(function)定义包括:返回类型(returntype)、函数名字,由0或多个形参(parameter)组成的列表以及函数体。我们通过调用运算符来执行函数,形式为"()"。函数调用完成两项工作:一是用实参初始化函数对应的形参,二是将控制权转移给被调用函数。此时,主调......
  • CVE-2023-1454注入分析复现
    简介JeecgBoot的代码生成器是一种可以帮助开发者快速构建企业级应用的工具,它可以通过一键生成前后端代码,无需写任何代码,让开发者更多关注业务逻辑。影响版本Jeecg-Boot<=3.5.1环境搭建idea+后端源码:https://github.com/jeecgboot/jeecg-boot/archive/refs/tags/v3.5.0.zip......
  • 【14.0】Django框架之CBV添加装饰器的三种方式
    【一】给类方法加装饰器指名道姓的装--放在方法上面路由path('login_view/',views.MyLogin.as_view()),需要导入一个模块fromdjango.utils.decoratorsimportmethod_decorator视图fromdjango.viewsimportViewfromdjango.utils.decoratorsimportmetho......
  • C++中的异常处理详细说明
    看代码的过程中,经常看到try{}catch{}语句块,而且还经常性的看到这样的语句try{//保护代码}catch(...){//处理任何异常的代码}刚开始我对catch(...)非常困惑,因为C#中并没有这样的用法.所以,特意来了解学习一下C++中的异常处理方式通常来说,try{}catch{}块中,try......
  • C++ 异常处理
     异常是程序在执行期间产生的问题。C++异常是指在程序运行时发生的特殊情况,比如尝试除以零的操作。异常提供了一种转移程序控制权的方式。C++异常处理涉及到三个关键字:try、catch、throw。throw: 当问题出现时,程序会抛出一个异常。这是通过使用 throw 关键字来完成的。......
  • 常用语言的线程模型(Java、go、C++、python3)
    背景知识软件是如何驱动硬件的?硬件是需要相关的驱动程序才能执行,而驱动程序是安装在操作系统内核中。如果写了一个程序A,A程序想操作硬件工作,首先需要进行系统调用,由内核去找对应的驱动程序驱使硬件工作。而驱动程序怎么让硬件工作的呢?驱动程序作为硬件和操作系统之间的媒介,可以......