【C++】C++11的新特性(上)

news2024/12/24 0:35:58

引入 

   C++11作为C++标准的一个重要版本,引入了许多令人振奋的新特性,极大地丰富了这门编程语言的功能和表达能力。本章将为您介绍C++11的一些主要变化和改进,为接下来的章节铺垫。

  

文章目录

引入

一、列表初始化

1、1 {} 初始化

1、2 std::initializer_list的介绍

二、声明

2、1 auto关键字的引入

2、2 decltype关键字

2、3 nullptr

三、智能指针

四、范围for

五、STL中一些变化

5、1 新容器

5、2 新接口

六、右值引用和移动语义

6、1 左值引用和右值引用

6、1、1 左值与左值引用 

6、1、2 右值与右值引用

6、2 左值引用与右值引用比较

6、3 右值引用的使用场景与意义

6、3、1 移动构造

6、3、2 移动赋值

七、总结


🙋‍♂️ 作者:@Ggggggtm 🙋‍♂️

👀 专栏:C++ 👀

💥 标题:C++11 💥

❣️ 寄语:与其忙着诉苦,不如低头赶路,奋路前行,终将遇到一番好风景 ❣️ 

一、列表初始化

1、1 {} 初始化

  在C++98中,标准允许使用花括号{}对数组或者结构体元素进行统一的列表初始值设定。比如:

struct Point
{
	int _x;
	int _y;
};
int main()
{
	int array1[] = { 1, 2, 3, 4, 5 };
	int array2[5] = { 0 };
	Point p = { 1, 2 };
	return 0;
}

  在C++11中,引入了列表初始化的概念,允许我们使用花括号来初始化各种类型的对象,使其可用于所有的内置类型和用户自定义的类型,都可以使用统一的初始化语法,从而减少了初始化的歧义性和错误。 使用初始化列表时,可添加等号(=),也可不添加。具体如下:

class Date
{
public:
	Date(int year, int month, int day)
		:_year(year)
		, _month(month)
		, _day(day)
	{
		cout << "Date(int year, int month, int day)" << endl;
	}

private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	int x1 = 1;
	int x2 = { 2 };
	int x3 { 2 };

	// 都是在调用构造函数
	Date d1(2022, 11, 22);
	Date d2 = {2022, 11, 11}; 
	Date d3{ 2022, 11, 11 };
	return 0;
}

  我们发现,C++11列表初始化允许我们使用花括号来初始化各种类型的对象后,可能会在某些情况下带来一些方便,但实际上看起来还是有一点不适应的。

1、2 std::initializer_list的介绍

 std::initializer_list 是 C++11 标准库中引入的一个特殊容器,它用于方便地初始化数据。它允许我们在列表初始化(也称为花括号初始化)的情况下,以逗号分隔的值列表的形式传递一组元素。

  使用 std::initializer_list 可以将一组值作为参数传递给函数或构造函数,从而简化代码的编写,并提高可读性。它的主要作用是用于表示一个不可修改的序列,类似于数组或容器,但没有提供像容器操作那样丰富的接口。

下面是 sstd::initializer_list 的主要特点和用法:

  1. 定义和声明:std::initializer_list 是一个模板类,位于 <initializer_list> 头文件中。可以通过以下语法来声明并初始化一个 std::initializer_list 对象:

    std::initializer_list<T> list = {value1, value2, ...};
  2. 使用范围:std::initializer_list 可以用于任何需要一组值作为参数的上下文中,例如函数参数、构造函数参数等。

  3. 迭代器和大小:std::initializer_list 提供了类似容器的迭代器和大小函数,可以使用 begin()end() 来获取迭代器,size() 来获取序列的大小。

  4. 元素访问:std::initializer_list 并不提供随机访问元素的功能,只能顺序遍历元素。可以使用 std::initializer_list 的迭代器或范围-based for 循环来访问元素。

  具体我们可结合如下实例理解:

