当前位置: 首页 > news >正文

C++ 参数传递 笔记

目录

1、输入参数的传递方式-选择传值还是传引用?

处理用户信息

处理坐标

处理配置

处理ID

2、对于需要修改的参数,使用非const引用传递

        有趣的例外:警惕表象的迷惑

需要警惕的陷阱 

“糟糕”的update方法:

“完美”的set_name与set_age方法:

总结与最佳实践:

如何避免掉进这些坑?

利用编译器的警告和错误:

遵循const正确性:

明确函数意图:

使用std::optional或返回值:

代码审查和测试:

了解编译器的特性:

持续学习和实践:

3、为什么要采用移动语义?

如何实现移动语义?

移动语义的实战应用

注意事项

总结

例:

代码分析

潜在陷阱

结论

4、返回值 vs 输出参数

返回值

输出参数

栗子

什么时候该用输出参数???

返回多个值???

5、指针 VS 引用

引用的特点:

指针的特点:

选择指针还是引用?


你是否曾凝视着代码,心中浮现出这些疑惑:

  1. 这个参数更适合按值传递还是按引用传递呢?
  2. 为何我的程序运行如此缓慢,是否与参数的传递方式有关?
  3. 移动语义背后究竟隐藏着怎样的奥秘?
  4. 在需要返回多个值时,是选择使用元组(tuple)还是结构体(struct)更为合适?

若有,那就继续Look Look... 

1、输入参数的传递方式-选择传值还是传引用?

        这是一个在C++编程中经常让人纠结的问题。让我们来探讨一下这个问题的黄金法则:

        当参数是“轻量级”的数据类型(例如int、指针等小型变量)时,选择传值是一个明智之举。这是因为传值操作直接且高效,无需担心指针解引用的额外开销。

        然而,当参数是“重量级”的数据类型(例如string这种大型对象)时,传const引用则显得更为合适。这是因为传const引用可以避免不必要的对象拷贝,从而节省时间和内存资源。

        那么,为什么会这样呢?原因如下:

        传值的好处在于其简单直接性,无需处理复杂的指针操作,减少了出错的可能性。

        而传const引用的好处则在于其效率性,特别是对于大型对象而言,避免了拷贝的开销,使得程序运行更加高效。

        

在处理不同类型的数据时,选择正确的参数传递方式至关重要。下面是对您提供的代码片段中参数传递方式的点评及优化建议:

处理用户信息

  • 推荐方式void processUserInfo(const string& name); 
    • 理由:字符串(string)在C++中通常是一个相对较大的数据结构,包含动态分配的内存。因此,通过const引用传递可以避免不必要的拷贝,提高程序效率。
  • 不推荐方式void processUserInfo(string name); 
    • 理由:这种方式会导致每次调用函数时都进行字符串的拷贝,增加了不必要的内存分配和释放操作,降低了程序性能。

处理坐标

  • 推荐方式void movePoint(int x, int y); 
    • 理由:整数(int)是基本数据类型,占用内存小,传值操作高效且直接。
  • 不推荐方式void movePoint(const int& x, const int& y); 
    • 理由:对于基本数据类型,使用引用传递反而会增加额外的开销(如指针解引用),而且整数本身就很小,拷贝的开销几乎可以忽略不计。

处理配置

  • 推荐方式void updateConfig(const vector<int>& config); 
    • 理由vector<int>可能包含大量的整数元素,占用较大的内存空间。通过const引用传递可以避免整个向量的拷贝,提高程序效率。
  • 不推荐方式void updateConfig(vector<int> config); 
    • 理由:这种方式会导致每次调用函数时都进行向量的拷贝,包括动态内存分配和元素复制,开销很大。

处理ID

  • 推荐方式void processId(int id); 
    • 理由:ID通常是整数类型,占用内存小,传值操作高效且直接。
  • 不推荐方式void processId(const int& id); 
    • 理由:同样地,对于基本数据类型(如整数ID),使用引用传递是不必要的,因为整数本身就很小,拷贝的开销几乎可以忽略不计。

        综上所述,在选择参数传递方式时,应根据数据类型的大小和特性来决定。对于大型数据结构(如字符串、向量等),使用const引用传递可以避免不必要的拷贝;而对于小型数据结构(如基本数据类型),则可以直接传值。

2、对于需要修改的参数,使用非const引用传递

        当需要修改函数内部的参数时,我们应选择使用非const引用来传递它。这样做的原因何在呢?让我们通过一个生动的比喻来揭示其重要性。

        想象一下,你有一把珍贵的钥匙,需要交给朋友去开门。但你不确定他是否会私自复制这把钥匙。这种不确定性就像你把一个参数传给函数时,却不清楚它是否会在函数内部被修改。

        为了避免这种潜在的风险,使用非const引用就像是给参数加上一个醒目的标签:“注意这个参数我可能会在函数内部进行修改哦!”这样一来,代码的意图就变得清晰明了,任何阅读你代码的人都能立刻明白这个参数的用途和可能的变化。

        非const引用的这种明确性不仅提高了代码的可读性,还增强了代码的安全性。它确保了函数内部对参数的修改是可控和预期的,从而避免了因参数被意外修改而导致的潜在错误。

        因此,在需要修改函数内部参数的情况下,选择非const引用来传递参数是一个明智且安全的选择。

        举例:


// result命名不明确,无法直观判断其用途
void calculate_sum(int values[], int count, int* result); // sum作为输出参数,明确表示其将被函数修改以存储结果
void calculate_sum(const int values[], int count, int& sum);// 使用char*类型的str作为参数,其修改状态不明确
void parse_name(char* str);// 使用string引用作为参数,直观表达其将被修改
void parse_name(std::string& str);

        有趣的例外:警惕表象的迷惑

        有些数据类型看似温和无害,实则暗藏玄机,能在不经意间修改原始对象。换句话说,就是有些类型表面上看起来安全无害,但实际上它们具有修改原始对象的能力,这种能力往往不易被察觉。

#include <vector>
#include <memory>class Widget {std::vector<int> data;
public:void add(int x) { data.push_back(x); }
};// process函数接收一个shared_ptr<Widget>的按值传递参数
// 但由于shared_ptr管理的是一个动态分配的对象,
// 因此通过解引用shared_ptr(即w->add(42))可以修改该对象的状态
void process(std::shared_ptr<Widget> w) 
{w->add(42);  // 这里的操作看似按值传递,但实则修改了w所指向的Widget对象
}// update_iterator函数接收一个vector<int>::iterator的按值传递参数
// 迭代器本质上是一个指向vector中元素的指针封装
// 因此,通过解引用迭代器(即*it = 100)可以修改vector中对应元素的值
void update_iterator(std::vector<int>::iterator it)
{*it = 100;  // 这里的操作虽然也是按值传递迭代器,但实则修改了迭代器所指向的vector元素
}

详细解释

  1. process函数
    • 参数w是一个std::shared_ptr<Widget>的按值传递实例。
    • 尽管w是按值传递的,但它所管理的Widget对象是动态分配的,并且多个shared_ptr实例可能共享对该对象的所有权。
    • 通过w->add(42),我们实际上是在修改w所指向的Widget对象的状态,向其data成员添加了一个新元素。
    • 因此,尽管w本身是按值传递的,但其所指向的对象的状态是可以被修改的。
  2. update_iterator函数
    • 参数it是一个std::vector<int>::iterator的按值传递实例。
    • 迭代器it实际上是一个封装了指向vector<int>中某个元素的指针的对象。
    • 通过*it = 100,我们解引用了迭代器,并修改了它所指向的vector<int>中的元素的值。
    • 因此,尽管it本身是按值传递的,但其所指向的vector<int>元素的值是可以被修改的。

总结

        在这两个场景中,尽管参数是按值传递的,但由于它们分别指向或封装了可以修改的对象或元素,因此仍然可以产生副作用。这提醒我们在编写代码时要格外小心,确保理解参数的实际含义和它们所指向或封装的内容。

需要警惕的陷阱 

        引用参数,这一编程中的利器,犹如一把双刃剑,既能够作为输入传递数据,又能够作为输出返回结果。然而,若使用不当,这把剑也可能反过来伤到自己,带来意想不到的问题。

        在编程实践中,引用参数因其能够直接修改原始数据而备受青睐。这种特性使得函数能够高效地处理大型数据结构,而无需进行繁琐的数据复制。然而,正是这种直接修改的能力,也带来了潜在的风险。

        当函数通过引用参数接收数据时,调用者可能并不清楚数据会在函数内部被如何修改。如果函数内部对数据进行了意外的修改,那么调用者的数据状态也可能随之发生变化,从而导致程序行为异常。

        此外,即使函数本身没有意图修改数据,但由于引用参数的存在,其他函数或代码段也可能通过该引用间接地修改数据。这种隐蔽的数据修改方式往往难以追踪和调试,从而增加了程序的复杂性和维护成本。

        因此,在使用引用参数时,我们需要格外小心。要明确函数对引用参数的修改意图,并在文档中清晰地说明这一点。同时,也要谨慎地选择是否使用引用参数,以避免不必要的复杂性和潜在的风险。

        总之,引用参数是一把双刃剑,既能带来便利,也可能带来麻烦。只有在使用时保持警惕和谨慎,才能确保程序的正确性和稳定性。

“糟糕”的update方法

void update(Person& p) { *this = p;  // 危险操作:完全替换了当前对象
}

        此方法名为update,但实际操作却如同“夺舍”——它完全用传入的Person对象p替换了当前对象的状态。这种操作极具破坏性,因为它不仅更改了对象的所有属性,还忽略了调用者可能只想更新部分属性的需求。此外,若p是一个临时对象或即将被销毁的对象,这种替换可能导致资源泄露或悬挂指针等潜在问题。

“完美”的set_nameset_age方法

void set_name(const string& new_name) { name_ = new_name; }void set_age(int new_age) { if (new_age < 0) throw invalid_argument("年龄不能为负,你想穿越吗?");age_ = new_age; 
}

        相比之下,set_nameset_age方法则如同两位专业的美容师,它们温柔地、有选择地更新对象的属性。set_name方法仅修改name_属性,而set_age方法不仅修改age_属性,还在赋值前进行有效性检查,确保年龄不会设为负数。这种方法既安全又灵活,因为它允许调用者精确地控制哪些属性需要更新。

总结与最佳实践

  • 避免使用“夺舍”式的update方法:它们破坏了对象的封装性,可能导致不可预测的状态变化。
  • 采用“美容师”式的属性设置方法:通过提供明确的、有选择性的属性设置方法(如set_nameset_age),可以更好地控制对象的状态变化,并确保属性的有效性。
  • 遵循最小权限原则:仅暴露必要的接口给外部使用,以减少潜在的安全风险和维护成本。

        通过遵循这些最佳实践,可以创建更加健壮、可维护和安全的代码。

