突破编程_C++_C++11新特性(右值引用与移动语义)

1 右值引用

1.1 右值引用的基本概念

右值引用是 C++11 中引入的一个关键特性,它允许程序员显式地将一个表达式标记为右值,从而可以利用移动语义进行优化。在深入探讨右值引用的基本概念之前,首先需要理解左值和右值的概念。

在 C++ 中,每个表达式都可以被分类为左值或右值。左值是指那些可以取地址的表达式,例如变量、数组元素、成员变量等。它们通常出现在赋值符号的左边,因为它们可以被赋值。相反,右值则是指那些不能取地址的表达式,例如字面量、临时变量、表达式求值结果等。它们通常出现在赋值符号的右边,因为它们提供了数据值。

右值引用就是对右值进行引用的类型。它的语法是在变量名前添加两个连续的 “&” 符号,例如 “int&&”。通过右值引用,可以获取右值的引用,并在其上进行修改操作。这在以前是不可能的,因为右值是临时对象,其生命周期很短,不能被持久地修改。

右值引用的主要应用场景是实现移动语义。移动语义允许我们将一个对象的状态“移动”到另一个对象,而不是复制它。这通常涉及将资源(如内存指针、文件句柄等)的所有权从一个对象转移到另一个对象,而不是进行深拷贝。这种操作可以显著提高性能,特别是在处理大型对象时。

1.2 右值引用的定义与语法

(1)定义:

右值引用本质上是一个引用,但它只能绑定到右值上。右值通常是那些临时性的、无法取地址的表达式结果,如字面常量、临时变量、函数返回值等。因此,右值引用提供了一种能够直接操作这些临时对象的方式,而无需进行复制或移动操作。

(2)语法:

右值引用的语法相对简单。在变量类型后面添加两个连续的“&&”符号即可表示一个右值引用。例如:

int&& rvalueRef = 10; // 正确:10是右值,可以被右值引用绑定

然而,需要注意的是,不能将一个左值(即有名字的、可以取地址的对象)直接绑定到一个右值引用上,除非使用 std::move 函数将其转换为右值。例如:

int x = 10;  
int&& rvalueRef = x; // 错误:x是左值,不能直接绑定到右值引用上  
int&& rvalueRef = std::move(x); // 正确:使用 std::move 将 x 转换为右值

在上面的例子中,std::move函数并不真正移动任何东西,它只是将其参数转换为右值引用,从而允许将其绑定到一个右值引用上。然而,一旦将一个对象转换为右值引用并进行了某些操作(如移动构造或移动赋值),那么原对象的状态将变得未定义,因此在使用 std::move 时需要格外小心。

1.3 右值引用与左值引用的区别

右值引用与左值引用在 C++ 中都是引用的类型,但它们之间存在显著的差异,主要体现在所引用的对象、生命周期以及使用场景上。

(1)首先,左值引用和右值引用所引用的对象类型不同。

左值引用是对左值的引用,左值指的是有确定的内存地址、有名字的变量,它们可以被赋值,可以在多条语句中使用。而右值引用则是对右值的引用,右值指的是没有名字的临时变量,如字面常量和临时变量,它们不能被赋值,只能在一条语句中出现。简而言之,左值引用是对持久性对象的引用,而右值引用是对临时性对象的引用。

(2)其次,这两种引用的生命周期也有所不同。

左值引用的对象在程序运行期间持续存在,其生命周期与程序的控制流相关。而右值引用的对象,其生命周期通常较短,它们往往是在表达式求值过程中产生的临时对象,一旦表达式执行完毕,这些临时对象就会被销毁。

(3)再者,右值引用和左值引用在使用场景上也有很大的区别。

左值引用主要用于给左值取别名,也就是给已存在的对象一个新的名字,方便在代码中引用它。而右值引用则主要用于实现移动语义,即将资源(如内存、文件句柄等)的所有权从一个对象转移到另一个对象,而非进行传统的深拷贝操作。通过右值引用,可以避免不必要的资源复制,提高程序的性能。

(4)最后,需要注意的是,const 左值引用可以引用左值或右值。
这是因为 const 保证了引用不会修改所引用的对象,从而避免了潜在的风险。而普通的右值引用只能引用右值,不能引用左值,这是由 C++ 的语法规则决定的。