int main()
{

	// 调用支持list (initializer_list<value_type> il)类似这样的构造函数
	vector<int> v1 = { 1, 2, 3, 4, 5, 6 };
	vector<int> v2{ 1, 2, 3, 4, 5, 6 };

	list<int> lt1 = { 1, 2, 3, 4, 5, 6 };
	list<int> lt2{ 1, 2, 3, 4, 5, 6 };

	auto x = { 1, 2, 3, 4, 5, 6 };
	cout << typeid(x).name() << endl;

	vector<Date> v4 = { { 2022, 1, 1 }, {2022, 11, 11} };

	string s1 = "11111";

	// 构造
	map<string, string> dict = { { "sort", "排序" }, { "insert", "插入" } };

	// 赋值重载
	initializer_list<pair<const string, string>> kvil = { { "left", "左边" }, { "left", "左边" } };
	dict = kvil;
	return 0;
}

  我们先看一下运行结果:

  同时我们再看一下C++的标准库的介绍文档:

二、声明

2、1 auto关键字的引入

  C++11引入了 auto 关键字,允许变量的类型根据初始化表达式进行自动推导。这种类型推导机制不仅简化了代码书写,还减少了代码中的重复信息,提高了代码的可读性和维护性。

  在C++98中 auto 是一个存储类型的说明符,表明变量是局部自动存储类型,但是局部域中定义局部的变量默认就是自动存储类型,所以 auto 就没什么价值了。C++11中废弃 auto 原来的用法,将其用于实现自动类型推断。这样要求必须进行显示初始化,让编译器将定义对象的类型设置为初始化值的类型。具体我们看如下代码:

int main()
{
	int i = 10;
	auto p = &i;
	auto pf = strcpy;
	cout << typeid(p).name() << endl;  // int *
	cout << typeid(pf).name() << endl; // char * (__cdecl*)(char *,char const *) 函数指针
	map<string, string> dict = { {"sort", "排序"}, {"insert", "插入"} };
	//如下auto的作用体现
	//map<string, string>::iterator it = dict.begin();
	auto it = dict.begin();
	return 0;
}

 auto关键字可以方便地简化代码,尤其是当变量类型较为复杂或使用模板时。它可以更灵活地适应不同的场景,并且减少了编写冗长类型名称的工作。

2、2 decltype关键字

  除了 auto 关键字外,C++11还引入了 decltype 关键字。decltype关键字则用于获取表达式的类型,而不是进行类型推导。它主要用于在编译时获取表达式的静态类型信息,并且保留了const、引用修饰符等特性。

  有些同学可能会感觉 auto 和 decltype 非常的相似。其实他们是有所区别的,具体看如下代码:

int main()
{
	int x = 10;

	// typeid拿到只是类型的字符串,不能用这个再去定义对象什么的
	//typeid(x).name() y = 20;  错误

	// decltype(x) int
	decltype(x) y1 = 20.22;
	auto y2 = 20.22;

	cout << y1 << endl;
	cout << y2 << endl;

	return 0;
}

  运行结果如下图:

2、3 nullptr

  由于C++中NULL被定义成字面量0,这样就可能回带来一些问题,因为0既能指针常量,又能表示整形常量。所以出于清晰和安全的角度考虑,C++11中新增了nullptr,用于表示空指针。

#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif

三、智能指针

  后面会专门对智能指针进行详细解释,此处就不再过多解释。

四、范围for

  C++11引入了范围for循环(Range-based for loop),可以更简洁地遍历容器或者其它可迭代对象的元素。具体使用方式是:“for (type variable : range)”。

  范围for循环的底层原理是通过迭代器(iterator)实现的。迭代器是访问容器内元素的一种方式,通过指向容器中的特定位置来遍历元素。范围for循环会自动遍历整个容器中的元素,并使用迭代器实现循环。

下面是范围for循环使用与底层原理的详解:

  1. 使用方式:
    • 针对容器:对于容器类型(比如vector、list等),范围for循环按顺序遍历容器中的每个元素,将每个元素赋值给变量,直到遍历完所有元素。
    • 针对数组:对于数组类型,范围for循环也能正常工作,类似于按顺序访问数组中的每个元素。
    • 自定义类型:如果要对自定义类型使用范围for循环,需要定义相应的迭代器接口或者提供begin()和end()成员函数,以便定义迭代的开始和结束位置。

示例代码如下:

std::vector<int> vec = {1, 2, 3, 4, 5};
for (int num : vec) {
    std::cout << num << " ";
}
// 输出:1 2 3 4 5

