• 【C++入门基础】


    C++入门(输入与输出、函数重载、引用、命名空间、缺省参数、内联函数)

    一.命名空间

    相信很多小伙伴,在初学C++是都写过这样的代码:

    include<iostream>
    using namespace std;
    int main ()
    {
        cout<<"hello world!"<<endl;
        retuirn 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    他比c语言多了一个namespace的东西,那他到底是干嘛的呢?

    在C++中,变量、函数、类都是大量存在的,这些变量、函数和类的名称将都存在于全局作
    用域中,可能会导致很多冲突。使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突或名字
    污染
    ,namespace关键字的出现就是针对这种问题的 。

    1.命名空间的定义

    1. 普通的命名空间

      namespace N1 // N1为命名空间的名称
      {
          // 可以定义变量,也可以定义函数,还可以定义结构体
          int a;
          int Add(int left, int right)
          {
              return left + right;
          }
          struct Node
      	{
      		struct Node* next;
      		int val;
      	};
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
    2. 命名空间可以嵌套

      namespace N2
      {
          int a;
          int b;
          int Add(int left, int right)
          {
              return left + right;
          }
          namespace N3
          {
              int c;
              int d;
              int Sub(int left, int right)
              {
                  return left - right;
              }
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
    3. 同一个工程中允许存在多个相同名称的命名空间,编译器最后会合成同一个命名空间中。

      namespace N1 
      {
          int a;
          int Add(int left, int right)
          {
          return left + right;
          }
      }
      namespace N1//看似是两个,其实是一个
      {
          int Mul(int left, int right);
          {
              return left * right;
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15

    2.命名空间的使用

    1. 使用作用域限定符和命名空间

      namespace tzx
      {
      	// 命名空间中可以定义变量/函数/类型
      	int a = 0;
          int b = 1;
      	int Add(int left, int right)
      	{
      		return left + right;
      	}
      	struct Node
      	{
      		struct Node* next;
      		int val;
      	};
      
      int main()
      {
      	// 编译报错:error C2065: “a”: 未声明的标识符
          printf("%d\n", a);
          //用作用域限定符对a进行修饰,表示使用在tzx命名空间里的a,这样就不会报错了。
      	printf("%d\n", tzx::a);
      	return 0;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
    2. 使用 using关键字,可将命名空间的变量引入

      using tzx::b;
      int main()
      {
          printf("%d\n", tzx::a);
          printf("%d\n", b);
          return 0;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7

      ​ 这样就不需要在用tzx::去修饰b,可以直接使用

    3. using关键字还可以将命名空间直接引入

      using namespace tzx;
      int main()
      {
      	printf("%d\n", a);
      	printf("%d\n", b);
      	Add(10, 20);
      	return 0;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8

    ​ tzx命名空间里的变量、函数、结构体就都不用tzx::修饰了。

    ​ using namespace std;就是一个最好的例子。

    二.C++的输入与输出

    输入输出关键字的解释

    cin:c和in是分开读的,它是istream类型的对象

    >>:流提取运算符

    cout:同样,c和out是分开读的,它是ostream类型的对象

    <<:流插入运算符

    endl:特殊的C++符号,表示换行。

    cin、cout等标准库的定义和事项都包含在命名空间std里

    int main()
    {
    	printf("%d\n", a);
    	printf("%d\n", b);
    	cin >> a;
    	cout << a << endl;
    	Add(10, 20);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    cin和cout会自动识别类型,不再需要手动控制类型,使得输入输出更方便了。

    三.缺省参数

    1.概念

    声明函数的某个参数的时候为之指定一个默认值,在调用该函数的时候如果采用该默认值,你就无须指定该参数

    void Func(int a = 0)
    {
        cout<<a<<endl;
    }
    
    • 1
    • 2
    • 3
    • 4

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

    2.缺省参数的类别

    全缺省参数

    函数里的所有形参都有缺省值。

    void Func(int a = 10, int b = 20, int c = 30)
    {
    	cout << "a = " << a << endl;
    	cout << "b = " << b << endl;
    	cout << "c = " << c << endl;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    半缺省参数

    函数里的所有形参部分有缺省值。

    void Func(int a , int b = 20, int c = 30)
    {
    	cout << "a = " << a << endl;
    	cout << "b = " << b << endl;
    	cout << "c = " << c << endl;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    半缺省参数必须从右往左依次来给出,不能间隔着给

    //错误示范
    //1 隔着给
    void Func(int a = 10, int b, int c = 30)
    //2 未从右往左
    void Func(int a = 10, int b = 20, int c)
    
    • 1
    • 2
    • 3
    • 4
    • 5

    缺省参数不能在函数声明和定义中同时出现

    //a.h
    void Func(int a = 10);
    // a.cpp
    void Func(int a = 20)
    {}
    // 注意:如果生命与定义位置同时出现,恰巧两个位置提供的值不同,那编译器就无法确定到底该用那个缺省值。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    缺省值必须是常量或者全局变量

    四.函数重载

    1.概念

    函数重载:是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数
    的形参列表**(参数个数 或 类型 或 类型顺序)**不同,常用来处理实现功能类似数据类型不同的问题。

    // 1、参数类型不同
    int Add(int left, int right)
    {
    	cout << "int Add(int left, int right)" << endl;
    	return left + right;
    }
    double Add(double left, double right)
    {
    	cout << "double Add(double left, double right)" << endl;
    	return left + right;
    }
    // 2、参数个数不同
    void f()
    {
    	cout << "f()" << endl;
    }
    void f(int a)
    {
    	cout << "f(int a)" << endl;
    }
    // 3、参数类型顺序不同
    void f(int a, char b)
    {
    	cout << "f(int a,char b)" << endl;
    }
    void f(char b, int a)
    {
    	cout << "f(char b, int a)" << endl;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    仅返回类型不同是不能构成函数重载的

    2.函数重载的原理

    在C/C++中,一个程序要运行起来,需要经历以下几个阶段:预处理、编译、汇编、链接

    img

    1. 实际项目通常是由多个头文件和多个源文件构成,而通过C语言阶段学习的编译链接,我们可以知道,
      【当前a.cpp中调用了b.cpp中定义的Add函数时】,编译后链接前,a.o的目标文件中没有Add的函数地
      址,因为Add是在b.cpp中定义的,所以Add的地址在b.o中。那么怎么办呢?

    2. 所以链接阶段就是专门处理这种问题,链接器看到 a.o调用Add,但是没有Add的地址,就会到b.o的符号表中找Add的地址,然后链接到一起**。

    3. 那么链接时,面对Add函数,链接接器会使用哪个名字去找呢?这里每个编译器都有自己的函数名修饰
      规则。

    这个问题我们可以在Linux环境下,使用gcc和g++来看到,c/c++在链接时符号表的差别

    Linux的指令:

    img

    在gcc环境下:

    img

    在g++环境下:

    img

    通过上面我们可以看出gcc的函数修饰后名字不变。而g++的函数修饰后变成【_Z+函数长度+函数名+类
    型首字母】。

    这使不同形成列表的函数的命名也是不同的,也就是函数重载的原理。

    3.extern “C”

    在C 和 C++混合模式下开发,由于C和C++编译器对函数名字修饰规则不同,可能就会导致链接失败,在该种场景
    下,就需要使用extern “C”。在函数前加****extern “C”,意思是告诉编译器,将该函数按照C语言规则来编译。**

    1.calc.h
    #ifdef __cplusplus
    extern "C"
    {
    #endif
        int Add(int left, int right);
        int Sub(int left, int right);
    #ifdef __cplusplus
    }
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    条件编译的作用:

    ​ 如果是C++工程,编译器已经定义_cplusplus宏,编译时该宏是可以被识别的,被声明的函数就被
    extern "C"修饰了,此时C++编译就知道,静态库中的函数是按照C的方式编译的,这样在链接时就会按照C的方式找函
    数名字
    ​ 如果是C工程,编译器未定义_cplusplus宏,编译时该宏无法被识别,则条件编译就无效,函数就
    不会被extern "C"修饰

    2.calc.c
    #include "calc.h"
    int Add(int left, int right)
    {
    	return left + right;
    }
    int Sub(int left, int right)
    {
    	return left - right;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    3.test.cpp

    要将头文件和静态库导入到该工程文件中

    #include 
    using namespace std;
    #include "./../Debug/calc.h"
    #pragma comment(lib, "./../Debug/CalcLib.lib")
    int main()
    {
    	int ret = Add(10, 20);
    	cout << ret << endl;
    	ret = Sub(30, 20);
    	cout << ret << endl;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    如果在实现静态库时,函数没有使用extern "C"修饰 ,会报无法找到xxx函数的链接错误。

    五.引用

    1.概念

    给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它
    引用的变量共用同一块内存空间。

    int main()
    {
    	int a = 1;
    	int& b = a;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    引用类型必须和引用实体是同种类型的

    2.引用的特点

    1. 引用使用时必须初始化
    2. 引用只能引用一个实体
    3. 一个变量可以有多个引用
    4. 常量的引用前面也要加const

    3.引用的使用

    1.做参数

    和传参数的地址的作用是一样的,但就不需要形参,直接对实参操作,节省了空间。

    void Swap(int& left, int& right)
    {
    	int temp = left;
    	left = right;
    	right = temp;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    2.做返回值
    int& add(int a,int b)
    {
        static int c = a+b;
        return c;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    传引用返回时要注意:如果函数返回时,出了函数作用域,如果返回对象还在(还没还给系统),则可以使用引用返回,
    如果已经还给系统了,则必须使用传值返回。

    int& add(int a, int b)
    {
    	int c = a + b;
    	return c;
    	//return a + b;
    }
    
    int main()
    {
    	int& ret = add(3, 4);
    	add(1, 2);
    	cout << ret;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    该代码最终的输出结果是 :

    img

    add函数执行完后,它的栈帧会被系统回收,在函数内部定义的c就没有实际的意义了,但存c的数据的空间还在,只是没有了使用权,第二次执行add时,又提共了使用权,而ret是对c的空间的数据引用,也就跟着a+b的结果变成了3。

    所以传引用返回,是不能返回只属于函数栈帧的局部变量的。

    引用和指针的区别

    虽然在语法概念上引用就是一个别名,没有独立空间,和其引用实体共用同一块空间,但在底层实现上实际是有空间的,因为引用是按照指针方式来实现的。

    int main()
    {
    	int a = 10;
    	int& ra = a;
    	ra = 20;
    	int* pa = &a;
    	*pa = 20;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    汇编代码(本质上是一样的)

    img

    不同点:
    1. 引用概念上定义一个变量的别名,指针存储一个变量地址。

    2. 引用在定义时必须初始化,指针没有要求

    3. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型
      实体

    4. 没有NULL引用,但有NULL指针。

    5. 在sizeof中含义不同引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占
      4个字节)

    6. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小

    7. 有多级指针,但是没有多级引用

    8. **访问实体方式不同,**指针需要显式解引用,引用编译器自己处理

    9. 引用比指针使用起来相对更安全

    六.内联函数

    1.概念

    以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调用建立栈帧
    的开销,内联函数提升程序运行的效率。

    inline int Add(int left, int right)
    {
    	return left + right;
    }
    
    
    int main()
    {
    	int ret = 0;
    	ret = Add(1, 2);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    2.特点

    1. inline是一种以空间换时间的做法,缺陷:可能会使目标文件变大,优势:少了调用开销,提高程序运行效率。

    2. inline对于编译器而言只是一个建议,不同编译器关于inline实现机制可能不同。

    3. inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会
      找不到。

    // F.h
    #include 
    using namespace std;
    inline void f(int i);
    // F.cpp
    #include "F.h"
    void f(int i)
    {
    	cout << i << endl;
    }
    // main.cpp
    #include "F.h"
    int main()
    {
    	f(10);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    若用上述代码,编译器会报链接错误。

  • 相关阅读:
    C编程入门到精通 专辑目录
    嵌入式单片机无刷电机FOC控制与实现详解
    基于Dijkstra和A*算法的机器人路径规划(Matlab代码实现)
    图解LeetCode——828. 统计子串中的唯一字符(难度:困难)
    经典文献阅读之--Deformable DETR
    TypeScript - 枚举 - 数字枚举
    如何将 Docsify 项目部署到 CentOS 系统的 Nginx 中?
    C语言经典习题(异或思想)
    JavaScript高级 js语言通识
    软件测试面试真题 | Selenium 的工作原理是什么?
  • 原文地址:https://blog.csdn.net/m0_52882232/article/details/126482271