2 移动语义

2.1 移动语义的概念

移动语义(Move Semantics)是 C++11 中引入的一种新的语言特性,旨在提高程序的性能和资源管理效率。其核心概念在于允许对象间资源的转移,而非传统的拷贝操作。

在传统的 C++ 中,对象拷贝涉及数据的复制和资源的分配,这对于大型对象来说,可能会造成不必要的浪费和占用。移动语义通过右值引用(Rvalue Reference)和移动构造函数(Move Constructor)实现了资源的所有权从一个对象到另一个对象的转移,从而避免了不必要的复制操作。

具体来说,右值引用允许标识临时对象或可以被移动的对象。当一个对象即将被销毁(例如,作为函数返回的临时对象或即将离开其作用域的局部变量)时,就可以使用右值引用来引用它,并调用其移动构造函数来转移其资源。移动构造函数接收一个右值引用参数,并将其资源“移动”到新的对象中,而不是复制这些资源。这样,原对象的资源就被新对象接管,而原对象则处于有效但未定义的状态,通常不再使用。

移动语义的实现依赖于有效的资源管理类,例如智能指针和容器类。这些类具备合适的移动构造函数和移动赋值运算符,以确保资源的正确转移和释放。

通过引入移动语义,C++ 程序员能够更高效地管理资源和处理对象,提供了一种全新的编程范式。这有助于减少不必要的内存分配和释放操作,降低程序的运行开销,提高程序的性能。

2.2 移动构造函数

移动构造函数是 C++11 引入的一个新特性,用于实现对象的移动语义。它允许在创建新对象时,从一个临时或即将被销毁的对象中“移动”资源,而不是进行传统的复制操作。这种机制可以显著提高程序的性能和资源管理效率。

移动构造函数的定义形式如下:

ClassName(ClassName&& other);

这里的 ClassName&& 表示一个右值引用参数,意味着这个函数只能接受一个右值作为参数。右值通常指的是临时对象或即将被销毁的对象,它们不再需要原来的资源。

移动构造函数的主要任务是接管另一个对象的资源,并将其置于新创建的对象中。这通常通过交换两个对象的内部指针或直接转移资源的所有权来实现。移动构造完成后,原对象通常处于有效但未定义的状态,它的资源已经被新对象接管,因此不应再被使用。

移动构造函数的使用场景主要包括:

(1)函数返回值传递: 当函数返回一个临时对象时,通过移动构造函数可以避免不必要的拷贝操作。移动构造允许直接转移临时对象的资源给调用者,而不需要复制整个对象。

(2)容器插入操作: 在容器(如std::vector)中插入临时对象时,可以通过移动构造函数实现高效插入。这避免了复制大对象时可能导致的性能开销。

(3)资源管理: 在涉及大量资源分配和释放的场景中,移动构造函数可以显著减少不必要的内存分配和释放操作,提高性能。

下面是一个简单的移动构造函数的例子:

#include <iostream>  
#include <cstring>  class MyString {
public:MyString(const char* str) : data_(new char[strlen(str) + 1]) {strcpy(data_, str);std::cout << "Copy constructor called." << std::endl;}// 移动构造函数  MyString(MyString&& other) noexcept : data_(other.data_) {other.data_ = nullptr;std::cout << "Move constructor called." << std::endl;}~MyString() {delete[] data_;}// 禁止拷贝赋值和移动赋值,以保持简单示例  MyString& operator=(const MyString&) = delete;MyString& operator=(MyString&&) = delete;private:char* data_;
};int main() 
{std::cout << "create str1" << std::endl;MyString str1("Hello, world!");std::cout << "create str2" << std::endl;MyString str2(std::move(str1)); // 这里会调用移动构造函数  return 0;
}

上面代码的输出为:

create str1
Copy constructor called.
create str2
Move constructor called.

在上面的例子中,当构造函数的入参为 std::move(str1) 时,编译器会自动选择移动构造函数来构造 str2,从而避免了不必要的拷贝操作。

需要注意的是,移动构造函数应该被标记为 noexcept(如果确实不会抛出异常),这样可以保证在移动操作失败时不会造成资源泄露或其他问题。此外,移动构造函数通常还会将原对象的资源指针设为 nullptr,以避免悬挂指针问题。