int arr[] = {6, 7, 8, 9, 10};
for (int num : arr) {
    std::cout << num << " ";
}
// 输出:6 7 8 9 10
  1. 底层原理: 范围for循环的底层原理是通过使用迭代器实现的。迭代器是遍历容器或者其它可迭代对象的一种通用方式,提供了对元素的访问和操作。

    范围for循环的执行过程如下:

    • 对于容器类型,编译器会自动调用容器的begin()和end()成员函数获取容器的起始位置和结束位置的迭代器。
    • 对于数组类型,编译器将数组名转换为指向数组首元素的指针作为起始位置,以及指向数组最后一个元素的下一个位置的指针作为结束位置的迭代器。
    • 循环开始时,将起始位置的迭代器赋值给临时变量,然后判断迭代器是否达到结束位置,如果未达到,则执行循环体内的代码。
    • 每次循环迭代时,将迭代器指向的元素赋给循环变量,并使迭代器前进到下一个位置。
    • 直到迭代器达到结束位置,循环停止。

  范围for循环的引入简化了代码编写,使得遍历容器等可迭代对象更加方便和易读。

五、STL中一些变化

5、1 新容器

  C++11STL中引入了一些新的容器,如下图:

  C语言中的数组和C++11中的array是两种不同的数据类型,它们在以下几个方面存在一些对比和区别:

  1. 类型安全性:

    • C语言中的数组没有类型安全性检查,可以存储任意类型的数据。这就意味着您可以将一个元素类型不匹配的值赋给数组,这可能导致不可预测的结果或者错误。
    • C++11中的array是一个模板类,它对数组的类型进行了严格控制。只能存储指定类型的元素,当我们尝试存储不匹配的类型时,会在编译过程中产生错误,提供了更好的类型安全性。
  2. 大小确定性:

    • C语言中的数组在创建时需要显式指定大小,并且在使用时无法动态改变大小。这意味着数组长度是固定的,不能根据需求进行扩展或收缩。
    • C++11中的array也需要在创建时指定大小,但它提供了size()和max_size()等成员函数来获取数组的大小信息。同时,由于C++的特性,您可以使用动态数组(如vector)来代替array,动态数组具备灵活调整大小的能力。
  3. 内存管理:

    • C语言中的数组通过指针实现,其内存管理需要手动进行。数组的声明不会自动分配内存空间,需要使用malloc()或者calloc()等函数进行显式的内存分配和释放操作。
    • C++11中的array作为一个容器类,它在栈上分配内存,并在其作用域结束时自动释放。无需显式调用释放内存函数,这样更方便地管理和避免内存泄漏。
  4. 拷贝行为:

    • C语言中的数组不能直接进行拷贝,只能通过遍历数组元素进行手动赋值或者使用memcpy()函数来实现数组拷贝。
    • C++11中的array可以进行拷贝和移动操作,支持复制构造函数和赋值操作符的使用。

  forward_list 底层就是单链表。重点是unordered_map和unordered_set,其他的大家了解一下即可。

5、2 新接口

  如果我们再细细去看会发现基本每个容器中都增加了一些C++11的方法,但是其实很多都是用得比较少的。比如提供了cbegin和cend方法返回const迭代器等等,但是实际意义不大,因为begin和end也是可以返回const迭代器的,这些都是属于锦上添花的操作。
  实际上C++11更新后,容器中增加的新方法最后用的插入接口函数的右值引用版本:rempalce_back/emplace。下面我们会详细解释这些接口。

六、右值引用和移动语义

6、1 左值引用和右值引用

  传统的C++语法中就有引用的语法,而C++11中新增了的右值引用语法特性,所以从现在开始我们之前学习的引用就叫做左值引用。无论左值引用还是右值引用,都是给对象取别名。

