• [C++] C++入门


    ☃️个人主页fighting小泽
    🌸作者简介:目前正在学习C++和Linux
    🌼博客专栏:C++入门
    🏵️欢迎关注:评论👊🏻点赞👍🏻留言💪🏻

    0. 本节知识点安排目的

    C++是在C的基础之上,容纳进去了面向对象编程思想,并增加了许多有用的库,以及编程范式等。熟悉C语言之后,对C++学习有一定的帮助,本章节主要目标:

    1. 补充C语言语法的不足,以及C++是如何对C语言设计不合理的地方进行优化的,比如:作用域方面、IO方面、函数方面、指针方面、宏方面等。
    2. 为后续类和对象学习打基础

    1. C++关键字(C++98)

    C++总计63个关键字,C语言32个关键字
    ps:下面我们只是看一下C++有多少关键字,不对关键字进行具体的讲解。后面我们学到以后再细讲。

    在这里插入图片描述
    注: 因为c++是兼容c语言的所以里面会有很多熟悉的关键字,用法还是跟以前一样的。

    2.命名空间

    在C/C++中,变量、函数和后面要学到的类都是大量存在的,这些变量、函数和类的名称将都存在于全局作用域中,可能会导致很多冲突。

    使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突或名字污染,namespace关键字的出现就是针对这种问题的

    #include 
    #include 
    int rand = 10;
    //假如不包头文件,rand只是一个普通的全局变量
    //带上头文件,rand就是一个函数了,名字就冲突了
    // C语言没办法解决类似这样的命名冲突问题,所以C++提出了namespace来解决
    int main()
    {
     printf("%d\n", rand);
    return 0;
    }
    // 编译后后报错:error C2365: “rand”: 重定义;以前的定义是“函数”
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    2.1命名空间的定义

    定义命名空间,需要使用到namespace关键字,后面跟命名空间的名字,然后接一对{}即可,{}中即为命名空间的成员。

    // lz是命名空间的名字,一般开发中是用项目名字做命名空间名。
    // 1. 正常的命名空间定义
    namespace lz
    {
     // 命名空间中可以定义变量/函数/类型
     int rand = 10;
     
     int Add(int left, int right)
     {
     return left + right;
     }
    
     struct Node
     {
     struct Node* next;
     int val;
     };
    }
    
    //2. 命名空间可以嵌套
    // test.cpp
    namespace N1
    {
    int a;
    int b;
    int Add(int left, int right)
     {
         return left + right;
     }
    namespace N2
     {
         int c;
         int d;
         int Sub(int left, int right)
         {
             return left - right;
         }
     }
    }
    //3. 同一个工程中允许存在多个相同名称的命名空间,编译器最后会合成同一个命名空间中。
    // ps:一个工程中的test.h和上面test.cpp中两个N1会被合并成一个
    // test.h
    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
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    注意:一个命名空间就定义了一个新的作用域,命名空间中的所有内容都局限于该命名空间中

    2.3命名空间的使用

    命名空间中成员该如何使用呢?比如:

    namespace N
    {
     // 命名空间中可以定义变量/函数/类型
     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);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    命名空间的使用有三种方式:

    • 加命名空间名称及作用域限定符
    int main()
    {
        printf("%d\n", N::a);
        return 0;    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 使用using将命名空间中某个成员引入
    using N::b;
    int main()
    {
        printf("%d\n", N::a);
        printf("%d\n", b);
        return 0;    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 使用using namespace 命名空间名称 引入
    using namespce N;
    int main()
    {
        printf("%d\n", N::a);
        printf("%d\n", b);
        Add(10, 20);
        return 0;    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    3. C++输入&输出

    学习c++,当然要见见我们的老朋友hello world ,那么它是怎样输出的呢?

    #include
    
    using namespace std;
    int main()
    {
    	cout<<"Hello world!!!"<<endl;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    1. 注意:std是C++标准库的命名空间名,C++将标准库的定义实现都放到这个命名空间中
    2. 使用cout标准输出对象(控制台)和cin标准输入对象(键盘)时,必须包含< iostream >头文件以及按命名空间使用方法使用std。
    3. cout和cin是全局的流对象,endl是特殊的C++符号,表示换行输出,他们都包含在包含< iostream >头文件中。
    4. <<是流插入运算符,>>是流提取运算符。
    5. 使用C++输入输出更方便,不需要像printf/scanf输入输出时那样,需要手动控制格式。C++的输入输出可以自动识别变量类型。
    6. 实际上cout和cin分别是ostream和istream类型的对象,>>和<<也涉及运算符重载等知识,
    #include 
    using namespace std;
    int main()
    {
       int a;
       double b;
       char c;
         
       // 可以自动识别变量的类型
       cin>>a;
       cin>>b>>c;
         
       cout<<a<<endl;
       cout<<b<<" "<<c<<endl;
       return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    std命名空间的使用惯例:
    std是C++标准库的命名空间,如何展开std使用更合理呢?

    1. 在日常练习中,建议直接using namespace std即可,这样就很方便。
    2. using namespace std展开,标准库就全部暴露出来了,如果我们定义跟库重名的类型/对象/函数,就存在冲突问题。该问题在日常练习中很少出现,但是项目开发中代码较多、规模大,就很容易出现。所以建议在项目开发中使用,像std::cout这样使用时指定命名空间 + using std::cout展开常用的库对象/类型等方式。

    4. 缺省参数

    4.1 缺省参数概念

    缺省参数是声明或定义函数时为函数的参数指定一个缺省值。在调用该函数时,如果没有指定实参则采用该形参的缺省值,否则使用指定的实参。

    void Func(int a = 0)
    {
     cout<<a<<endl;
    }
    int main()
    {
    	Func();     // 没有传参时,使用参数的默认值
     	Func(10);   // 传参时,使用指定的实参
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    4.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 = 10, int c = 20)
     {
         cout<<"a = "<<a<<endl;
         cout<<"b = "<<b<<endl;
         cout<<"c = "<<c<<endl;
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    注意:

    1. 半缺省参数必须从右往左依次来给出,不能间隔着给
    2. 缺省参数不能在函数声明和定义中同时出现,通常写在声明中
    3. 缺省值必须是常量或者全局变量
    4. C语言不支持(编译器不支持)

    5. 函数重载

    5.1 函数重载概念

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

    #include
    using namespace std;
    // 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;
    }
    int main()
    {
     Add(10, 20);
     Add(10.1, 20.2);
     f();
     f(10);
     f(10, 'a');
     f('a', 10);
     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
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    C++支持函数重载的原理是根据名字修饰的

    6. 引用

    6.1 引用概念

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

    类型& 引用变量名(对象名) = 引用实体;

    void TestRef()
    {
        int a = 10;
        int& ra = a;//<====定义引用类型
        printf("%p\n", &a);
        printf("%p\n", &ra);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    6.2 引用特性

    1. 引用在定义时必须初始化
    2. 一个变量可以有多个引用
    3. 引用一旦引用一个实体,再不能引用其他实体
    void TestRef()
    {
       int a = 10;
       // int& ra;   // 该条语句编译时会出错
       int& ra = a;
       int& rra = a;
       printf("%p %p %p\n", &a, &ra, &rra);  
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    6.3 常引用

    void TestConstRef()
    {
        const int a = 10;
        //int& ra = a;   // 该语句编译时会出错,a为常量
        const int& ra = a;
        // int& b = 10; // 该语句编译时会出错,b为常量
        const int& b = 10;
        double d = 12.34;
        //int& rd = d; // 该语句编译时会出错,类型不同
        const int& rd = d;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    6.4 使用场景

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

    2.做返回值

    int& Count()
    {
       static int n = 0;
       n++;
       // ...
       return n;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    下面代码输出什么结果?为什么?

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

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

    6.5 传值、传引用效率比较

    以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直接返回,而是传递实参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,效率是非常低下的,尤其是当参数或者返回值类型非常大时,效率就更低。

    6.6引用和指针的区别

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

    我们来看下引用和指针的汇编代码对比:

    在这里插入图片描述
    引用和指针的不同点:

    1. 引用概念上定义一个变量的别名,指针存储一个变量地址。
    2. 引用在定义时必须初始化,指针没有要求
    3. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体
    4. 没有NULL引用,但有NULL指针
    5. 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节)
    6. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
    7. 有多级指针,但是没有多级引用
    8. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
    9. 引用比指针使用起来相对更安全

    7. 内联函数

    7.1 概念

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

    如果在函数前增加inline关键字将其改成内联函数,在编译期间编译器会用函数体替换函数的调用。
    在这里插入图片描述

    7.2 特性

    1. inline是一种以空间换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会用函数体替换函数调用,缺陷:可能会使目标文件变大,优势:少了调用开销,提高程序运行效率。
    2. inline对于编译器而言只是一个建议,不同编译器关于inline实现机制可能不同,一般建议:将函数规模较小(即函数不是很长,具体没有准确的说法,取决于编译器内部实现)、不是递归、且频繁调用的函数采用inline修饰,否则编译器会忽略inline特性。:
    3. inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到

    【面试题】
    宏的优缺点?
    优点:

    1. 增强代码的复用性。
    2. 提高性能。

    缺点:

    1. 不方便调试宏。(因为预编译阶段进行了替换)
    2. 导致代码可读性差,可维护性差,容易误用。
    3. 没有类型安全的检查 。

    C++有哪些技术替代宏?

    1. 常量定义 换用const enum
    2. 短小函数定义 换用内联函数

    8. auto关键字(C++11)

    auto用于主动推导类型

    int main()
    {
        int x = 10;
        auto a = &x;
        auto* b = &x;
        auto& c = x;
        cout << typeid(a).name() << endl;
        cout << typeid(b).name() << endl;
        cout << typeid(c).name() << endl;
        *a = 20;
        *b = 30;
         c = 40;
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    注意auto不能作为函数的参数,因为类型不明。auto不能声明数组

    9. 范围for

    范围for用于遍历数组,依次取array数组中的数据赋值给e,自动迭代到结束。

    void TestFor()
    {
    int array[] = { 1, 2, 3, 4, 5 };
    for(auto& e : array)
         e *= 2;
    for(auto e : array)
         cout << e << " ";
    return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    注意范围for循环迭代的范围必须是确定的:
    对于数组而言,就是数组中第一个元素和最后一个元素的范围;对于类而言,应该提供begin和end的方法,begin和end就是for循环迭代的范围。

    void TestFor(int array[])
    {
        for(auto& e : array)
            cout<< e <<endl;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    函数传参,传进来的不是数组是指针,for循环迭代范围不明。

    10. 关键字nullptr

    在C++中NULL被定义为常量0,而不是指针类型(void*)

    #ifndef NULL
    #ifdef __cplusplus
    #define NULL   0
    #else
    #define NULL   ((void *)0)
    #endif
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    这样的话传参可能会出现错误

    在这里插入图片描述

    1. 在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引入的。
    2. 在C++11中,sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同。
    3. 为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr。
  • 相关阅读:
    Maven配置阿里云镜像
    nmap各种扫描的注意事项
    【无标题】Java 函数式接口
    智能宠物喂食器方案软硬件设计
    君方智能设计平台-对象持久化技术方案
    微软官方开源免费的Blazor UI组件库 - Fluent UI Blazor
    介绍 TensorFlow 的基本概念和使用场景。
    聊聊volatile作用,原理
    Server is too busy _解决方法和httpRuntime代码放在web.config哪
    熟练使用 Redis 的五大数据结构:Java 实战教程
  • 原文地址:https://blog.csdn.net/qq_65673848/article/details/133977490