2.3 移动赋值运算符

移动赋值运算符是 C++11 引入的一个新特性,它允许从一个对象中“移动”资源到另一个对象,而不是进行传统的复制操作。这种机制对于提升性能、优化资源管理至关重要。

移动赋值运算符的基本语法如下:

ClassName& operator=(ClassName&& other) noexcept;

这里的 ClassName&& 表示一个右值引用参数,意味着这个函数只能接受一个右值(即临时对象或即将被销毁的对象)作为参数。noexcept 关键字表示该操作不会抛出异常,这有助于编译器进行更好的优化。

移动赋值运算符的主要任务是从源对象(other)中“窃取”资源,并将其置于当前对象中。这通常通过交换两个对象的内部状态来实现,或者简单地通过移动源对象的资源到当前对象,并将源对象的资源置为空或进行清理。

移动赋值运算符通常按照以下步骤工作:

(1)检查自赋值: 首先,检查当前对象(*this)是否就是源对象(other)。如果是,则直接返回当前对象的引用,以避免自赋值问题。

(2)释放当前对象的资源: 释放当前对象所持有的资源,以防止资源泄露。这可能涉及删除动态分配的内存、关闭文件句柄等操作。

(3)移动源对象的资源: 将源对象的资源(如指针、句柄等)转移到当前对象中。这通常涉及简单的赋值操作,因为源对象之后将不再需要这些资源。

(4)置空源对象的资源: 将源对象的资源指针或句柄置为nullptr或相应的无效状态,确保源对象在析构时不会尝试释放已经移动的资源。

(5)返回当前对象的引用: 返回当前对象的引用,以便支持链式赋值操作。

移动赋值运算符的一个关键优点是它避免了不必要的复制操作,特别是在处理大型对象或资源密集型对象时。通过移动而不是复制资源,可以显著提高程序的性能。

需要注意的是,一旦一个对象的资源被移动,该对象就处于有效但未定义的状态。这意味着它仍然是一个有效的对象,但其内部状态是未知的,因此不应再被使用,除非被重新赋值或重新初始化。

此外,移动赋值运算符通常与移动构造函数一起使用,以提供完整的移动语义支持。编译器在适当的情况下会自动生成移动赋值运算符和移动构造函数,但也可以根据需要自定义它们。

下面是一个简单的移动构造函数的例子:

#include <iostream>  
#include <cstring>  class MyString {
public:MyString() {}MyString(const char* str) : data_(new char[strlen(str) + 1]) {strcpy(data_, str);std::cout << "Copy constructor called." << std::endl;}// 移动赋值函数  MyString& operator=(MyString&& other) noexcept {if (this != &other) {// 释放当前对象的资源  delete[] data_;data_ = other.data_;other.data_ = nullptr;std::cout << "Move constructor called." << std::endl;}return *this;}~MyString() {delete[] data_;}private:char* data_ = nullptr;
};int main() 
{std::cout << "create str1" << std::endl;MyString str1("Hello, world!");std::cout << "create str2" << std::endl;MyString str2; // 不可以直接写成 MyString str2 = std::move(str1); 此时调用的还是移动构造函数,而不是移动赋值函数(这里由于没有定义移动构造函数,所以会编译失败)str2 = std::move(str1); // 这里会调用移动赋值函数  return 0;
}

上面代码的输出为:

create str1
Copy constructor called.
create str2
Move constructor called.

3 右值引用与移动语义的实际应用

3.1 自定义类型中的移动语义

在 C++ 中,当需要传递或返回大的自定义类型对象时,如果采用传统的值传递方式,会涉及到对象的拷贝操作,这可能会导致不必要的性能开销。移动语义则允许我们将一个对象的资源“移动”到另一个对象,而不是进行拷贝,从而避免了不必要的开销。

要实现移动语义,需要定义移动构造函数和移动赋值运算符。移动构造函数接受一个右值引用(rvalue reference)作为参数,这个右值引用通常是一个临时对象或者是一个即将被销毁的对象。移动构造函数会“移动”这个右值引用对象的资源(如动态分配的内存、文件句柄等),然后将其置于有效的未定义状态。这样,就不需要再对这个右值引用对象进行任何操作,因为它已经不再拥有这些资源了。