6、1、1 左值与左值引用 

  什么是左值?什么是左值引用?

  左值是一个表示数据的表达式(如变量名或解引用的指针),我们可以获取它的地址+可以对它赋值,左值可以出现赋值符号的左边或右边。定义时const修饰符后的左值,不能给他赋值,但是可以取它的地址。左值引用就是给左值的引用,给左值取别名。

  下面详细解释左值和左值引用:

  1. 左值:

    • 左值可以表示具有名称的变量、对象或数据成员,例如:int a = 10;、int array[10];、std::string str = "Hello";
    • 左值可以出现在赋值语句的左边或右边,例如:a = 20;、int b = a;;
    • 对一个左值进行取地址操作(&)时,可以获取其在内存中的位置;
    • 左值有持久性,意味着它们在表达式执行完后仍然存在。
  2. 左值引用:

    • 左值引用是使用符号“&”声明的引用类型,例如:int& ref = a;;
    • 左值引用可以将一个左值绑定到引用变量上,并通过引用直接访问绑定的左值;
    • 通过修改引用变量的值,也会影响所绑定的左值;
    • 左值引用可以作为函数的参数,使函数能够修改传递给它的变量的值;
    • 引用本身并不占用内存,它只是对绑定的左值进行别名。
int main()
{
	// 以下的p、b、c、*p都是左值
	int* p = new int(0);
	int b = 1;
	const int c = 2;
	// 以下几个是对上面左值的左值引用
	int*& rp = p;
	int& rb = b;
	const int& rc = c;
	int& pvalue = *p;
	return 0;
}

6、1、2 右值与右值引用

  右值也是一个表示数据的表达式,如:字面常量、表达式返回值,函数返回值(这个不能是左值引用返回)等等,右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,右值不能取地址。右值引用就是对右值的引用,给右值取别名。

  右值引用(Rvalue Reference)是一种新的引用类型,用于绑定到右值上。它的语法是在类型前面添加两个连续的“&”符号,例如int&&。右值引用可以延长右值的生命周期,也可以让我们知道一个表达式是右值。

int main()
{
	double x = 1.1, y = 2.2;
	// 以下几个都是常见的右值
	10;
	x + y;
	fmin(x, y);
	// 以下几个都是对右值的右值引用
	int&& rr1 = 10;
	double&& rr2 = x + y;
	double&& rr3 = fmin(x, y);
	// 这里编译会报错:error C2106: “=”: 左操作数必须为左值
	// 10 = 1;
	// x + y = 1;
	// fmin(x, y) = 1;
	return 0;
}

   需要注意的是右值是不能取地址的,但是给右值取别名后,会导致右值被存储到特定位置,且可以取到该位置的地址,也就是说例如:不能取字面量10的地址,但是rr1引用后,可以对rr1取地址,也可以修改rr1。如果不想rr1被修改,可以用const int&& rr1 去引用,是不是感觉很神奇,这个了解一下实际中右值引用的使用场景并不在于此,这个特性也不重要。

int main()
{
	double x = 1.1, y = 2.2;
	int&& rr1 = 10;
	const double&& rr2 = x + y;
	rr1 = 20;
	rr2 = 5.5; // 报错
	return 0;
}

6、2 左值引用与右值引用比较

  左值引用总结:

  • 左值引用只能引用左值,不能引用右值。
  • 但是const左值引用既可引用左值,也可引用右值。

  右值引用总结:

  • 右值引用只能右值,不能引用左值。
  • 但是右值引用可以move以后的左值。

  具体我们可结合如下代码对左值引用和右值引用进行对比理解:

int main()
{
	// 左值引用只能引用左值,不能引用右值。
	int a = 10;
	int& ra1 = a; // ra为a的别名
	//int& ra2 = 10; // 编译失败,因为10是右值
	// const左值引用既可引用左值,也可引用右值。
	const int& ra3 = 10;
	const int& ra4 = a;


	// 右值引用只能右值,不能引用左值。
	int&& r1 = 10;
	// error C2440: “初始化”: 无法从“int”转换为“int &&”
	// message : 无法将左值绑定到右值引用
	int a = 10;
	int&& r2 = a;  // 错误
	// 右值引用可以引用move以后的左值
	int&& r3 = std::move(a);

	return 0;
}

  std::move是一个函数模板,定义在<utility>头文件中。它接受一个对象作为参数,并将其转换为右值引用。调用std::move后,原始对象的值仍然有效,但不能再保证其状态的有效性。std::move用于标记一个对象为可移动的,并将其传递给需要右值引用参数的函数,以实现资源的移动而不是复制。 

6、3 右值引用的使用场景与意义