如何避免掉进这些坑?

要避免掉进编程中的陷阱,特别是与引用参数相关的陷阱,我们可以依靠编译器的帮助,并遵循一些最佳实践。以下是一些建议,帮助你编写更加健壮和清晰的代码:

  1. 利用编译器的警告和错误

    • 现代编译器非常智能,它们能够识别出许多潜在的代码问题。例如,如果你声明了一个非const引用参数但在函数体内没有修改它,一些编译器可能会给出警告,提示你可能忘记了修改或者这个参数应该被声明为const
    • 同样,如果你不小心对一个非const引用参数执行了移动操作(std::move),编译器也可能会警告你这样做可能会导致数据丢失或未定义行为。
  2. 遵循const正确性

    • 默认情况下,应将函数参数声明为const引用,除非你确实需要在函数内部修改它们。这有助于保护数据不被意外修改,并增加代码的可读性和可维护性。
    • 如果函数不需要修改参数,使用const引用可以避免不必要的复制,同时保证数据的安全性。
  3. 明确函数意图

    • 通过函数命名和文档清晰地表达函数的意图。如果函数旨在修改参数,那么使用非const引用是合适的。但是,如果函数只是读取参数,则应使用const引用。
  4. 使用std::optional或返回值

    • 如果函数有时需要修改参数,有时又不需要,可以考虑使用std::optional<T&>(在C++17及更高版本中可用)来明确指示哪些参数可能被修改。
    • 另一种方法是让函数返回一个新的对象或值,而不是修改输入参数。这有助于保持函数的纯净性(即无副作用)。
  5. 代码审查和测试

    • 定期进行代码审查,让团队成员相互检查代码,以发现潜在的错误和陷阱。
    • 编写单元测试来验证函数的行为,确保它们按预期工作。
  6. 了解编译器的特性

    • 不同的编译器可能有不同的警告和错误消息。了解你所使用的编译器的特性,并启用尽可能多的警告选项,可以帮助你发现更多的问题。
  7. 持续学习和实践

    • 编程是一个不断学习和实践的过程。通过阅读文档、参加培训课程、参与社区讨论等方式,不断提高自己的编程技能。

        记住,编译器是我们的好朋友,但它并不能捕捉到所有的错误。因此,我们还需要依靠良好的编程习惯、代码审查和测试来确保代码的质量和可靠性。遵循这些建议,你将能够避免许多常见的陷阱,并编写出更加健壮和清晰的代码。

在C++的舞台上,"移动"对象这出戏确实是一场精彩绝伦的表演。它关乎于如何高效地传递大型对象,避免不必要的复制,从而提升程序的性能。接下来,我们就来深入探讨一下这场表演的艺术所在。

3、为什么要采用移动语义?

        在C++中,当我们需要传递或返回大型对象时,如果采用值传递的方式,编译器会生成该对象的副本。对于大型对象而言,这个过程可能会消耗大量的时间和内存。然而,很多时候,我们并不需要在源位置保留这个对象,此时,移动语义就派上了用场。

        移动语义允许我们“偷走”对象的资源(如动态分配的内存、文件句柄等),而不是复制它们。这样,目标对象就可以接管这些资源,而源对象则变为一个有效但未定义状态(通常称为“空”或“已移动”状态)。这种方式可以极大地提高性能,特别是在处理大型数据结构时。

如何实现移动语义?

        在C++中,实现移动语义通常需要以下两个步骤:

  1. 定义移动构造函数:这是一个特殊的构造函数,它接受一个右值引用(T&&)作为参数。右值引用是C++11引入的一种新特性,它允许我们区分左值(如变量、函数返回值等)和右值(如临时对象、std::move的返回值等)。移动构造函数会“偷走”传入对象的资源,而不是复制它们。

  2. 定义移动赋值运算符:这与移动构造函数类似,但它用于赋值操作。它同样接受一个右值引用作为参数,并“偷走”传入对象的资源。

移动语义的实战应用

        在标准库中,许多容器和字符串类都实现了移动语义。例如,std::vectorstd::string都提供了移动构造函数和移动赋值运算符。这使得我们可以高效地传递和返回这些类型的对象。

        此外,std::move函数也是一个非常重要的工具。它可以将一个左值强制转换为右值引用,从而允许我们调用移动构造函数或移动赋值运算符。但请注意,std::move本身并不移动任何东西;它只是改变了对象的值类别,使得移动操作成为可能。

注意事项

        虽然移动语义可以极大地提高性能,但我们也必须小心使用。一旦对象被移动,它的状态就不再有效。因此,在移动对象后,我们应该避免再使用它(除非我们明确知道它的新状态)。此外,移动语义还可能与对象的析构函数和资源管理策略相互作用,因此我们需要确保这些方面都得到妥善处理。

总结

        在C++的舞台上,"移动"对象这出戏确实是一场值得一看的表演。通过实现移动构造函数和移动赋值运算符,并合理使用std::move函数,我们可以高效地传递和返回大型对象,从而提升程序的性能。但请记住,移动语义是一把双刃剑;在使用它时,我们需要小心谨慎地处理对象的状态和资源管理问题。

例:

        在C++中,您所展示的代码片段巧妙地运用了移动语义来优化性能。然而,这里有一些细微之处和潜在陷阱需要注意。首先,让我们来分析一下代码:

string make_greeting(string&& name) {string result = "Hello, ";result += std::move(name); // 直接“偷”走name的内容return result; // result也会被移动返回,效率极高!
}// 使用示例
string name = "Alice";
string greeting = make_greeting(std::move(name)); 
// 此时name变为空字符串,greeting则包含了完整的问候语

代码分析

  1. 函数签名string make_greeting(string&& name) 表明该函数接受一个右值引用到string类型的参数。这允许函数“偷”走传入string对象的资源,因为右值引用通常用于表示可以安全移动的对象。

  2. 移动操作:在函数体内,std::move(name)被用于将name的内容“转移”给result。这里,std::move并不真正移动数据,而是将name转换为右值引用,从而允许编译器选择移动赋值运算符(如果可用)来优化性能。

  3. 返回值:函数返回result,这是一个局部变量。在C++11及更高版本中,返回局部变量时,如果类型支持移动语义,编译器通常会使用命名返回值优化(NRVO)或移动语义来避免不必要的复制。然而,即使没有这个优化,std::string的拷贝构造函数和赋值运算符通常也被高度优化,因此这里的性能差异可能并不显著(除非string非常大或复制操作非常昂贵)。

  4. 调用后的状态:由于namestd::move转换并传递给make_greeting,它在函数返回后处于未定义状态(但在这个特定情况下,由于std::string的移动构造函数会清空源字符串,所以name变为空字符串)。这意味着在调用make_greeting后,不应再使用name

潜在陷阱

  • 重复移动:虽然在这个例子中std::move的使用是合理的,但在某些情况下,过度使用std::move可能会导致不必要的移动操作,从而损害性能。例如,如果name在传递给make_greeting之前或之后还需要被使用,那么就不应该对它使用std::move

  • 未定义状态:记住,在移动操作后,源对象(在这个例子中是name)处于未定义状态。因此,在移动之后使用它是不安全的。

  • 命名返回值优化(NRVO):虽然NRVO可能会减少或消除返回result时的复制或移动操作,但这不是程序员可以依赖的行为。它取决于编译器的实现和优化级别。

结论

您的代码示例展示了如何在C++中使用移动语义来优化性能。然而,在实际编程中,需要谨慎使用std::move和移动语义,以避免潜在的陷阱和未定义行为。同时,也要意识到现代C++编译器在优化返回值方面已经做得非常出色,因此有时不必过度担心性能问题。

4、返回值 vs 输出参数

返回值

优点

  1. 直观性:这种方法非常直观,因为函数直接返回所需的结果。调用者无需准备额外的变量来接收结果。

  2. 简洁性:代码更加简洁,因为不需要额外的参数来传递输出。

  3. 链式调用:返回值允许链式调用,即一个函数的返回值可以作为另一个函数的参数。

缺点

  1. 大型对象:对于大型对象或复杂数据结构,返回值的开销可能较大,因为可能需要复制或移动数据。

  2. 错误处理:如果函数可能失败并需要报告错误,返回值可能变得复杂,特别是当成功和失败的情况都需要返回不同类型的数据时。

输出参数

优点

  1. 避免复制:对于大型对象,使用输出参数可以避免复制或移动数据,因为数据是直接在调用者提供的变量中修改的。

  2. 灵活性:输出参数允许函数返回多个结果,或者通过引用修改输入参数。

  3. 错误处理:在某些情况下,输出参数可以更容易地处理错误,例如通过引用传递一个错误代码或状态。

缺点

  1. 不直观:对于不熟悉的人来说,输出参数可能不太直观,因为它们需要额外的变量来接收结果。

  2. 易出错:如果调用者忘记提供有效的输出参数(例如,传递了一个空指针或未初始化的引用),则可能导致未定义行为或崩溃。

  3. 链式调用受限:输出参数通常不支持链式调用,因为函数不返回任何有用的结果(或只返回一个状态码)。

        在实际编程中,选择返回值还是输出参数取决于具体的情况和需求。对于小型、简单的数据结构,返回值通常是更好的选择,因为它更直观且易于使用。对于大型、复杂的数据结构或需要返回多个结果的情况,输出参数可能更合适,因为它可以避免不必要的复制和移动操作,并提供更大的灵活性。

栗子

#include <iostream>int add(int a, int b) {return a + b;
}int main() {int x = 5;int y = 10;int sum = add(x, y);std::cout << "Sum (return value): " << sum << std::endl;return 0;
}
#include <iostream>void add(int a, int b, int& result) {result = a + b;
}int main() {int x = 5;int y = 10;int sum;add(x, y, sum);std::cout << "Sum (output parameter): " << sum << std::endl;return 0;
}