类似地,移动赋值运算符也接受一个右值引用作为参数,并将右侧对象的资源移动到左侧对象。与移动构造函数不同的是,移动赋值运算符在移动资源之前需要处理左侧对象原有的资源,以避免资源泄漏。这通常涉及到释放左侧对象原有的资源,然后再移动右侧对象的资源。

通过定义移动构造函数和移动赋值运算符,可以让自定义类型支持移动语义。这样,在需要传递或返回大的自定义类型对象时,编译器可以自动选择使用移动操作而不是拷贝操作,从而提高程序的性能。

需要注意的是,移动语义并不总是比拷贝语义更优。在某些情况下,拷贝操作可能更简单、更安全。因此,在定义移动构造函数和移动赋值运算符时,需要仔细考虑它们是否真的能够提高程序的性能,并且确保它们不会引入新的问题或错误。

注意:移动语义的本质是根据传入的右值引用类型调用移动构造函数或者移动赋值运算符!

如下为样例代码(实际上是综合了上面章节中的样例代码):

#include <iostream>  
#include <cstring>  class MyString {
public:// 构造函数  MyString(const char* str) {length = strlen(str);data = new char[length + 1];strcpy(data, str);std::cout << "MyString(const char* str)" << std::endl;}// 移动构造函数  MyString(MyString&& other) noexcept : data(other.data), length(other.length) {// 将原对象的数据指针设为nullptr,防止其析构时再次释放  other.data = nullptr;other.length = 0;std::cout << "MyString(MyString&& other) noexcept : data(other.data), length(other.length)" << std::endl;}// 析构函数  ~MyString() {delete[] data;}// 拷贝构造函数(为了完整性)  MyString(const MyString& other) {length = other.length;data = new char[length + 1];strcpy(data, other.data);std::cout << "MyString(const MyString& other)" << std::endl;}// 拷贝赋值运算符(为了完整性)  MyString& operator=(const MyString& other) {if (this != &other) {delete[] data;length = other.length;data = new char[length + 1];strcpy(data, other.data);}std::cout << "MyString& operator=(const MyString& other)" << std::endl;return *this;}// 移动赋值运算符  MyString& operator=(MyString&& other) noexcept {if (this != &other) {delete[] data;data = other.data;length = other.length;other.data = nullptr;other.length = 0;}std::cout << "MyString& operator=(MyString&& other) noexcept" << std::endl;return *this;}// 打印字符串  void print() const {std::cout << data << std::endl;}private:char* data;size_t length;};int main() 
{// 创建一个MyString对象  MyString s1("Hello");// 使用移动构造函数创建一个新对象  MyString s2 = std::move(s1); // s1现在处于有效但未定义状态  // 打印s2(应该输出"Hello")  s2.print();// 尝试使用s1(不允许,因为s1现在处于未定义状态)  // s1.print(); // 可能会导致未定义行为  // 使用移动赋值运算符  MyString s3("World");s2 = std::move(s3); // s3现在处于有效但未定义状态  // 打印s2(应该输出"World")  s2.print();return 0;
}

上面代码的输出为:

MyString(const char* str)
MyString(MyString&& other) noexcept : data(other.data), length(other.length)
Hello
MyString(const char* str)
MyString& operator=(MyString&& other) noexcept
World

在这个示例中,String 类管理了一个动态分配的字符数组。它定义了移动构造函数和移动赋值运算符,这些构造函数和运算符将资源的所有权从一个 String 对象移动到另一个对象,而不是进行拷贝。在移动操作之后,原对象(在本例中是 other)处于有效但未定义的状态,因此不能再使用。

noexcept 关键字用于标记这些操作不会抛出异常,这允许编译器在更多情况下优化代码。

3.2 函数返回值优化与移动语义

函数返回值优化(Return Value Optimization,简称 RVO)和移动语义都是 C++ 中用于提高性能的重要技术,它们各自在资源管理和对象创建方面扮演着关键角色。

函数返回值优化是 C++ 编译器的一项优化技术,用于减少对象在返回时的拷贝操作。当函数返回一个对象时,传统的做法是在函数内部创建一个临时对象,然后通过拷贝构造函数将这个临时对象拷贝到调用者那里。然而,这种做法涉及不必要的资源拷贝和对象创建,可能导致性能下降。