6、3、1 移动构造

  我们知道,引用就是别名,其价值是为了减少拷贝。但是左值引用可以解决一些问题,同时也会遇到一些特殊情况解决不了拷贝的问题,具体如下:

  1. 做参数:a、减少拷贝,提高效率。b、做输出型参数,减少拷贝。
  2. 做返回值:a、引用返回可减少拷贝,提高效率。b、引用返回,可对返回值进行修改。

  但是并不是任何情况下返回值都可以是引用返回的。当返回值是临时变量(出了函数的作用域就销毁),就不可以作为引用返回。此时拷贝就是必不可少的。如下场景:

  这种情况就是避免不了需要进行拷贝,如下图:

  大部分编译器会对上述情况进行优化,不会产生中间的临时变量。直接使用str拷贝构造ret。那有没有很好的办法解决拷贝这种情况呢? 

  当然,我们也可以选择使用输出型参数,但是并不符合我们的使用习惯。具体如下:

   在C++11中,引入了移动构造。对比拷贝构造具体代码如下:

		// 移动构造
		string(string&& s)
			:_str(nullptr)
			, _size(0)
			, _capacity(0)
		{
			cout << "string(string&& s) -- 资源转移" << endl;
			swap(s);  // 资源转移
		}

        // 拷贝构造
        string(const string& s)
			:_str(nullptr)
		{
			cout << "string(const string& s) -- 拷贝构造(深拷贝)" << endl;

			//string tmp(s._str);
			//swap(s);

			_str = new char[s._capacity+1];
			strcpy(_str, s._str);
			_size = s._size;
			_capacity = s._capacity;
		}

  因为上述的 str 出了作用域就会销毁,我们将它视为将亡值,也就是右值。在既有拷贝构造和移动构造的情况下,编译器会匹配最移动构造。因为编译器会匹配最适合的参数。那么这里就不会采用拷贝构造了,而是选择移动构造。

  大部分编译器也会对上述进行优化。直接将 str 的资源转移到 ret 中。对比拷贝构造,我们能够明显的感觉出来,移动构造并没有进行申请资源,而是直接将将亡值的资源进行转移。移动构造减少了资源的申请和拷贝,提高了效率

6、3、2 移动赋值

  移动赋值与移动构造的原理大同小异。具体情况如下:

  上图的情况必须要进行拷贝构造和拷贝赋值。在C++11中引入移动构造后,同时也引入的移动赋值,对比移动复制和拷贝赋值具体代码如下:

        // 拷贝赋值
		string& operator=(const string& s)
		{
			cout << "string& operator=(string s) -- 拷贝赋值(深拷贝)" << endl;
			string tmp(s);
			swap(tmp);

			return *this;
		}

		// 移动赋值
		string& operator=(string&& s)
		{
			cout << "string& operator=(string s) -- 移动赋值(资源移动)" << endl;
			swap(s);

			return *this;
		}

  同样,这里的 str 会被识别成将亡值(右值),不会再调用拷贝构造和拷贝赋值。会调用移动构造和移动复制。移动构造和移动复制都是对右值的资源进行移动转移,减少了资源的申请和拷贝,从而提高效率。

  移动构造和移动赋值操作相对于拷贝构造和拷贝赋值操作的优点有以下几个方面:

  1. 性能更高:移动操作能够直接将资源(比如堆上的内存)从一个对象转移到另一个对象,而无需进行复制和销毁。这样可以消除额外的内存分配和释放开销,提高程序的性能。

  2. 减少内存拷贝:移动操作通过转移资源的所有权,避免了不必要的内存拷贝过程。对于大型对象或频繁进行内存操作的情况下,移动操作可以显著减少内存拷贝的次数,提高程序的效率。

  3. 资源管理效率:移动操作使得资源管理更加高效。在移动语义中,资源的所有权转移给了目标对象,源对象则不再拥有该资源。这意味着在移动后,源对象不再需要释放或删除资源。这样一来,在一些场景下可以避免重复释放资源或导致资源泄漏的问题。

  4. 容器的性能提升:使用移动构造和移动赋值可以有效提高容器操作(比如动态数组、动态字符串等)的性能。很多标准库容器都提供了移动语义的支持,通过移动操作,可以快速将对象移入或移出容器,而不会进行额外的拷贝操作。

  需要注意的是,移动操作通常适用于临时对象、将要被销毁的对象以及右值引用的情况下,而拷贝操作适用于需要保留原始对象的情况。对于用户自定义的类,为了充分利用移动操作,通常需要显式实现移动构造函数和移动赋值运算符,并确保正确处理资源的转移和释放。