什么时候该用输出参数???

        在C++编程中,选择是否使用输出参数(通常通过引用或指针传递)取决于多个因素,包括函数的目的、需要返回的数据量、以及是否希望修改传入的参数等。以下是一些指导原则,帮助你在自定义函数中决定何时使用输出参数:

  1. 需要修改传入的参数
    如果函数需要修改其传入的参数,并且这些修改对调用者是有意义的,那么应该使用输出参数。例如,一个排序函数可能需要传入一个数组,并对其进行原地排序。

  2. 返回多个值
    当函数需要返回多个值时,使用输出参数是一种常见的方法。C++函数只能有一个返回值,但如果有多个结果需要返回给调用者,那么可以通过输出参数来实现。

  3. 避免不必要的复制
    对于大型对象或数据结构,如果通过返回值传递会导致不必要的复制,那么使用输出参数可能更高效。通过引用或指针传递可以避免复制,从而节省时间和内存。

  4. 保持接口的一致性
    如果函数的接口需要与现有的API或库保持一致,并且这些接口已经使用了输出参数,那么为了保持一致性,你的函数也应该使用输出参数。

  5. 函数的目的和语义
    考虑函数的目的和它所表达的语义。有时候,使用输出参数可以使函数的意图更加清晰。例如,一个查找函数可能通过输出参数返回找到的元素的索引,并通过返回值表示是否找到了该元素。

  6. 错误处理和异常安全
    使用输出参数有时可以更容易地处理错误和异常。例如,如果函数在执行过程中遇到错误,它可以通过输出参数返回部分结果或状态信息,同时设置错误代码或抛出异常。

  7. 调用者的便利性
    考虑调用者的便利性。有时候,使用输出参数可以使调用者的代码更加简洁和直观。例如,如果调用者已经有一个变量来存储结果,并且希望直接更新这个变量,那么使用输出参数可能更方便。

        然而,也需要注意过度使用输出参数可能会导致函数接口变得复杂和难以理解。因此,在决定使用输出参数时,应该权衡其优缺点,并确保函数接口的设计是清晰、直观和易于使用的。

返回多个值???

        在C++中,当你需要从一个函数返回多个值时,有几种推荐的方式可以考虑。以下是一些常见的方法:

  1. 使用结构体或类
    定义一个结构体或类来封装需要返回的多个值。这种方法具有良好的可读性和类型安全性。

    struct Result {int value1;double value2;std::string message;
    };Result getValues() {Result result;result.value1 = 42;result.value2 = 3.14;result.message = "Success";return result;
    }
  2. 使用std::tuple
    std::tuple是C++11引入的一个模板类,用于存储固定大小的异构值集合。它提供了一种轻量级的方式来返回多个值,而不需要定义新的结构体或类。

    #include <tuple>
    #include <string>std::tuple<int, double, std::string> getValues() {return std::make_tuple(42, 3.14, "Success");
    }// 使用时,可以通过std::get来获取值
    int main() {auto [value1, value2, message] = getValues(); // C++17结构化绑定// 或者在C++11/14中使用std::get// int value1 = std::get<0>(getValues());// double value2 = std::get<1>(getValues());// std::string message = std::get<2>(getValues());return 0;
    }

    注意:在C++17中,你可以使用结构化绑定来简化从std::tuple中提取值的语法。

  3. 使用输出参数
    通过引用或指针将值作为参数传递给函数,并在函数内部进行赋值。这种方法适用于需要修改传入参数或避免复制大型对象的情况。

    void getValues(int& value1, double& value2, std::string& message) {value1 = 42;value2 = 3.14;message = "Success";
    }int main() {int value1;double value2;std::string message;getValues(value1, value2, message);return 0;
    }

    然而,这种方法可能会使函数签名变得复杂,并且需要调用者准备适当的变量来接收输出。

  4. 使用std::pair
    如果只需要返回两个值,并且这两个值的类型不同,那么可以使用std::pair

    #include <utility> // 包含std::pairstd::pair<int, double> getValues() {return std::make_pair(42, 3.14);
    }int main() {auto [value1, value2] = getValues(); // C++17结构化绑定// 或者在C++11/14中使用std::get(但这里不适用,因为std::pair没有std::tuple的std::get函数,应该直接访问first和second)// int value1 = getValues().first;// double value2 = getValues().second;return 0;
    }

    注意:尽管std::pair只能存储两个值,并且这两个值的类型在编译时是固定的,但它比std::tuple更轻量,并且在只需要返回两个值的情况下更为方便。

        总的来说,选择哪种方法取决于你的具体需求、代码的可读性和维护性考虑。对于返回多个异构值的情况,std::tuple和结构体/类是很好的选择。如果你只需要返回两个值,并且这两个值的类型不同,那么std::pair可能是一个更简单的选择。输出参数适用于需要修改传入参数或避免复制大型对象的情况,但可能会使函数签名变得复杂。

5、指针 VS 引用

在C++编程中,指针和引用都是用于间接访问变量的工具,但它们有不同的使用场景和特性。选择使用指针还是引用,通常取决于你的具体需求、代码的可读性、安全性以及潜在的性能考虑。

引用的特点:

  1. 安全性:引用在创建时必须被初始化,且之后不能再改变指向。这有助于避免悬挂指针(dangling pointer)和野指针(wild pointer)等问题。

  2. 语法简洁:使用引用可以使代码更加简洁和直观,因为它们提供了类似变量的语法。

  3. 无法为空:引用不能为空(null),这意味着你不能创建一个未初始化的引用。这有时是一个优点,因为它可以强制你在使用引用之前对其进行初始化。

  4. 不支持算术运算:引用不支持指针算术运算,这有助于防止一些常见的指针错误。

指针的特点:

  1. 灵活性:指针可以指向任何有效的内存地址,包括空指针(null)。这使得指针在需要表示“无值”或“未初始化”状态时非常有用。

  2. 动态内存管理:指针常用于动态内存分配和释放,这在需要处理大量数据或创建复杂数据结构时非常有用。

  3. 支持算术运算:指针支持算术运算,如加法、减法和比较,这使得它们在处理数组和内存块时非常有用。

  4. 风险更高:由于指针的灵活性,它们也更容易出错。例如,悬挂指针、野指针、内存泄漏和缓冲区溢出等问题通常与指针的使用有关。