为了解决这个问题,编译器会尝试进行 RVO 优化。在优化过程中,编译器会尝试直接在调用者的栈帧上构造返回对象,而不是在函数内部创建临时对象后再进行拷贝。这样,就可以避免不必要的拷贝操作,提高性能。

值得注意的是,虽然 RVO 是编译器的优化行为,但为了确保优化的有效性,程序员在编写代码时也应该遵循一些最佳实践。例如,避免在函数内部创建不必要的临时对象,确保返回的对象类型与函数返回类型一致等。

在实际应用中,这两种技术往往可以相互补充。当编译器进行 RVO 优化时,它可能会利用移动语义来更有效地管理资源。例如,在返回对象时,如果编译器决定直接在调用者的栈帧上构造对象,那么它可能会使用移动语义来“窃取”函数内部临时对象的资源,而不是进行传统的拷贝操作。

(1)RVO

结合 “3.1 自定义类型中的移动语义” 样例代码中创建的 MyString 类型,做如下 RVO 的测试:

// class MyString 的定义,这里不再重复// 返回一个String对象的函数  
MyString createString(const char* str) {return MyString(str); // 这里可能发生RVO  
}int main() 
{// 调用createString函数并接收返回的String对象  MyString s = createString("Hello, World!");// 打印返回的String对象  s.print();return 0;
}

上面代码的输出为:

MyString(const char* str)
Hello, World!

如果编译器进行了 RVO 优化,将只会看到 “MyString(const char* str)”的输出,而不会看到 “MyString(const MyString& other)” 的输出,因为实际上没有发生拷贝操作。

(2)移动语义

继续使用 “3.1 自定义类型中的移动语义” 样例代码中创建的 MyString 类型,做如下移动语义的测试:

// class MyString 的定义,这里不再重复// 返回一个String对象的函数  
MyString createString(const char* str) {MyString s = str;return s; // 这里会自动调用移动语义,也可以写成 std::move(s);   
}int main() 
{// 调用createString函数并接收返回的String对象  MyString s = createString("Hello, World!");// 打印返回的String对象  s.print();return 0;
}

上面代码的输出为:

MyString(const char* str)
MyString(MyString&& other) noexcept : data(other.data), length(other.length)
Hello, World!

3.3 STL 容器中的移动语义

STL(Standard Template Library)容器中的移动语义是 C++11 引入的一个重要特性,它允许我们更加高效地处理容器中的对象,特别是在涉及到对象所有权转移的场景中。移动语义通过避免不必要的拷贝操作,显著提高了代码的性能。

在 C++ 中,传统的对象复制操作(如拷贝构造函数和拷贝赋值运算符)涉及创建对象的完整副本,这可能会消耗大量的时间和内存资源。然而,在某些情况下,实际上并不需要创建对象的副本,而只是需要将一个对象的资源(如动态分配的内存)转移到另一个对象。这就是移动语义发挥作用的地方。

STL容器中的移动语义主要体现在以下几个方面:

  • 移动构造函数和移动赋值运算符:STL容器中的许多类型都定义了移动构造函数和移动赋值运算符。这些特殊成员函数允许容器在需要时,通过“窃取”另一个对象的资源来初始化或赋值给当前对象,而不是进行传统的复制操作。这种资源转移的方式通常更加高效。
  • 插入和赋值操作:在STL容器中插入或赋值对象时,如果提供了右值引用(rvalue reference)作为参数,容器就可以利用移动语义来避免不必要的拷贝。例如,使用 std::vector 的 push_back 方法时,如果传递的是一个临时对象或即将被移动的对象(右值),则vector会使用移动构造函数或移动赋值运算符来添加元素,而不是进行拷贝。
  • 容器间的元素转移:STL 提供了一些函数和算法,用于在容器之间高效地转移元素。例如,std::move 函数可以用于将一个容器中的元素移动到另一个容器中,而不需要进行任何拷贝操作。此外,std::swap 函数在 C++11 之后也利用了移动语义,以便在交换两个对象时更加高效。
    下面是一个简单的示例,演示了如何在 STL 容器中使用移动语义:
#include <iostream>  
#include <vector>  
#include <string>  int main() 
{  // 创建一个包含字符串的vector  std::vector<std::string> vec1 = {"Hello", "World"};  // 使用移动语义将 vec1 中的元素移动到 vec2 中  std::vector<std::string> vec2(std::make_move_iterator(vec1.begin()), std::make_move_iterator(vec1.end()));  vec1.clear(); // 清空 vec1,因为元素已经被移动  // 输出 vec2 中的元素,与 vec1 原来的元素相同,而 vec1 则为空for (const auto& str : vec2) {  std::cout << str << std::endl;  }  return 0;  
}

这个示例使用了 std::make_move_iterator 来创建移动迭代器,这些迭代器允许将 vec1 中的元素移动到 vec2 中,而不需要进行任何拷贝操作。这样,就可以高效地转移容器中的元素,同时减少内存使用和性能开销。

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

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

相关文章

HTML5:七天学会基础动画网页(end)

想了想还是有一点东西还没说&#xff0c;当然这块内容也比较简单&#xff0c;就是当我们有一段完整素材时&#xff0c;如下: 我在网上随便找的素材&#xff0c;当然我们平时在使用素材时要注意尊重他人的著作权&#xff0c;不管是字体图片还是别的&#xff0c;不然后面不小心侵…

字符串筛选排序 - 华为OD统一考试(C卷)

OD统一考试(C卷) 分值: 100分 题解: Java / Python / C++ 题目描述 输入一个由n个大小写字母组成的字符串, 按照 ASCII 码值从小到大的排序规则,查找字符串中第 k 个最小ASCII 码值的字母(k>=1) , 输出该字母所在字符串的位置索引(字符串的第一个字符位置索引为0) 。…

NS3 使用 waf 工具添加外部库

我最近在写 NS3 的时候想要把他人写好的外部库添加到 NS3 中一起编译&#xff0c;在 Linux 系统中&#xff0c;添加外部库往往通过编译选项 -l<外部库名> 来添加&#xff0c;而在大型项目中往往需要把外部库写到 Makefile 文件中通过 make 来编译。奈何 NS3 的早期版本都…

org.springframework.beans.factory.BeanNotOfRequiredTypeException异常处理

目录 一、问题详情 二、示例代码 三、原因分析 四、解决方案 一、问题详情 在本地启动项目的时候,突然报了如下错误,导致整个项目启动失败了。 org.springframework.bea

【蓝桥杯备赛】Day15:递推与递归(倒计时23天)

题目1:题目 2335: 信息学奥赛一本通T1422-活动安排 设有n个活动的集合E{1,2,…,n}&#xff0c;其中每个活动都要求使用同一资源&#xff0c;如演讲会场等&#xff0c;而在同一时间内只有一个活动能使用这一资源。每个活动i都有一个要求使用该资源的起始时间si和一个结束时间fi…

【微信小程序】零基础快速入门

微信小程序 小程序与普通网页开发的区别 1 运行环境不同 网页运行在浏览器环境中 小程序运行在微信环境中2 API 不同 由于运行环境的不同,所以在小程序中,无法调用 DOM 和 BOM 的API 但是,小程序中可以调用微信环境提供的各种 API,例如:地理位置、扫码、支付等等3 开发模…

【C++】CC++内存管理

目录 一、C/C内存分布二 、C语言中动态内存管理方式&#xff1a;malloc/calloc/realloc/free三、 C内存管理方式3.1 new/delete操作内置类型3.2 new和delete操作自定义类型3.3 长度域 四、operator new与operator delete函数五、new和delete的实现原理5.1 内置类型5.2 自定义类…

Lombok:@Singular集合元素灵活添加利器

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 目录 一、Singular介绍 二、使用示例 三、注意事项 四、使用场景 总结 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一、Singular介绍 Sing…

【LAMMPS学习】三、构建LAMMPS(7)具有额外构建选项的软件包

3. 构建 LAMMPS 3.7.具有额外构建选项的软件包 当使用某些包进行构建时&#xff0c;除了Build_package页面上描述的步骤之外&#xff0c;可能还需要额外的步骤。 ​ 对于CMake构建&#xff0c;可能有额外的可选或必需的变量要设置。对于使用make进行构建&#xff0c;可能需…

【C语言】基本语法知识C语言函数操作符详解