七、总结

  由于C++11更新的重要内且常用容较多,就分为两篇内容进行详解。本篇内容的细节较多,重点在于右值引用和移动语义。下篇文章内容依然较多且为重要。本篇文章的讲解就到这里,感谢阅读ovo~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/948007.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

问道管理:macd参数改良设置?

MACD目标是一种广泛应用的技能剖析目标&#xff0c;其特色在于能够同时反映趋势和动量改变。可是&#xff0c;很多买卖者感到困惑的是&#xff0c;怎么设置MACD的参数才能更好地指导买卖。 1. MACD目标参数解析 MACD目标是依据两个移动平均线的穿插来判别商场的趋势与动量改变…

解压jar包并导入库环境

背景 因为各种历史原因&#xff0c;当初的maven依赖环境已下载不了了&#xff0c;所以需要从生产环境的jar包里&#xff0c;获取库环境来本地运行。 但是网上很多方法都是用mvn install命令&#xff0c;一个个jar包导入的&#xff0c;不符合我的需求&#xff08;需要导入280多…

Linux 虚拟机同步时间crontab以及crond详解

目录 一 Linux 虚拟机同步时间设置 1. 检查是否安装cron服务&#xff08;即时间同步器&#xff09; 2. 下载时间同步器 3. 编辑crontab 内容 4. 同步更新电脑网络时间 5.设置 reload 6. 查看 crond 状态 二 crond 详解 1. 启动/关闭cron服务 2. crontab命令格式 3. …

VK1616是LED显示控制驱动电路/LED驱动IC、数显驱动芯片、数码管驱动芯片

产品品牌&#xff1a;永嘉微电/VINKA 产品型号&#xff1a;VK1616 封装形式&#xff1a;SOP16 产品年份&#xff1a;新年份 概述&#xff1a;VK1616是一种数码管或点阵LED驱动控制专用芯片&#xff0c;内部集成有3线串行接口、数据锁存器、LED 驱动等电路。SEG脚接LED阳极&a…

mysql存储过程之遍历设置表中某些字段值

缘起 ​ mysql数据库中有用到分表&#xff0c;大概300张表&#xff0c;都是以geo_data_xxxxxx来命名&#xff0c;之前测试过程中&#xff0c;有给300张表中的一个字段设置过值&#xff0c;但是想重新生成一遍数据。那么后面是做了一个数据清洗&#xff0c;给300张表is_turnout…

Prometheus监控(三)架构

文章目录 Prometheus架构图Prometheus生态圈组件Prometheus Serverclient librariesPushgatewayexporterAlartmanager Prometheus架构理解存储计算层采集层应用层 Prometheus架构图 Prometheus生态圈组件 Prometheus Server 主服务器&#xff0c;负责收集和存储时间序列数据 …

SAP_ABAP_BDC录屏案例

SAP ABAP顾问能力模型梳理_企业数字化建设者的博客-CSDN博客SAP Abap顾问能力模型https://blog.csdn.net/java_zhong1990/article/details/132469977 一、实施步骤 1.1 SHDB --> 新建记录-->输入录制的tcode :BP,-->执行录屏操作-->录制结果封装成函数 1.2 SHDB …

大数据到底是好是坏?_光点科技

近年来&#xff0c;随着科技的不断发展和互联网的普及&#xff0c;大数据已经成为一个备受关注的话题。它带来了许多机遇和挑战&#xff0c;引发了人们对于其是好是坏的争议。大数据究竟是一把双刃剑&#xff0c;需要我们从多个角度来审视。 大数据的好处无疑是显而易见的。首先…

GNU make系列之写Makefile文件(1)

一.欢迎来到我的酒馆 在本章节介绍如何写Makefile文件。 目录 一.欢迎来到我的酒馆二.Makefile包含了什么三.引入其它的Makefile文件四.MAKEFILES变量 二.Makefile包含了什么 2.1 Makefile包含了5种类型&#xff1a;显式的规则&#xff0c;隐式的规则&#xff0c;变量的定义&am…