选择指针还是引用?

  1. 如果你需要一个必须被初始化的变量别名:使用引用。引用在创建时必须被初始化,且之后不能改变指向,这有助于避免一些常见的错误。

  2. 如果你需要处理可能为空的情况:使用指针。引用不能为空,而指针可以。

  3. 如果你需要动态分配内存:使用指针。虽然可以使用引用来引用动态分配的对象,但指针在内存管理方面提供了更多的灵活性。

  4. 如果你需要处理数组或内存块:使用指针。指针支持算术运算,这使得它们在处理数组和内存块时非常有用。

  5. 如果你希望代码更加简洁和直观:在可能的情况下使用引用。引用的语法更接近于普通变量,这使得代码更易于阅读和理解。

  6. 如果你需要传递大型对象:考虑使用引用或指针来避免不必要的复制。然而,请注意,对于小型对象,复制可能更高效,因为复制的开销可能小于传递指针或引用的开销(包括可能的间接访问和缓存未命中)。

总的来说,选择使用指针还是引用取决于你的具体需求。在可能的情况下,优先考虑使用引用,因为它们提供了更好的安全性和语法简洁性。然而,在某些情况下,指针的灵活性和动态内存管理能力可能是必要的。

        PS:这些都是浮云,代码能跑就行

相关文章:

C++ 参数传递 笔记

目录 1、输入参数的传递方式-选择传值还是传引用&#xff1f; 处理用户信息 处理坐标 处理配置 处理ID 2、对于需要修改的参数,使用非const引用传递 有趣的例外&#xff1a;警惕表象的迷惑 需要警惕的陷阱 “糟糕”的update方法&#xff1a; “完美”的set_name与set…...

【Linux】注释和配置文件的介绍

目录 一、help vim-modes指令 二、vim命令模式下的注释 1、直接注释&#xff1a; 2、快捷键注释&#xff08;比较麻烦&#xff0c;了解即可&#xff09; 三、vim的配置文件 .vimrc 四、sudo指令的相关问题 一、help vim-modes指令 在底行模式输入该指令可以用于查看vim的十…...

安卓主板_基于联发科MTK MT8788平台平板电脑方案_安卓核心板开发板定制

联发科MT8788安卓核心板平台介绍&#xff1a; MTK8788设备具有集成的蓝牙、fm、wlan和gps模块&#xff0c;是一个高度集成的基带平台&#xff0c;包括调制解调器和应用处理子系统&#xff0c;启用LTE/LTE-A和C2K智能设备应用程序。该芯片集成了工作在2.0GHz的ARM Cortex-A73、最…...

CLIP(Contrastive Language-Image Pre-Training)在SOPHON BM1684X上进行推理

1、链接 https://github.com/sophgo/sophon-demo/tree/release/sample/CLIP2、开发环境中交叉编译生成sophon_arm-3.8.0-py3-none-any.whl 3、sail安装 算能官网技术资料中SDK-24.04.01的 libsophon_soc_0.4.1_aarch64.tar.gz sophon-mw-soc_0.4.1_aarch64.tar.gz SOPHON-SA…...

Ascend Extension for PyTorch的源码解析

1 源码下载 Ascend对pytorch代码的适配&#xff0c;可从以下链接中获取。 Ascend/pytorch 执行如下命令即可。 git clone https://gitee.com/ascend/pytorch.git2 目录结构解析 源码下载后&#xff0c;如果需要编译torch-npu&#xff0c;最好保持pytorch的源码版本匹配&…...

鸿蒙HarmonyOS开发:给应用添加基础类型通知和进度条类型通知(API 12)

文章目录 一、通知介绍1、通知表现形式2、通知结构3、请求通知授权 二、创建通知1、发布基础类型通知2、发布进度类型通知3、更新通知4、移除通知 三、设置通知通道1、通知通道类型 四、创建通知组五、为通知添加行为意图1、导入模块。2、创建WantAgentInfo信息。4、创建WantAg…...

从零开始使用YOLOv11——Yolo检测detect数据集自建格式转换为模型训练格式:20w+图片1w+类别代码测试成功

在之前的文章中记录了YOLO环境的配置安装和基本命令的一些使用&#xff0c;上一篇博文的地址快速链接&#xff1a;从零开始使用YOLOv8——环境配置与极简指令&#xff08;CLI&#xff09;操作&#xff1a;1篇文章解决—直接使用&#xff1a;模型部署 and 自建数据集&#xff1a…...

自动化新时代:机器取代工作,我们该如何重塑自我?

内容概要 在自动化时代的浪潮中&#xff0c;技术的飞速发展对传统工作模式产生了深远影响。我们眼前浮现的是一个充满机遇与挑战的新世界。许多岗位面临被机器取代的威胁&#xff0c;然而&#xff0c;这一变化并不仅仅是消极的。在这个背景下&#xff0c;个体不仅需要重新审视…...

GEE 土地分类——利用Sentinel-2数据进行土地分类

目录 简介 函数 ee.Classifier.smileRandomForest(numberOfTrees, variablesPerSplit, minLeafPopulation, bagFraction, maxNodes, seed) Arguments: Returns: Classifier 代码 结果 简介 利用Sentinel-2数据进行土地分类的流程大致可分为以下几个步骤: 1. 数据获取…...

《C++ 游戏开发》

