• C++基础


    本篇博客默认浏览者拥有C语言基础

    目录

    命名空间

    输入与输出

    缺省函数

    理解缺省函数

    缺省函数的分类

    全缺省函数

    半缺省函数

    函数重载 

    理解函数重载

    引用 

    理解引用

    引用的特征

    引用的用法

    作为函数的参数

    作为函数的返回值

    引用和指针的区别

    内联函数

    理解内联函数


    命名空间

    在C语言中,定义一个变量时有几率会与头文件中的已存在的变量重命名,导致出现重赋值的操作导致和错误,所以在C++中,我们可以引用命名空间来进行区分,下面是示例代码

    1. #include
    2. namespace mynp//自定义一个mynp命名空间
    3. {
    4. int rand = 10;//此处定义一个rand变量,赋值为10
    5. int Sum(int rand)//此处定义一个Sum求和函数,参数为rand
    6. {
    7. return rand + rand;
    8. }
    9. }
    10. int Sum()//命名空间外定义一个同名函数Sum
    11. {
    12. return 0;
    13. }
    14. using namespace mynp;//调用命名空间
    15. int main()
    16. {
    17. int rand = 100;
    18. printf("此处为命名空间外的rand:%d\n", rand);
    19. printf("此处为命名空间内的rand:%d\n", mynp::rand);
    20. printf("此处调用了命名空间外的函数Sum:%d\n", Sum());
    21. printf("此处调用了命名空间内的函数Sum:%d\n", mynp::Sum(rand));
    22. }

    运行结果:

    通过上面的代码可以看到,我们可以定义一个命名空间,在内部定义我们想要的变量和方法

    当我们在命名空间外定义了同名的变量与函数后并不会报错

    但是需要注意的是 命名空间的定义方式成员引入方法

    1. namespace <命名空间名字>
    2. {
    3. 在此处定义变量与方法
    4. int A = 1;
    5. int Sum
    6. {
    7. return 0;
    8. }
    9. ...
    10. }
    11. using namespace <命名空间名字>;
    12. int main()
    13. {
    14. int A = 10;
    15. int B = A; //使用命名空间外的A
    16. int C = <命名空间名字>::A; //使用命名空间内的A
    17. }

    输入与输出

    在C语言中我们总是需要使用scanf与printf来进行输入与输出的操作

    在输入与输出时还需要专门进行类型的分辨操作,显得过于累赘

    在C++中引用了一种全新的方法进行输入输出操作

    cin cout

    下面是代码示例

    1. #include //std命名空间所在头文件
    2. using namespace std; //std包含了cout与cin的定义
    3. int main()
    4. {
    5. int a = 10;
    6. int b;
    7. cout << "请输入b的值:";
    8. cin >> b;
    9. cout << "a的值为:" << a << endl;
    10. int c, d;
    11. cout << "请输入c和d的值:" << endl;
    12. cin >> c >> d;
    13. cout << a << " " << b << " " << c << " " << d << endl;
    14. }

    运行结果如下:

    需要注意的是: 

    1.使用cout标准输出对象(控制台)和cin标准输入对象(键盘)时,必须包含< iostream >头文件 以及按命名空间使用方法使用std。

    2. cout和cin是全局的流对象,endl是特殊的C++符号,表示换行输出,他们都包含在包含< iostream >头文件中。

    3. >是流提取运算符

    4. 使用C++输入输出更方便,不需要像printf/scanf输入输出时那样,需要手动控制格式, C++的输入输出可以自动识别变量类型

    5. 实际上cout和cin分别是ostream和istream类型的对象,>>和<<也涉及运算符重载等知识, 此知识点后续再谈~

    我们除了这样使用,也可以选择不添加 using namespace std;来使用cin与cout

    1. #include
    2. //此处使用std::cin与std::cout的方法使用
    3. int main()
    4. {
    5. int a = 10;
    6. int b;
    7. std::cout << "请输入b的值:";
    8. std::cin >> b;
    9. std::cout << "a的值为:" << a << endl;
    10. int c, d;
    11. std::cout << "请输入c和d的值:" << endl;
    12. std::cin >> c >> d;
    13. std::cout << a << " " << b << " " << c << " " << d << endl;
    14. }

    缺省函数

    理解缺省函数

    缺省参数是声明或定义函数时为函数的参数指定一个缺省值

    在调用该函数时,如果没有指定实参则采用该形参的缺省值,否则使用指定的实参

    举个例子:

    1. #include
    2. using namespace std;
    3. void test(int a = 0) //此处定义一个测试函数
    4. { //参数设定一个默认值
    5. cout << a << endl; //输出传入的参数
    6. }
    7. int main()
    8. {
    9. test(); //此处不传参调用测试函数
    10. test(10); //测出传入10作为参数调用测试函数
    11. }

    运行结果:

    可以看到,我们在定义函数时就设定了参数的默认值为0

    在下面进行调用时,不传参数的函数输出了默认值0

    而传入10作为参数的函数输出了10

    这便是缺省函数的使用方法 

    缺省函数的分类

    全缺省函数

    1. void test(int a = 10, int b = 20, int c = 30)
    2. {
    3. cout << "a = " << a << endl;
    4. cout << "b = " << b << endl;
    5. cout << "c = " << c << endl;
    6. }
    7. int main()
    8. {
    9. test();
    10. test(100, 200, 300);
    11. }

    运行结果:

    此处定义一个测试函数,存在3个参数abc,并都设置了默认值,这就叫全缺省函数

    半缺省函数

    1. void test(int a, int b = 10, int c = 20)
    2. {
    3. cout << "a = " << a << endl;
    4. cout << "b = " << b << endl;
    5. cout << "c = " << c << endl;
    6. }
    7. int main()
    8. {
    9. test(100, 300);
    10. test(100, 200, 300);
    11. }

    运行结果:

    此处定义一个函数拥有3个参数abc,其中bc给定默认值,而a没有给定,这就叫做半缺省函数

    值得一提的是:

    1. void test(int a, int b = 10, int c)
    2. {
    3. cout << "a = " << a << endl;
    4. cout << "b = " << b << endl;
    5. cout << "c = " << c << endl;
    6. }
    7. int main()
    8. {
    9. test(100, 300);
    10. test(100, 200, 300);
    11. }

     对于这段代码,a与c未给定默认值,下方第一次调用会出现错误

    原因是c未接收到参数,此处的100与300分别给的是a与b,所以在后续的使用需要注意缺省函数的填写顺序,以免出现类似错误


    函数重载 

    理解函数重载

    函数重载是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数

    这些同名函数的形参列表(参数个数类型类型顺序)不同,常用来处理实现功能类似数据类型 不同的问题

    举例理解:

    1. #include
    2. using namespace std;
    3. // 1、参数类型不同
    4. int Add(int left, int right)
    5. {
    6. cout << "int Add(int left, int right)" << endl;
    7. return left + right;
    8. }
    9. double Add(double left, double right)
    10. {
    11. cout << "double Add(double left, double right)" << endl;
    12. return left + right;
    13. }
    14. // 2、参数个数不同
    15. void f()
    16. {
    17. cout << "f()" << endl;
    18. }
    19. void f(int a)
    20. {
    21. cout << "f(int a)" << endl;
    22. }
    23. // 3、参数类型顺序不同
    24. void f(int a, char b)
    25. {
    26. cout << "f(int a,char b)" << endl;
    27. }
    28. void f(char b, int a)
    29. {
    30. cout << "f(char b, int a)" << endl;
    31. }
    32. int main()
    33. {
    34. Add(10, 20);
    35. Add(10.1, 20.2);
    36. f();
    37. f(10);
    38. f(10, 'a');
    39. f('a', 10);
    40. return 0;
    41. }

    通过上面可以看到,即使函数重名,但只要参数类型,参数个数与参数顺序不同,都可以构成函数重载进行使用

    值得一提的是

    cin与cout底层是通过多个函数重载操作来实现cin与cout的忽略数据类型的匹配

    Qs:为什么C语言不支持函数重载而C++支持?

    As:一句话概括

    C语言的编译方式是通过同名函数进行链接,没办法区分

    C++是通过函数修饰规则来区分链接,只要参数不同,修饰出来的名字就不一样,就支持了重载


    引用 

    理解引用

    引用不是新定义一个变量,而是给已存在变量取了一个别名

    编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间

    下面举个例子来进行理解:

    1. int main()
    2. {
    3. int a = 100;
    4. int& aa = a; //&引用关健符
    5. cout << "这里为被引用变量:" << a << endl;
    6. cout << "这里为引用变量:" << aa << endl;
    7. }

    运行结果:

     引用的特征

    1. 引用在定义时必须初始化

    2. 一个变量可以有多个引用

    3. 引用一旦引用一个实体,再不能引用其他实体

    4.引用对象不能为常量

    5.引用变量的数据类型必须和被引用对象的数据类型一致

    1. int main()
    2. {
    3. int a = 100;
    4. int& aa = a;
    5. int& bb; //引用必须初始化
    6. int& aaa = a; //一个变量可以被多个引用变量引用
    7. int c = 200;
    8. int& aa = c; //引用不能进行多次初始化操作
    9. const int d = 300;
    10. int& dd = d; //引用对象不能为常量
    11. double e = 400.00;
    12. int& ee = e; //引用变量的数据类型必须一致
    13. cout << "这里为被引用变量:" << a << endl;
    14. cout << "这里为引用变量:" << aa << endl;
    15. }

     ​​​​

    引用的用法

    作为函数的参数

    将 C++ 引用作为函数参数有以下几个好处:

    1. 避免拷贝:引用参数允许函数直接访问并操作原始数据,而不需要进行拷贝操作。这可以提高程序的性能,尤其是当传递大型对象时。

    2. 修改调用者的数据:通过使用引用参数,函数可以直接修改调用者传递的数据。这对于需要在函数内部修改外部变量的情况非常有用。

    3. 更直观的语法:使用引用参数可以让函数调用的语法更加直观和自然。调用者无需使用额外的语法来传递指针或对象副本,只需将变量作为参数传递即可。

    4. 实现函数重载:函数重载是 C++ 的一项强大特性。通过使用引用参数,可以在函数签名中使用相同的参数类型,但实际上根据引用的类型选择不同的重载函数。

    1. void Swap(int& left, int& right)
    2. {
    3. int temp = left;
    4. left = right;
    5. right = temp;
    6. }

    需要注意的是,传递引用参数可能存在一些潜在的风险。例如,如果函数不需要修改传递的数据,应该使用常量引用来避免意外修改。此外,如果引用参数指向的对象在函数执行期间被销毁,会导致悬空引用的问题。因此,在使用引用参数时需要确保引用的有效性和生命周期。

    作为函数的返回值

    将 C++ 引用作为函数返回值有以下几个好处

    1. 避免拷贝:返回引用可以避免大型对象的拷贝操作,减少函数返回值的开销。

    2. 可链式操作:返回引用使得函数调用可以使用连续的点运算符进行链式操作。这样的语法更加简洁和直观,提高了代码的可读性。

    3. 支持函数重载:通过使用引用返回值,可以在函数签名中使用相同的返回类型,但根据不同的输入参数选择不同的重载函数。这是 C++ 函数重载的一个重要应用场景。

    4. 可以实现赋值操作的连续性:当返回引用时,可以将函数调用的结果直接赋值给变量,从而实现对返回值的连续操作。这在一些表达式操作和流式编程中非常有用。

    需要注意的是,返回引用时需要确保引用指向的对象在函数返回后仍然有效。如果引用指向的对象在函数结束后销毁,则返回悬空引用会导致未定义行为,下面看一个例子:

    1. int& Add(int a, int b)
    2. {
    3. int c = a + b;
    4. return c;
    5. }
    6. int main()
    7. {
    8. int ret = Add(1, 2);
    9. cout << "Add(1, 2) is :" << ret << endl;
    10. }

    这个代码的结果如下:

    但是我们知道

    在Add函数内部创建临时变量int c,函数返回值为c的引用,临时变量c出了函数栈帧销毁,这个返回值怎么还能对c进行引用???

    其实这是就是编译器的未定义行为

    对于Visual Studio而言,使用的默认编译器是 Microsoft Visual C++ 编译器(MSVC)

    在这个编译器内,该函数栈帧结束后销毁,但是由于没有其他函数对这个函数栈帧进行占用操作,使得任然保留了这个值,但这种行为是有风险,我们可以在Linux系统上使用g++编译看看:

    可以看到下方报了警告 warning: reference to local variable ‘c’ returned [-Wreturn-local-addr]

    所以对于这种行为,在每一种编译器下都有不同的结果,需要谨慎使用

    引用和指针的区别

    1. 语法和操作

      • 引用使用 & 符号进行声明,指向对象后无需使用符号进行间接访问,直接使用对象名即可。例如:int a = 5; int& ref = a; ref = 10;
      • 指针使用 * 符号进行声明,通过解引用操作符 * 访问指针指向的对象。例如:int a = 5; int* ptr = &a; *ptr = 10;
    2. 空值

      • 引用必须在声明时被初始化,并且不能指向空值(nullptr)。
      • 指针可以在声明时不进行初始化,允许指向空值(nullptr)。
    3. 重新赋值

      • 引用一旦被赋值,不能再引用其他对象。引用在初始化后,就一直引用该对象,不能更改引用的目标对象。
      • 指针可以重新赋值,可以在运行时改变指针指向的对象。
    4. 引用的安全性

      • 引用在使用时不需要进行空指针检查,因为引用必须在初始化时指向有效对象。
      • 指针需要进行空指针检查,避免解引用空指针导致的错误。
    5. 数组和函数的使用

      • 引用无法直接指向数组或函数,必须指向某个对象。
      • 指针可以指向数组或函数,并可以进行数组指针或函数指针的运算。

    但实际上,对于引用而言

    底层逻辑上可以被视为指针的一种语法糖。在编译器内部,引用通常被实现为指针的别名

    通过指针来实现引用的语义

    当我们声明一个引用时,编译器会将其转换为一个指针,并与所引用的对象关联起来

    在访问引用时,编译器会自动解引用指针,可以直接操作引用所指向的对象,就像操作该对象的直接别名一样

    我们可以通过下面的一个例子来理解:

    1. int main()
    2. {
    3. int a = 10;
    4. int b = 20;
    5. int& aa = a;
    6. int* pb = &b;
    7. }

    这里定义了两个变量a和b,变量a使用aa进行引用,变量b使用pb进行指针指向

    在反汇编内他们的实现是这样的:

    可以看到,引用与指针都是使用ptr实现的

    所以引用在底层实现上实际是有空间的,因为是按照指针方式来实现的


    内联函数

    理解内联函数

    以inline修饰的函数叫做内联函数

    编译时C++编译器会在调用内联函数的地方展开没有函数调用建立栈帧的开销

    内联函数提升程序运行的效率

    1. inline int add(int a, int b) {
    2. return a + b;
    3. }
    4. int main() {
    5. int result = add(5, 3); // 内联函数在这里直接展开,不会产生函数调用
    6. return 0;
    7. }

    inline是一种以空间换时间的做法

    如果编译器将函数当成内联函数处理,在编译阶段,会用函数体替换函数调用

    缺陷:可能会使目标文件变大

    优势:少了调用开销,提高程序运行效率

    需要注意的是:

    内联函数的展开是由编译器决定的,因此 inline 关键字只是对编译器的提示,不一定会被采纳

    编译器通常会根据函数的复杂度、调用频率和编译器策略等因素来决定是否将函数进行内联展开

  • 相关阅读:
    【GlobalMapper精品教程】030:栅格重采样案例教程(航测DSM)
    077:vue+openlayers加载geoserver发布的遥感影像,开启关闭AOI及影像(示例代码)
    深信服AC应用控制技术
    Spring Security JWT Authentication and Authorisation(一)
    RocketMQ系列——搭建Namesrv源码调试环境整理
    nacos配置mysql死活报 Nacos No DataSource set异常解决
    React 的 useContext 的使用
    STFT:解决音频-视频零样本学习 (ZSL) 中的挑战
    Android 数组适配器和简单适配器
    慢SQL排查定位
  • 原文地址:https://blog.csdn.net/m0_74483774/article/details/134041325