• 【C++】C++基础语法


     在学习C++之前,或者说任意一门语言的时候,我们都要了解并学习它不同于其他语言的语法知识,这些有利于我们后期的学习,也对开始学习C++有非常重要的作用,接下来让我们开始这段旅程吧!6268bec8574e4caab229f454442f9e63.gif


    前言

    C++兼顾C语言的语法,在C++上,可以编写C的代码!

    C++基础语法呢,有非常多的细节,需要大家慢慢来摸索,仔细的回顾,反复的复习!fc5c3e56397149b3aba854029405eb96.gif


    目录

    一、C++关键字(C++98)

    二、命名空间

    1.由来

    2.定义

     1.定义和初步了解

    2.命名空间的嵌套

    3.同一文件命名空间名相同时

    4.std官方库定义的命名空间

    三、C++的输入和输出 

     四、缺省参数

    1.定义

    2.分类

    3.缺省值在声明和定义中

     五、函数重载

    1.定义

    2.缺省函数与重载函数

    六、auto自动识别类型

    1.定义

    2.使用规则

      1.与引用结合

    2.注意:

    七、指针空值nullptr(C++11)

    八、内联函数

    总结


    一、C++关键字(C++98)

    在C语言的基础上多加了C语言没有的关键字,到后期边使用边学习,先大概看一眼!!

    1e433b8d081545f7abd5f7cc9368b6ce.png


    二、命名空间

    1.由来

         当我们定义一个变量时,会不会偶尔和库里面的函数名字相同??

         当我们协同完成一个项目时,你定义的变量会不会与其他人定义的变量名冲突???

         当然会,所以就会出现命名空间这个词,在学习命名空间前呢,我们得先了解一个关键字 namespace.

    举例说明:

    1. #include
    2. #include
    3. int rand = 10;
    4. int main()
    5. {
    6. printf("%d\n", rand);
    7. return 0; }
    8. // 编译后后报错:error C2365: “rand”: 重定义;以前的定义是“函数”

    649ba9e27e2b4c4fb1e74c6a8525eb44.png

    这个例子就是 rand于库函数中的rand函数重名,导致重定义

    C语言没办法解决类似这样的命名冲突问题,所以C++提出了namespace来解决


    2.定义

     1.定义和初步了解

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

    namespace +命名空间的名字

    {

        // 命名空间中可以定义变量  /  函数  /  类型

        //...... ;  

    }

    这是什么意思呢?

    ee6471fdf24345d1b9bcc43451fe2a81.pngd86eb66c4a0c4611af9a875745401227.png

    在使用变量时,默认查找规则:先局部,再全局

    图一是创建了命名空间bit,这会打乱默认查找规则,会直接到定义的rand的命名空间中找,即先找指定,所以输出的为10,且   命名空间名  +  : : +变量名/函数/类型,为域作用限定符,这样规定格式。

    图二则是没使用域作用限定符,会首先找局部,局部没有找全局,全局就是库函数中的rand函数了,所以是随机值。

     注意:若命名空间中,定义了结构体,域作用用符的使用是这样的:struct bit:: Node

    1. namespace bit
    2. {
    3. int rand = 10;
    4. int x = 1;
    5. int Add(int left, int right)
    6. {
    7. return left + right;
    8. }
    9. struct Node
    10. {
    11. struct Node* next;
    12. int val;
    13. };
    14. }
    15. int main()
    16. {
    17. //结构体
    18. struct bit::Node list;
    19. //函数和其他变量
    20. bit::Add();
    21. bit::rand=5;
    22. }

    那么加了namespace和直接定义到外面当全局,有什么区别呢?

    那就是为了防止命名冲突,加了namespace就相当于加了一堵围墙,别人不可以随意的访问里面的内容,只能通过 bit::这把钥匙来访问。


    2.命名空间的嵌套

    命名空间可以嵌套多层

    1. namespace N1
    2. {
    3. int a; //全局变量 在命名空间中,只有在自定义函数中,才是局部变量。其他是全局变量
    4. int b; //全局变量
    5. int Add(int left, int right)
    6. {
    7. return left + right;
    8. }
    9. namespace N2
    10. {
    11. int c; //全局变量
    12. int d; //全局变量
    13. int Sub(int left, int right)
    14. {
    15. return left - right;
    16. }
    17. }
    18. }
    19. int main()
    20. {
    21. N1::a=10;
    22. N1::N2::c=100;
    23. }
    '
    运行

    对于多层嵌套的命名空间用法就是这样的。


    3.同一文件命名空间名相同时

    同一个工程中允许存在多个相同名称的命名空间,编译器最后会合成同一个命名空间中。
    3cd63014b5994f6ab580965ef0af58cb.png
    就比如在官方库中,多个文件会定义相同的命名空间名,在Queue.h中,定义的为 bit,
    在Stack.h中,也是定义的bit,这会冲突吗??
    当然不会!!
    350121726cca46338a3f6f01ec423a0d.png
    在test.cpp中,调用那他们时,会在预处理阶段,将头文件展开,会直接合并命名空间名相同的命名空间!

    4.std官方库定义的命名空间

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

    C++中,我们使用的函数都在std命名空间里,所以在我们使用时,经常会这样:

    ef533274ff974481abd39d176005f458.png

    在使用时,会反反复复的去写域作用限定符,所以为了避免重复,c++就新出现 using namespace std;什么意思呢??

    之前我们说,命名空间就像围墙,把里面的东西围起来,需要钥匙打开,才可以使用里面的内容,

    于是  using namespace std;  就相当于把隔离围墙放开了,这下所有人都可以使用了,就还会出现 命名冲突,但是我们也可以把频率较高使用的单独放开围墙,这样我们就不需要重复去写

    cout是c++中的输出,相当于c的printf,所以将其单独放开的话,就是这样的:using std::cout;

    29ed39398ca042f980432fede709fc9f.png你懂了吗??87dcd4e9c3bf488388b76b4e84a286b8.gif

     当然,全部展开using namespace std是我们平时自己联系敲代码的时候可以这样!!


    三、C++的输入和输出 

    1.使用 cout 标准输出对象 ( 控制台 )cin 标准输入对象 ( 键盘 )时,必须 包含 < iostream > 头文件
    以及按命名空间使用方法使用std。
    2. cout和cin是全局的流对象,endl是特殊的C++符号,表示换行输出,他们都包含在包含<
    iostream >头文件中。
    3. << 是流插入运算符, >> 是流提取运算符
    4. 使用C++输入输出更方便,不需要像printf/scanf输入输出时那样,需要手动控制格式。
    C++的输入输出可以自动识别变量类型。

    直接举例:

    1. int main()
    2. {
    3. int a;
    4. cout << "请输入一个数字,按回车结束" << endl;
    5. //printf("请输入一个数字,按回车结束\n")
    6. cin >> a;
    7. //scanf("%d",&a);
    8. cout << a << endl;
    9. //printf("%d", a);
    10. return 0;
    11. }

    bd03dea0feee4a6ea24aaae122f9e09a.png


     四、缺省参数

    1.定义

    缺省参数是 声明或定义函数时为函数的 参数指定一个缺省值。在调用该函数时,如果没有指定实
    参则采用该形参的缺省值,否则使用指定的实参。
    3d2eda3562c14334a30002e76b478c19.png
    func 函数中的参数int a=10则为缺省参数,缺省参数的值是可以变的,没有指定参数时,会使用缺省参数,否则使用指定的实参。

    2.分类

    缺省参数分为半缺省全缺省,当然半缺省不是缺省一半的参数,半缺省必须传一个参数值,而全缺省不需要传值。

    半缺省(部分缺省),缺省参数只能从右向左连续缺省:

    void Func(int a,int b,int c =10)  可以  

    void Func(int a,int b=10,int c)   不可以

    void Func(int a,int b=10,int c=100)  可以

    传参时,从左往右给:

    void Func(int a,int b,int c =10) 

    Func(10,100);

    3.缺省值在声明和定义中

    缺省参数不能在函数声明和定义中同时出现
    在 .h中,void Func(int a=10) ;
    在.cpp中,void Func(int a=100) {;}
    若出现声明和定义中都有,那就会出现分歧,当声明和定义中都有, 规定缺省值只能在声明中出现
    在 .h中,void Func(int a=10) ;
    在.cpp中,void Func() {;}  // 可以

     五、函数重载

    在C语言中,我们会出现这种情况:

    1. int Add(int m, int n)
    2. {
    3. return m + n;
    4. }
    5. double Add(double m, double n)
    6. {
    7. return m + n;
    8. }
    9. int main()
    10. {
    11. int a=9; //a=9.99
    12. int b=10; //b=8.88
    13. Add(a,b);
    14. }
    '
    运行

    对于同样的函数功能,当参数类型不同的时候,我们需要再去写一个函数,而且还不能同名,如果重名,编译器不会通过,但如果在C++中,就可以使用,这叫做 函数重载。

    1.定义

    函数重载: 是函数的一种特殊情况, C++ 允许在 同一作用域中 声明几个功能类似 的同名函数 ,这
    些同名函数的 形参列表 ( 参数个数 或 类型 或 类型顺序 ) 不同 ,常用来处理实现功能类似数据类型
    不同的问题。
    总之一句话,函数名相同,参数不同。参数不同包括,参数个数,参数类型,参数顺序。
    1. void f()
    2. {
    3. cout << "f()" << endl;
    4. }
    5. void f(int a)
    6. {
    7. cout << "f(int a)" << endl;
    8. }

    顺序不同要注意的是:

    1. void f(int a, char b)
    2. {
    3. cout << "f(int a,char b)" << endl;
    4. }
    5. void f(char b, int a)
    6. {
    7. cout << "f(char b, int a)" << endl;
    8. }
    9. 他们属于函数重载,顺序不同本质就是类型不同,与变量名没有关系
    10. void f(int a, int b)
    11. {
    12. cout << "f(int a,char b)" << endl;
    13. }
    14. void f(int b, int a)
    15. {
    16. cout << "f(int a,char b)" << endl;
    17. }
    18. 这两个就不属于函数重载,类型的顺序还是int,int

    那怎么调用呢??函数重载可以支持自动识别类型

    2.缺省函数与重载函数

    1. void f()
    2. {
    3. cout << "f()" << endl;
    4. }
    5. void f(int a=10int b=100)
    6. {
    7. cout << "f(int a,int b)" << endl;
    8. }

    函数名相同,参数不同就可以构成 函数重载,但在调用时,f()这样调用会报错,发生歧义。


     那么,函数重载是怎么进行的呢??

    下面会简单的让大家理解这个过程。

    在调用函数时,我们会找函数的地址,来调用它

    那么如何找到它的地址呢??

    就是通过符号表来找到的,在linux编译C++中,它是这样进行的:

    函数名都叫  f  所以都是_z1f,第一个函数的参数是int,所以是_z1fi  (int),以此类推,第二个则是i c

    ,第三个是c i,所以这就很容易的找到了函数名,并找到它的地址,再调用。

    那么,就会有这样一个问题,参数不同构成函数重载,那我要返回值不同构成函数重载可以吗??

    是因为函数名修饰规则没有带返回值的原因吗??

    就是在符号表中函数名这里,再添加不同的返回值所代表的符号不就可以了吗??

    当然不行!!

    那是因为,我们在调用函数时,只可以指定它的参数,但无法指定他的返回值!!

    是不是没有想到??


    六、auto自动识别类型

    1.定义

    auto可以自动识别类型,举例说明:

    int a=10;

    auto b=a;

    //这样就可以自动识别类型,来确定b的类型,当然auto针对的还是较长的类型

    比如:

    std::map m{ { "apple", "苹果" }, { "orange", "橙子" },{ "pear", "梨" } };
    //这个类型我们现在不需要知道
    
    // auto是方便类型下面的地方
    //std::map::iterator it = m.begin();
      auto it = m.begin();  会自动识别it类型,无需重复复杂类型
    

    2.使用规则

      1.与引用结合

    auto 声明指针类型时,用 auto auto* 没有任何区别,但用 auto 声明引用类型时则必须
    &
    1. int main()
    2. {
    3.    int x = 10;
    4.    auto a = &x;
    5.    auto* b = &x;
    6.    auto& c = x;
    7.    cout << typeid(a).name() << endl;
    8.    cout << typeid(b).name() << endl;
    9.    cout << typeid(c).name() << endl;
    10.    return 0;
    11. }

    typeid(变量名),可以拿到变量类型的字符串。结果显示如下:

    2.注意:

     引用只是起别名,本质上,还是变量本身的类型。

    在同一行使用auto推导类型时,只能是相同类型的。
    void TestAuto ()
    {
        auto a = 1 , b = 2 ;
        auto c = 3 , d = 4.0 ;   // 该行代码会编译失败,因为 c d 的初始化表达式类型不同
    }
               auto 不能作为函数的参数,会无法对参数进行类型推导
               auto 不能直接用来声明数组

    3. 基于范围的for循环(C++11)

    对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因

    C++11 中引入了基于范围的 for 循环。 for 循环后的括号由冒号 分为两部分:第一部分是范
    围内用于迭代的变量,第二部分则表示被迭代的范围
    1. void TestFor()
    2. {
    3. int array[] = { 1, 2, 3, 4, 5 };
    4. for(auto& e : array) //auto 后面的e是可以变得,只不过习惯是e,element
    5.     e *= 2;
    6. for(auto e : array)
    7.     cout << e << " ";
    8. cout<
    9. }
    10. void TestFor()
    11. {
    12. int array[] = { 1, 2, 3, 4, 5 };
    13. 若将&引用去掉,那么则无法改变数组中的元素,只是改变了存在e中的元素
    14. for(auto e : array)
    15.     e *= 2;
    16. for(auto e : array)
    17.     cout << e << " ";
    18. }
    19. int main()
    20. {
    21. TestFor();
    22. TestFor1();
    23. }

     引用详细在这里

    但对于下面这种情况,就是错误的:

    void TestFor ( int array [])
    {
        for ( auto & e : array )
            cout << e << endl ;
    }
    这里的array只是地址,因为传数组时,只能将其首元素就是地址传来。

    七、指针空值nullptr(C++11)

    在C语言中,指针为空时为NULL;

    NULL实际是一个宏,在传统的C头文件(stddef.h)中,

    NULL可能被定义为字面常量0,或者被定义为无类型指针(void*)的常量。不论采取何
    种定义,在使用空值的指针时,都不可避免的会遇到一些麻烦。
    ​​​​​​​
    void f ( int )
    {
    cout << "f(int)" << endl ;
    }
    void f ( int* )
    {
    cout << "f(int*)" << endl ;
    }                                                     ​​​​​​​

    int main ()
    {
    f ( 0 );
    f ( NULL );                                 
    f (( int* ) NULL );
    return 0 ;
                                             
    在C++98中,字面常量0既可以是一个整形数字,也可以是无类型的指针(void*)常量,但是编译器
    默认情况下将其看成是一个整形常量,如果要将其按照指针方式来使用,必须对其进行强转(void *)0。

     所以,在C++中,就重新定义了nullptr,为(void*)类型

    注意:
    1. C++11 中, sizeof(nullptr) sizeof((void*)0) 所占的字节数相同。
    2. 为了提高代码的健壮性,在后续表示指针空值时建议最好使用 nullptr

    八、内联函数

    在我们编译代码的时候,总会有一些短小的代码,但需要我们反复去调用,那么调用函数就会建立栈帧,但是宏可以解决这样的问题,预先定义好宏,在预处理时,都会被替换直接展开,不需要写函数。

    但是宏有几个缺点,不能调试;没有类型安全检测;容易写错!!

    所以才会有内联函数来替代宏,让我们更方便。

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

     加了内联:

     call是调用函数,加了内联以后,无需建立栈帧。

    那么是不是我们以后可以随随便便加内联,都把需要的展开??

    首先当然不是,内联针对的是,代码少,但是需要经常调用,而且,你加了内联,只是像编译器说明,发出的一个请求,具体编译器要不要展开,人家自己考虑,可以忽略你这个请求!

    比如,有的代码代码很长,如果它有100行代码,要调用100次,那你展开岂不是需要100*100行代码??所以编译器是不会随随便便展开的。

    总结:

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

    ​​​​​​​

     调用func1时,找不到符号表,直接报错。

    所以最好的方式就是,定义和声明在一起,找的时候,会直接在上面的定义中调用。


    总结

    基础的语法知识细节很多,需要我们去仔细去学习,在后续学习中,这些必要的语法知识是非常重要的!!我们下期再见!

  • 相关阅读:
    IT运维大咖带你玩转企业信息运维自动化
    PMP(Project Management Professional)证在哪个行业比较有用?
    Shell 基本运算符
    React——谈谈属性与状态
    2. 慢查询、索引、执行计划详解
    力扣(LeetCode)算法_C++——至多包含两个不同字符的最长子串
    Docker——入门实战
    公司大佬推荐测试进阶书单
    java基础10题
    高精度时间测量(TDC)电路MS1022
  • 原文地址:https://blog.csdn.net/ChaoFreeandeasy_/article/details/127062169