一、引言 在当今的数字娱乐时代&#xff0c;游戏开发已经成为一个充满活力和创新的领域。C 作为一种强大的编程语言&#xff0c;在游戏开发中占据着重要的地位。它具有高效的性能、丰富的功能和广泛的适用性&#xff0c;能够满足游戏开发中对性能和灵活性的高要求。本文将深入探…...

2024年11月10日系统架构设计师考试题目回顾

案例分析 试题一&#xff1a;质量属性 基于描述填空是什么质量属性&#xff0c;常规题。&#xff08;性能&#xff0c;功能&#xff0c;安全&#xff0c;可用等等&#xff09;可用性而言&#xff0c;王工建议采用 ping/echo 机制检测&#xff0c;不过从资源使用角度&#xff…...

测试实项中的偶必现难测bug--苹果支付丢单问题

问题描述: app支付后,由于某种原因(可能是网络、流量不稳定、或者用户快速频繁操作。。。)会造成一定概率性的回调苹果支付结果失败的情况出现,表现的直观现象就是客户反馈已经支付了,包括苹果支付也是有记录,但是我们的后台显示的是已取消状态的订单 验证难点:测试和…...

Elasticsearch的数据类型

Elasticsearch(简称 ES)支持多种数据类型,主要分为以下几类: 1. 基本数据类型 Text:用于全文搜索的文本字段。ES 会对其内容进行分词处理。Keyword:适用于精确匹配的字段,例如名称、标签等。ES 不会对其内容分词处理。Integer:整数类型,包括 byte、short、integer 和…...

SSL 证书申请以及配置流程

SSL 证书申请以及配置流程 手动申请免费 SSL 证书的简明指南 如果你希望手动为你的网站申请免费的 SSL 证书&#xff0c;Let’s Encrypt 提供了一个很棒的免费服务。而 Certbot 则是官方推荐的工具&#xff0c;可以帮助你完成证书的申请和配置。以下是如何一步步完成的详细说…...

