zoukankan      html  css  js  c++  java
  • C++ 入门

     我根据所学,将入门分为十个小部分。如果有秃道同袍发现不对之处,还望多多指正,感激不尽。

    1.关键字:对于关键字的学习,是学习编程语言的基础,这些关键字的含义和使用方式组成了基础的语法部分,此处不列举关键字,关键字的学习是贯穿整个语法部分的学习过程的。C++98里是63个关键字,C++11中73个。(C语言中32个关键字)。

    2.命名空间:命名空间的引入是为了解决命名冲突。如果把所有变量、函数、类,名字都放在同一作用域,很容易出现傻傻分不清的情况,如果将它们放在不同的作用域,就能避免冲突。比如一个家庭里,兄弟两个的名字是一样的,会出现不知道叫的是谁这样的问题,如果同名的人在不同的地方,就很容易区分了,如西安小C,南京小C,虽然都叫小C,但是能区分叫的是谁。命名空间内的内容都局限于该命名空间。

    使用关键字namespace定义命名空间。

    namespace N1  //N1是命名空间的名字
    {
        //在括号内定义的变量、函数、类就是该命名空间的成员
        int a = 10;
        int Add(int left, int right)
        {
            return left + right;
        }
    
    }   //不加;号

    命名空间可以嵌套定义

    namespace N2
    {
        int a = 5;
        int* a = &a;
        namespace N3  //嵌套的命名空间,命名空间N2内的命名空间N3
        {
            int a = 10;
            int *a = &a;
        }
    }

    同一作用域下的同名命名空间会合并成为一个命名空间,若它们内部存在同名成员则会出现错误。不在同一作用域下的同名命名空间不会合并为一个。

     对于命名空间内的成员的使用, 第一种方式:命名空间的名字+作用域限定符

    namespace N1 
    {
        int a = 10;
        int Add(int left, int right)
        {
            return left + right;
        }
    }  
    
    int main()
    {
        int x = 11, y = 15;
        int sum;
        sum = N1::Add(x, y);
    cout << N1::a << endl;
    return 0; }

    第二种方式:使用 using将命名空间内的成员引入

    using N1::a;
    int main()
    {
        cout << a << endl;  //输出a
        return 0;
    }

    第三种方式:using namespace N1; 

    using namespace N1;
    // int a   //错误,此时N1的内容相当于全局变量,不可在同一作用域再有同名成员
    int main()
    {
        //int a = 5; //和N1中的a不是同一作用域,可以使用,且优先使用该局部变量
        cout << a << endl;  //输出a
        cout << N1::a << endl; //有同名局部变量时,用命名空间+作用域限定符来说明使用的是哪个变量
        return 0;
    }

    3.输入与输出:cout<<标准输出(控制台),cin>>标准输入(键盘),使用时要包含头文件<iostream>,以及标准命名空间 std。某些旧版本编译器支持使用<iostream.h>头文件。

    #include<iostream>
    using namespace std;
    
    int main()
    {
        cout << "hello world" << endl; //endl为换行
        return 0;
    }
    int main()
    {
        int a;
        cin >> a; //单个输入a
    
        int b;
        char c;
        cin >> b >> c;  //连续输入b 和 c
    
        cout << a << endl;//单个输出a ,endl为换行
        cout << a << " " << b << " " << c << endl; //连续输出a 空格 b 空格 c
         
        return 0;
    }

    有关于输入输出的缓冲区问题此处暂时不讨论。

    4.缺省参数:缺省参数是声明或定义函数时为函数的参数指定一个默认值。在调用该函数时,如果没有指定实参则采用该 默认值,否则使用指定的实参。(备胎,车上放一个备用轮胎……)

    几种情况如代码所示:

    int Add(int left, int right)
    {
        return left + right;
    }
    int main()
    {
        int a = 5, b = 6;
        int c = Add(a, b);
        // int d = Add(); //调用的函数得形参没有给出默认值,不传递参数无法调用函数
        return 0;
    }
    int Add(int left=10, int right=11)
    {
        return left + right;
    }
    int main()
    {
        int a = 5, b = 6;
        int c = Add(a, b);  //  c 的结果为11 ,传递实参则用实参的值
         int d = Add();    // d的结果为21,不传递实参则使用给出的默认值(缺省值)
        return 0;
    }

    函数的每个形参都给出缺省值,称为全缺省参数。

    int Add(int fir=1, int sec=3,int thr=5 )
    {
        return fir+sec+thr;
    }
    int main()
    {
        int a = 5, b = 6,c=7;
        int x = Add();    // x的结果为9
        int y = Add(a);   // y的结果是13
        int z = Add(a, b); // z的结果是16
        int n = Add(a, b, c); // n的结果是18
        return 0;
    }

    可以发现函数传递参数是从形参列表的左边依次到右边。

    只给函数的部分形参默认值,称为半缺省参数。

    //int Add(int fir=1, int sec=3,int thr ) //编译报错
    //{
    //    return fir+sec+thr;
    //}
    
    //int Add(int fir = 1, int sec, int thr) //编译报错
    //{
    //    return fir + sec + thr;
    //}
    
    //int Add(int fir = 1, int sec, int thr=3) //编译报错
    //{
    //    return fir + sec + thr;
    //} 
    
    //int Add(int fir , int sec, int thr) //编译通过
    //{                                   //没有给缺省值的函数
    //    return fir + sec + thr;
    //}
    
    //int Add(int fir, int sec = 3, int thr=5) //编译通过
    //{
    //    return fir + sec + thr;
    //}
    
    int Add(int fir, int sec , int thr = 5) //编译通过
    {
        return fir + sec + thr;
    }

    由于函数参数的传递方式是从左到右,所以决定了半缺省参数,只能从右往左给出,且中间不能间隔。

    缺省值必须是常量或者全局变量。 (C语言不支持缺省值)。

    缺省值不能再声明和定义中同时给出,声明或者定义二者选一个地方给出。

    5.函数重载:同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数、类型、或顺序)必须不同,用来处理功能类似,数据类型不同的问题。

    int Add(int left, int right)
    {
        return left + right;
    }
    double Add(double  left, double right)
    {
        return left + right;
    }
    int Add(int left, char right)
    {
        return left + right;
    }
    int main()
    {
        int a = 3, b = 5;
        int c = Add(a, b);
        cout << c << endl;
    
        double x = 11.1, y = 22.2;
        double z = Add(x, y);  //调用整形的Add会损失精度
        cout << z << endl;
        return 0;
    }

    实现函数重载的原理是,底层对函数编译的时候对函数名进行了修饰,将参数列表里的内容也使用了,所以能实现重载。C语言不支持函数重载,C语言底层对函数的修饰规则是在函数名前加下划线,如add,修饰为_add。VS下对C++工程中的函数名的修饰规则:

    关键字extern:外部符号引入

    extern   "C"   将某些函数按照C的风格来编译,在函数前加extern "C",意思是告诉编译器, 将该函数按照C语言规则来编译。

    extern "C" int Add(int left, int right) //按C语言方式编译,如果只给出声明,不给定义,会发现底层函数名为_Add
    {
        return left + right;
    };

    6.引用:引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它 引用的变量共用同一块内存空间。(在底层引用使用指针的方式实现的。)

    引用的定义方式:

    int main()
    {
        int a = 10;
        int& ra = a; // 定义引用类型,引用与被引用的实体必须是同种类型
        auto& rA = a; //可以使用auto来定义引用
        return 0;
    }

    引用的特性:

    1、引用在定义的时候必须初始化,不能出现空引用。

    2、一个变量可以有多个引用,但是一个引用,只能引用一个实体,并且不能再引用其它实体。

    int main()
    {
        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;
        return 0;
    }

    引用可以作为函数形参和返回值类型。

    //引用做形参
    void swap(int& left, int& right)
    {
        int temp = left;
        left = right;
        right = temp;
    }
    int main()
    {
        int a = 4, b = 7;
        swap(a, b);   //能该表外部实参  此处交换函数如果传值不能完成外实参的交换,传指针也可完成实参交换
        return 0;
    }
    //引用做返回值
    int& test(int a)
    {
        a += 3;
        return  a;
    }

    如果函数返回时,离开函数作用域后,其栈上空间已经还给系统,因此不能用栈上的空间作为引 用类型返回。如果以引用类型返回,返回值的生命周期必须不受函数的限制(即比函数生命周期长)。

    传引用的效率高于传值,因为传值要对参数进行拷贝,传引用不需要对参数进行拷贝。

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

    引用与指针的区别:

    1.引用在定义的时候必须初始化,指针没有要求。

    2.引用在引用一个实体后,不能再引用其它实体,而指针可以随时改变指向的实体(类型不变)。

    3.没有NULL引用,有NULL指针。

    4.在sizeof中的含义不同,引用的结果,是其引用实体的类型大小,指针始终是地址空间,一个整形大小。

    5.引用做自加操作是引用的实体的增加1,指针自加是指针向后偏移一个类型大小。

    6.没有多级指针,有多级指针。

    7.访问实体的方式不同,对引用进行的操作就是对实体操作,编译器自己处理,指针访问实体时需要解引用。

    8.引用比指针更安全,不会出现空引用。

    7.内联函数:以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数压栈的开销, 内联函数提升程序运行的效率。函数前增加inline关键字将其改成内联函数,在编译期间编译器会用函数体替换函数的调用。内联函数是一种对编译器的建议,编译器可能不会将其编译为内联函数。

    inline int Add(int left, int right)
    {
        return left + right;
    }
    int main()
    {
        int a = 4, b = 7;
        int c = Add(a, b); //在此处替换为函数体
        cout << c << endl;
        return 0;
    }

    特性:

    1. inline是一种以空间换时间的做法,省去调用函数额开销。所以代码很长或者有循环/递归的函数不适宜 使用作为内联函数。 
    2. inline对于编译器而言只是一个建议,编译器会自动优化,如果定义为inline的函数体内有循环/递归等 等,编译器优化时会忽略掉内联。

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

    宏的优缺点:1、增强代码的复用性。2、提高性能。

    宏的缺点:1、不方便调试。2、可读性差、可维护性差,容易误用。3、没有类型安全检查,可能会出现副作。

    C++中替换宏的技术:

    1、常量定义,使用const修饰

    2、函数定义,使用inline修饰,将函数写为内联函数

    8.auto关键字

     在早期C/C++中auto的含义是:使用auto修饰的变量,是具有自动存储器的局部变量,但遗憾的是一直没有 人去使用它。

    C++11中,标准委员会赋予了auto全新的含义即:auto不再是一个存储类型指示符,而是作为一个新的类型 指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。

    使用auto定义变量时必须对其进行初始化,在编译阶段编译器需要根据初始化表达式来推导auto的实际类 型。因此auto并非是一种“类型”的声明,而是一个类型声明时的“占位符”,编译器在编译期会将auto替换为 变量实际的类型。

    auto的使用细则 :
    1. auto与指针和引用结合起来使用 用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&

    int main()
    {
        int a = 11;
        auto b = a;
        auto c = 'c';
        auto d = Add();
        //auto e;  //报错,使用auto定义变量时必须初始化
        //auto arr[10]; //报错,auto不能定义数组
        int* pa1 = &a;
        auto pa2 = pa1;
        auto* pa3 = pa1; //指针类型,*写不写都一样
    
        int& ra1 = a;
        auto& ra2 = a;//引用必须加&
        return 0;
    }

    2. 在同一行定义多个变量 当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对 第一个类型进行推导,然后用推导出来的类型定义其他变量。

    void test(auto a) //报错,不能使用auto作为形参类型,因为编译器无法对a的实际类型进行推导
    {}
    int main()
    {
        auto a = 1, b = 2;
        //auto c = 3, d = 4.0; //报错,类型必须一致
        //auto arr[10]; //报错,auto不能定义数组
        return 0;
    }

    3. 为了避免与C++98中的auto发生混淆,C++11只保留了auto作为类型指示符的用法。

    4. auto在实际中常见的优势用法就是跟以后会讲到的C++11提供的新式for循环,还有lambda表达式等 进行配合使用。

    5. auto不能定义类的非静态成员变量(暂不做讲)。

    6. 实例化模板时不能使用auto作为模板参数(暂不做讲)。

    9.基于范围的for循环

    对于数组,我们的遍历方式有传统的循环和范围for循环

    int main()
    {
        int array[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
        for (int i = 0; i < 10; i++) // 传统循环
            cout << array[i] << " ";
        cout << endl;
    
        for (auto e : array)  //范围for循环,可用来遍历、循环赋值等
            cout << e << " ";  //范围for的使用,在STL中,迭代器的使用此处暂不讲
        cout << endl;
        return 0;
    }

    相比于传统循环,范围for代码更加简洁。

     对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因此C++11中 引入了基于范围的for循环。for循环后的括号由冒号“ :”分为两部分:第一部分是范围内用于迭代的变量, 第二部分则表示被迭代的范围。与普通循环类似,可以用continue来结束本次循环,也可以用break来跳出整个循环。

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

    2. 迭代的对象要实现++和==的操作。

    10.指针空值--nullptr:

           在良好的C/C++编程习惯中,声明一个变量时好给该变量一个合适的初始值,否则可能会出现不可预料的错误,比如未初始化的指针。如果一个指针没有合法的指向,我们基本都是按照如下方式对其进行初始化 :

        int* p1 = NULL;  //在C中初始化没有合法指向的指针的方式是将0赋值给指针
        int* p2 = 0;     //NULL是一个宏,结果是0,或者((void*)0)

    在使用空值指针的时候会遇到一些麻烦,例如重载单参的函数,将值为NULL的指针传递给函数,函数调用会不明确。

           程序本意是想通过f(NULL)调用指针版本的f(int*)函数,但是由于NULL被定义成0,因此与程序的初衷相悖。 在C++98中,字面常量0既可以是一个整形数字,也可以是无类型的指针(void*)常量,但是编译器默认情况下 将其看成是一个整形常量,如果要将其按照指针方式来使用,必须对其进行强转(void *)0。

            为了考虑兼容性,C++11并没有消除常量0的二义性,C++11给出了全新的nullptr表示空值指针。C++11为什 么不在NULL的基础上进行扩展,这是因为NULL以前就是一个宏,而且不同的编译器厂商对于NULL的实现可 能不太相同,而且直接扩展NULL,可能会影响以前旧的程序。因此:为了避免混淆,C++11提供了 nullptr,即:nullptr代表一个指针空值常量。nullptr是有类型的,其类型为nullptr_t,仅仅可以被隐式转 化为指针类型,nullptr_t被定义在头文件中:

    typedef decltype(nullptr) nullptr_t;

    注意:
    1. 在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引入的。

    2. 在C++11中,sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同。

    3. 为了提高代码的健壮性,在后续表示指针空值时建议好使用nullptr

    住进火焰就成为萤火虫。
  • 相关阅读:
    LFYZ-OJ ID: 1008 求A/B高精度值
    高精度运算
    【2018国庆雅礼集训】部分题解
    【模板】倍增求LCA
    Luogu1516 青蛙的约会
    loj #10043. 「一本通 2.2 例 1」剪花布条
    我太菜了
    Luogu1280 尼克的任务
    Luogu1091 合唱队形
    Luogu1006 传纸条
  • 原文地址:https://www.cnblogs.com/fengkun/p/11863816.html
Copyright © 2011-2022 走看看