主页&#xff1a;醋溜马桶圈-CSDN博客 专栏&#xff1a;C语言_醋溜马桶圈的博客-CSDN博客 gitee&#xff1a;mnxcc (mnxcc) - Gitee.com 目录 1.基本语法 1.1 代码解释 1.1.1 main()主函数 1.1.2 int 1.1.3 { } 1.1.4 printf()库函数 1.1.5 stdio.h头文件 1.2 C语言的…

ThreeJs的音频和位置音频

Threejs的场景有时候需要引入声音&#xff0c;比如下雨声音&#xff0c;撞击声音等&#xff0c;这需要用到Threejs的两个类Audio和PositionalAudio&#xff0c;第一个Audio是普通的声音&#xff0c;比如下雨&#xff0c;在整个场景中听到的都是下雨的声音&#xff0c;而且每个位…

[C语言]——操作符详解

目录 一.操作符的分类 二.二进制和进制转换 1.二进制转十进制 2.二进制转八进制和十六进制 2.1二进制转八进制 2.2二进制转十六进制 三.原码、反码、补码 四.移位操作符 1.左移操作符 2.右移操作符 五.位操作符&#xff1a;&、|、^、~ 练习1&#xff1a;编写代码实…

3d纸模型图纸制作方法---模大狮模型网

制作3D纸模型图纸通常需要按照以下步骤进行&#xff1a; 选择设计模型&#xff1a; 首先确定你想要制作的3D纸模型的设计&#xff0c;可以是建筑物、动物、交通工具等各种形式的模型。 绘制设计图纸&#xff1a; 使用计算机辅助设计软件(如AutoCAD、SketchUp)或手工绘图工具…

jsp基本语法

jsp的基本语法:java代码放在<% %>之间 jsp的变量定义:局部变量定义放在<% %>之间&#xff0c;全局变量放在<%! %>之间 jsp的表达式&#xff1a;把值显示在网页上&#xff0c;语法是:<%表达式> <% page language"java" contentType&quo…

JavaMySQL高级一(下)

目录 1.常用函数 1.字符串函数 2.时间日期函数 3.聚合函数 4.数学函数 2.分布查询 3.子查询基础 1.简单子查询 1.常用函数 在程序开发过程中&#xff0c;除了简单的数据查询&#xff0c;还有基于已数据进行数据的统计分析计算等需求。因此&#xff0c;在SQL中将一…

【小白成长记】使用watch优化获取不同tab下数据的代码逻辑

场景&#xff1a;页面有一个 el-tab&#xff0c;共两个tab&#xff1a;Tab1 与 Tab2。需求要求进入页面默认active第二个tab即Tab2。 开始代码写成如下&#xff1a; <el-tabs v-model"tabActiveName" type"card" before-leave"handleTabsClick&q…

数据库学习记录(二)多表设计与多表查询

一对多 一对多时候&#xff0c;一张表内的一个数据可能对应着其他表内的多个数据&#xff0c;例如一个部门内有多个员工&#xff0c;但是公司里不只一个部门&#xff0c;也不止一个员工&#xff0c;这个时候就是一对多的情况&#xff0c;这个时候可以绑定一个外键&#xff0c;…

Linux 磁盘的一生

注意&#xff1a;实验环境都是使用VMware模拟 ​ 磁盘接口类型这里vm中是SCSI&#xff0c;扩展sata,ide(有时间可以看看或者磁盘的历史) ​ 总结&#xff1a;磁盘从有到无—类似于建房子到可以住 ————————————————————————————————————…

php laravel 二维码

public function qr($url,$name2,$inpath){require_once(dirname(__FILE__) . /../../../Library/phpqrcode/phpqrcode.php);$errorCorrectionLevel L;//容错级别$matrixPointSize 10;//生成图片大小$QRcode new \QRcode() ;$QRcode->png($url, $inpath.$name2, $errorCor…

react快速入门教程

react快速入门教程 React是一个用于构建用户界面的JavaScript库。它由Facebook开发,并且已经成为前端开发中最受欢迎的工具之一。本快速入门教程将介绍React的基础知识和常用概念,帮助你开始使用React构建交互式的Web应用程序。 1. React的创建和嵌套组件 React是一个JavaSc…