There is already ‘xxxController‘ bean method的解决方法

报这个错的原因是因为你controller里的RequestMapping中的路径有重复&#xff01;

嵌入式linux设备网口带宽-测试方法

iperf是一个基于Client/Server的网络性能测试工具&#xff0c;可以测试TCP、UDP和SCTP带宽质量&#xff0c;能够提供网络吞吐率信息&#xff0c;以及震动、丢包率&#xff0c;最大段和最大传输单元大小等统计信息&#xff0c;帮助我们测试网络性能&#xff0c;定位网络瓶颈。其…

keil5 报错no target connected

场景&#xff1a;用ST_Link V2 在 keil5 中下载stm32程序 原因&#xff1a;线路连接错误 正确连接 注意&#xff1a;江科大stm32和stlink的接线&#xff0c;一定要对齐&#xff0c;我买的一个不是按照顺序接线的&#xff0c;需要仔细查看

Arduino程序设计(五)按键中断+按键状态检测

按键中断按键状态检测 前言一、按键中断1、中断的基本概念2、外部中断3、示例代码4、按键中断实验 二、按键状态检测1、按键单击、双击和长按的工作原理2、按键状态检测实验 参考资料 前言 本文主要介绍两种按键检测实验&#xff0c;分别是&#xff1a;1、外部中断实现按键控制…

SQL-DQL

-----分组查询----- 1.语法&#xff1a; SELECT 字段列表 FROM 表名 [WHERE 条件 ] GROUP BY 分组字段名 [HAVING 分组后过滤条件]&#xff1b; 2.where与having区别 》执行时机不同&#xff1a;where是分组之前进行过滤&#xff0c;不满足where条件&#xff0c;不参与分组&…

docker常见面试问题详解

在面试的时候&#xff0c;面试官常常会问一些问题&#xff1a; docker是什么&#xff0c;能做什么&#xff1f;docker和虚拟机的区别是什么呢&#xff1f;docker是用什么做隔离的&#xff1f;docke的网络类型&#xff1f;docker数据之间是如何通信的&#xff1f;docker的数据保…

阿里云申请免费SSL证书的两种验证方式及配置服务器Tomcat升级HTTPS协议

通用教程&#xff0c;其他服务商的免费 SSL 证书也差不多是这个流程。&#xff08;至少腾讯云的操作步骤和本文是一致&#xff0c;嘻嘻&#xff01;&#xff09; 申请 SSL 证书 首先在阿里云上创建并申请 SSL 证书&#xff0c;之后选择 DNS 验证的方式&#xff0c;一种是手动配…

读word模板批量生成制式文件

文章目录 1、Maven依赖2、.docx或.doc格式的word模板准备3、读word模板,批量替换代码域,生成文件,demo4、结果展示1、Maven依赖 <dependency><groupId>fr.opensagres.xdocreport</groupId><artifactId>fr.opensagres.xdocreport.core</artifactI…

在Linux系统中设置动态地址进行网络访问

在 Linux 系统中&#xff0c;配置动态地址可以帮助我们实现更安全、匿名或绕过某些限制的网络访问。本文将介绍几种常用的方法来配置和使用代理服务器&#xff0c;在 Linux 环境下轻松实现高效且可靠地通过HTTP进行网络访问。 1、使用环境变量设置 HTTP/HTTPS 在命令行界面执行…

洗地机选购指南!洗地机推荐

在清洁这件事情上&#xff0c;大多数人会选择先扫地再拖地&#xff0c;为了让大家高效的完成扫地和拖洗这件事情&#xff0c;许多清洁家居的厂家开始研究和思考&#xff0c;怎样让大家可以解放双手&#xff0c;提高效率呢。这时洗地机出现了&#xff0c;它帮助我们在清洁上面解…

如何写一个外设驱动?

我的圈子&#xff1a; 高级工程师聚集地 我是董哥&#xff0c;高级嵌入式软件开发工程师&#xff0c;从事嵌入式Linux驱动开发和系统开发&#xff0c;曾就职于世界500强企业&#xff01; 创作理念&#xff1a;专注分享高质量嵌入式文章&#xff0c;让大家读有所得&#xff01; …