[Docker#4] 镜像仓库 | 部分常用命令

目录 什么是 Docker Registry 镜像仓库生活案例 镜像仓库分类 镜像仓库工作机制 常用的镜像仓库 私有仓库 镜像仓库命令 镜像命令[部分] 容器命令[部分] 什么是 Docker Registry 定义&#xff1a;Docker Registry 负责存储、管理和分发镜像&#xff0c;并提供了登录认…...

工业通信协议对比:OPC-UA、Modbus、MQTT、HTTP

综合对比表 对比项OPC-UAModbusMQTTHTTP通信效率低&#xff0c;带宽消耗高高高&#xff0c;开销低&#xff0c;效率高低&#xff0c;带宽消耗大实时性一般&#xff0c;延迟较高高&#xff0c;延迟低高&#xff0c;低延迟低&#xff0c;延迟高性能消耗高&#xff0c;需要高性能…...

docker 常用方法

目录 docker参数解释 基础信息和环境变量设置 容器运行和管理相关参数 数据卷挂载 GPU 相关参数 镜像相关参数 查看现有的镜像 docker images 查看正在运行的docker docker ps 1、docker启动停止及查看状态 启动docker&#xff1a; systemctl start docker 停止docker…...

区块链技术入门:以太坊智能合约详解

&#x1f493; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4dd; Gitee主页&#xff1a;瑕疵的gitee主页 ⏩ 文章专栏&#xff1a;《热点资讯》 区块链技术入门&#xff1a;以太坊智能合约详解 区块链技术入门&#xff1a;以太坊智能合约详解 区块链技术入门&#xff1a;以太…...

特定数据库的备份脚本

该脚本 mysql_backup.sh 是一个 MySQL 数据库的备份脚本&#xff0c;以下是它的工作原理和需要注意的细节&#xff1a; 脚本内容分析 1.设置时间变量 TIME : TIMEdate %F_%H-%M-%S该变量 TIME 存储当前日期和时间&#xff0c;格式为 YYYY-MM-DD_HH-MM-SS&#xff0c;用于生…...

uni-app打包后报错云服务空间未关联

使用uni-app打包到h5 项目里面用到了uni-app的云端一体城市选择组件&#xff0c;这个组件数据用到了uniCloud云服务空间&#xff0c;在本地运行没问题&#xff0c;打包之后测试环境报错&#xff1a; 一顿查&#xff0c;查到了官网是这样说的&#xff1a; cli publish --platfo…...

FPGA学习(10)-数码管

前3节视频目的是实现显示0~F的数码管仿真&#xff0c;后3节是用驱动芯片驱动数码管。 目录 1.数码管显示原理 2.代码过程 2.1仿真结果 3.串行移位寄存器原理 3.1原理 ​编辑 3.2 数据手册 3.3 先行设计思路 4.程序 4.1确定SRCLK的频率 4.2序列计数器 4.3 不同coun…...

C++(继承)

继承的语法 继承的好处&#xff1a;减少重复代码 语法&#xff1a; class 子类 &#xff1a; 继承方法 父类 子类 也称为 派生类 父类 也成为 基类 继承方式 公共继承 保护继承 私有继承 结论&#xff1a;父类中私有成员也是被子类继承下去了&#xff0c;只是由编译器给隐藏后…...

华为OD机试 - RSA加密算法(Java 2024 E卷 100分)

long n (long) Math.sqrt(num); 与long n (long) Math.floor(Math.sqrt(num)); 这两行代码的目的都是计算 num 的平方根&#xff0c;并将结果转换为 long 类型的整数。然而&#xff0c;它们在处理方式上有一些微小的差别。 long n (long) Math.sqrt(num);long n (long) M…...

分组校验在Spring中的应用详解

目录 前言1. 什么是分组校验2. 分组校验的基本原理3. 分组校验的实现步骤3.1 定义分组接口3.2 在校验项中指定分组3.3 校验时指定要校验的分组3.4 默认分组和分组的继承 4. 分组校验的优势和适用场景4.1 优势4.2 适用场景 5. 常见问题与解决方案5.1 校验未生效5.2 无法识别默认…...

torch.nn.**和torch.nn.functional.**的区别

torch.nn.** torch.nn.**是一个继承了torch.nn.Module的类&#xff0c;使用前必须先构造对象&#xff0c;然后再调用。如果直接使用则会报错 例如 a torch.randn(3,4) print(a) sigmoid torch.nn.Sigmoid() a sigmoid(a) print(a) a torch.nn.Sigmoid(a)tensor([[ 0.2462…...

Air780E基于LuatOS编程开发

Air780E基于LuatOS编程开发 Air780E开发板下载固件版本开发板刷机开发调试源码编译下载源码编译工具编译工具链 Air780E开发板 合宙通信推出的 LTE Cat.1 bis通信模块&#xff0c;采用移芯EC618平台&#xff0c;支持4G全网通, 包括的模组有: Air780E – 4G Cat.1Air780EG – …...

贪心算法-汽车加油

这道题目描述了一个汽车旅行场景&#xff0c;需要设计一个有效的算法来决定在哪几个加油站停车加油&#xff0c;以便最小化加油次数。题目给出了汽车加满油后的行驶距离n公里&#xff0c;以及沿途若干个加油站的位置。我们需要找出一个方案&#xff0c;使得汽车能够完成整个旅程…...

Qt信号和槽-->day04

Qt信号和槽 标准的信号和槽函数Qt中的槽函数Qt中的信号 connect案例 自定义信号和槽案例分析 信号槽的拓展信号连接信号案例 信号槽的两种连接方式Qt5中的处理方式Qt4中的处理方式Qt5处理信号槽重载问题案例 lambda表达式简单案例Qt中的应用 补充知识点 标准的信号和槽函数 QW…...

【Linux】为终端命令自定义快件键并弹窗提醒 设置快捷键切换网络代理(Network Proxy)Disabled/Manual 并弹窗提醒

【Linux】为终端命令自定义快件键并弹窗提醒 设置快捷键切换网络代理&#xff08;Network Proxy&#xff09;Disabled/Manual 并弹窗提醒 可以自定义快捷键执行终端命令&#xff0c;执行完毕会有弹窗提醒。下面给一个例子&#xff0c;设置快捷键切换网络代理&#xff08;Netwo…...

十六:Spring Boot依赖 (1)-- spring-boot-starter 依赖详解

1. 简介&#xff1a; spring-boot-starter 是 Spring Boot 项目中的基础启动器依赖&#xff0c;它为开发者提供了 Spring Boot 应用所需的核心功能和自动配置 spring-boot-starter 不是一个具体的功能模块&#xff0c;而是一个基础的启动器。 Spring Boot 提供了一系列的 sta…...

美食推荐网站模板/经典软文案例和扶贫农产品软文

传送门&#xff1a;http://acm.hdu.edu.cn/showproblem.php?pid1823 Luck and Love Time Limit: 10000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) Problem Description世界上上最远的距离不是相隔天涯海角而是我在你面前可你却不知道我爱你―― 张…...

建设一个公司网站需要什么条件/磁力搜索器kitty

目录 一、C语言介绍二、C语言特点三、Hello World四、转义符五、占位符六、俄罗斯方块游戏七、文件下载一、C语言介绍 C是一种通用的编程语言&#xff0c;广泛用于系统软件与应用软件的开发。于1969年至1973年间&#xff0c;为了移植与开发UNIX操作系统&#xff0c;由丹尼斯里奇…...

如何申请企业域名/武汉seo公司哪家专业

npm install --global xxx 属于全局安装npm install xxx 属于本地安装npm config set cache "D:\Program Files\nodejs\node_cache" 设置缓存文件夹npm config get cache 查看全局缓存目录npm config set prefix "D:\Program Files\nodejs"设置全局模块存放…...

网站建设 网址导航/阿里云com域名注册

这个需求不常见&#xff0c;但是通常在svn管理的项目中&#xff0c;如果需要对比两个文件不为文本文件时&#xff0c;svn则无法提供比对。 这两个文件可能为二进制&#xff0c;可能为自定义加密的内容&#xff0c;也可能是unity对序列化后的.prefab或.unity二进制文件。 我们…...

沈阳市住房和城乡建设局网站/小程序开发平台有哪些

开发准备&#xff1a;Chrome浏览器、require.js模块管理、Swiper插件、iconfont图标、Sass 参考博客&#xff1a;http://www.haorooms.com/ 前端开发注意事项 1、关于meta属性 <meta name"viewport" content"widthdevice-width,initial-scale1.0,maximum-sca…...

驻马店 网站建设/自媒体引流推广

MYSQL的DATE_FORMAT()格式化日期DATE_FORMA T(date, format) 根据格式串format 格式化日期或日期和时间值date&#xff0c;返回结果串。可用DATE_FORMAT( ) 来格式化DATE 或DATETIME 值&#xff0c;以便得到所希望的格式。根据format字符串格式化date值:%S, %s 两位数字形式的秒…...