zoukankan      html  css  js  c++  java
  • C/C++知识点

     const

    作用

      1修饰变量,说明变量不可以被改变

      2修饰指针,分为指向常量的指针和指针常量

      3常量引用,经常用于形参类型,即避免了拷贝,又避免了函数对值的修改

      4修饰成员函数,说明该成员函数不能修改成员变量

    使用

     1 //
     2 class A
     3 {
     4   private:
     5    const int a;
     6  public:
     7      // 构造函数
     8     A() { };
     9     A(int x) : a(x) { };        // 初始化列表
    10 
    11     // const可用于对重载函数的区分
    12     int getValue();             // 普通成员函数
    13     int getValue() const;       // 常成员函数,不得修改类中的 何
    14                                     //数据成员的值     
    15 } ; 
    16 
    17 void function()
    18 {
    19     // 对象
    20     A b;                        // 普通对象,可以调用全部成员函数
    21     const A a;                  // 常对象,只能调用常成员函数、更新常成员变量
    22     const A *p = &a;            // 常指针
    23     const A &q = a;             // 常引用
    24 
    25     // 指针
    26     char greeting[] = "Hello";
    27     char* p1 = greeting;                // 指针变量,指向字符数组变量
    28     const char* p2 = greeting;          // 指针变量,指向字符数组常量
    29     char* const p3 = greeting;          // 常指针,指向字符数组变量
    30     const char* const p4 = greeting;    // 常指针,指向字符数组常量
    31 }
    32 
    33 // 函数
    34 void function1(const int Var);           // 传递过来的参数在函
    35 //数内不可变
    36 void function2(const char* Var);         // 参数指针所指内容为常量
    38 void function3(char* const Var);         // 参数指针为常指针
    39 void function4(const int& Var);          // 引用参数在函数内
    40 //为常量
    41 
    42 // 函数返回值
    43 const int function5();      // 返回一个常数
    44 const int* function6();     // 返回一个指向常量的指针变量,使用:
    46 const int *p = function6();
    47 int* const function7();     // 返回一个指向变量的常指针,使用:
    48 int* const p = function7();  

    static

    作用

      1 修饰普通变量,修改变量的存储区域和生命周期,使变量存储在静态区,在main函数运行前就分配了空间,如果又初始值就用初始值初始化它,如果没有初始值系统用默认值初始化它。

      2 修饰普通函数,表明函数的作用范围,仅在定义该函数的文件内才能使用。在多人开发项目时,为了防止与他人命名函数重名,可以将函数定位为static

      3 修饰成员变量,修饰曾元变量使所有的对象只保存一个变量,而且不需要生成对象就可以访问该成员。

      4 修饰成员函数,修饰成员函数使得不需要生成对象就可以访问该函数,但是在static函数内不能访问非静态成员。

    this指针

      1this指针是一个隐含于每个非静态成员函数中的特殊指针。它指向正在被该成员函数操作的那个对象。

      2当对一个对象调用成员函数时,编译程序先将对象的地址赋给this指针,然后调用成员函数,每次成员函数存取数据成员时,由隐含使用this指针

      3当一个成员函数被调用时,自动向他传递一个隐含的参数,该参数是一个指向这个成员函数所在的对象的指针。

      4this指针被隐含的声明为:className *const this, 这意味这不能给this指针赋值;在ClassName类的const成员函数中,this指针的类型为:const ClassName* const,这说明不能对this指针所指向的这种对象是不能修改的(即不能对这种对象的数据成员景象赋值操作);

      5 this并不是一个常规变量,而是一个右值,所以不能取得this的地址(不能&this)。

      6 在以下场景中,经常需要显式引用this指针:

      为实现对象的链式引用

      为避免统一对象进行赋值操作

      在实现一些数据结构时,如list

    inline内联函数

    特征

      相当于把内联函数里面的内容卸载调用内联函数处

      相当于不用指向进入函数 的步骤,直接执行函数体;

      相当于宏,却比宏多了类型检查,真正具有函数特征

      不能包含循环,递归,switch等复杂操作;

      在类声明中定义的函数,除了虚函数的其他函数都会自动隐式的当成内联函数。

    // 声明1(加 inline,建议使用)
    inline int functionName(int first, int secend,...);
    // 声明2(不加 inline)
    int functionName(int first, int secend,...);
    // 定义
    inline int functionName(int first, int secend,...) {/****/};
    
    // 类内定义,隐式内联
    class A {
        int doA() { return 0; }         // 隐式内联
    }
    
    // 类外定义,需要显式内联
    class A {
        int doA();
    }
    inline int A::doA() { return 0; }   // 需要显式内联

    编译器对inline函数的处理步骤

      1 将inline函数体复制到inline函数调用点处;

      2 为所用inline函数中的局部变量分配内存空间

      3将inline函数的输入参数和返回值映射到调用方法的局部变量空间中;

      4如果inlin函数有多个返回点,将其转变为inline函数代码块末尾的分支(使用goto)

    优缺点

    优点

      1内联函数同洪函数一样将在被调用处进行代码展开,省去了参数压栈,栈帧开辟与回收,结果返回等,从而提高程序运行速度

      2内联函数相比宏函数来说,在代码展开时,会做安全检查或自动类型转换(同普通函数),而宏定义则不会。

      3在类中声明同时定义的成员函数,自动转化为内联函数,因此内联函数可以访问类的成员变量,宏定义则不能。

      4内联函数在运行时可调试,而宏定义不可以

    缺点

      1代码膨胀。内联是以代码膨胀(复制)为代价,消除函数调用带来的开销。如果执行函数体内代码的时间,相比于函数调用的开销较大,那么效率的收获会很少。另一方面,每一处内联函数的调用都要复制代码,将使程序的总代码量增大,消耗更多的内存空间。

      2inlin函数无法随着函数库升级而升级,inlin函数改变需要重新编译,不像non-inline可以直接链接。

      3是否内联,程序员不可控。内联函数只是对编译器的建议,是否函数内联,决定权在于编译器。

    虚函数(virtual)可以是内联函数(inline)吗?

      虚函数可以是内联函数,内联是可以修饰虚函数的,但是当虚函数表现多态性的时候不能内联。

      内联是在编译器建议编译器内联,而虚函数的多态性在运行期,编译器无法直到运行期间调用那个代码,因此虚函数表现为多态时(运行期)不可以内联。

      inline virtal 唯一可以捏脸的时候是:编译器知道所调用的对象是那个类(如Base::who()),这只有在编译器具有实际对象而不是对象的指针或引用时才会发生。

     1 #include<iostream>
     2 using namespace std;
     3 class base
     4 {
     5    public:
     6        inline virtual void who()
     7         {
     8            cout<<"i AM BASE
    ";
     9     
    10          } 
    11         virtual ~Base() {}
    12 };    
    13 
    14 class Derived : public Base
    15 {
    16 public:
    17     inline void who()  // 不写inline时隐式内联
    18     {
    19         cout << "I am Derived
    ";
    20     }
    21 };
    22 
    23 int main()
    24 {
    25     // 此处的虚函数 who(),是通过类(Base)的具体对象(b)来调用的,编译期间就能确定了,所以它可以是内联的,但最终是否内联取决于编译器。 
    26     Base b;
    27     b.who();
    28 
    29     // 此处的虚函数是通过指针调用的,呈现多态性,需要在运行时期间才能确定,所以不能为内联。  
    30     Base *ptr = new Derived();
    31     ptr->who();
    32 
    33     // 因为Base有虚析构函数(virtual ~Base() {}),所以 delete 时,会先调用派生类(Derived)析构函数,再调用基类(Base)析构函数,防止内存泄漏。
    34     delete ptr;
    35     ptr = nullptr;
    36 
    37     system("pause");
    38     return 0;
    39 } 

    assert()

    断言,是宏,而非函数。assert宏的原型定义在<assert.h>(C)、<assert>(C++)中,其作用是如果它的条件返回错误,则终止程序执行。可以通过NDEBUG来关闭assert,但是需要在源代码的开头,include<assert.h>之前。

    1 #define NDBUG  //这行可以使assert不可用
    2 #include<assert.h>
    3 assert(p!=NULL);//assert不可用

    sizeof()

      sizeof对数组,得到整个数组所占空间大小

      sizeof对指针,得到指针本身所占空间大小

    pragme pack(n)

    设定结构体,联合以及类成员变量以n字节方式对其

    #pragma pack(push)//保存对齐状态
    #pragma pack(4)//设定为4字节对齐
    struct test
    {
        char m1;
        double m4;
        int m3;
        char c1;
    };
    #pragma pack(pop)//恢复对齐方式

    test t1;
    cout << sizeof(t1) << endl; //结果为20

    位域

    bit mode: 2;//mode 占 2 位

    类可以将其(非静态)数据成员定义为位域(bit-field),在一个位域中含有一定数量的二进制位。当一个程序需要向其他程序或硬件设备传递二进制数据时,通常会用到位域。

      -位域在内存中的布局时与机器有关的

      -位域的类型必须是整形或枚举类型,带符号类型中的位域的行为将因具体实现而定

      -取地址运算符(&)不能作用于位域,任何指针都无法指向类的位域;

    volatile

    volatile int i= 10;

      volatile关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素(操作系统,硬件,其他线程等)更改。所以使用volatile告诉编译器不应对这样的对象经行优化。

      volatile关键字声明的变量,每次访问时都必须从内存中取出值(没有被volatile修饰的变量,可能由于编译器的优化,从CPU寄存器中取值)

      const 可以是 volatile(如只读的状态寄存器)

      指针可以是volatile

    extern "C"

    被extern限定的函数或变量是extern类型的

    被extern “C”修饰的变量和函数是按照C语言方式编译和链接的

    可以必满C++因为符号修饰导致代码不能和C语言库中的符号进行链接的问题。

    #ifdef __cplusplus
    extern "C" {
    #endif
    
    void *memset(void *, int, size_t);
    
    #ifdef __cplusplus
    }
    #endif

    struct 和 typedef struct

    c中

    typedet struct student

    {

    int age;

    }s;

    等价于

    struct student{

      int age;

    };

    typedef struct Student S;

    此时 S 等价于 struct Student,但两个标识符名称空间不相同。

    另外还可以定义与 struct Student 不冲突的 void Student() {}

    // cpp
    struct Student { 
        int age; 
    };
    
    void f( Student me );       // 正确,"struct" 关键字可省略

    二、若定义了与 Student 同名函数之后,则 Student 只代表函数,不代表结构体,如下:

    typedef struct Student { 
        int age; 
    } S;
    
    void Student() {}           // 正确,定义后 "Student" 只代表此函数
    
    //void S() {}               // 错误,符号 "S" 已经被定义为一个 "struct Student" 的别名
    
    int main() {
        Student(); 
        struct Student me;      // 或者 "S me";
        return 0;
    }

    C++中的struct和class

    总的来说,struct更合适看成是一个数据结构的实现体,class更适合看成是一个对象的实现体;

    区别

      最本质的一个区别就是默认的访问权限

        默认的继承访问权限。struct是public的,class是private

        struct作为数据结构的实现体,

    union 联合

    联合union是一种节省空间的特殊的类,一个union可以由多个数据成员,但是在任意时刻只能由一个数据成员有值。当某个成员被赋值后其他成员变为未定义状态。联合有如下特点:

      -默认访问控制符位public

      -可以含有构造函数,析构函数

      -不能含有引用类型的成员

      -不能继承自其他类,不能作为基类

      -不能含有虚函数

      -匿名union在定义所在作用域可以直接访问union成员

      -匿名union不能包含protected成员或private成员

      -全局匿名联合必须是静态(static)的

    #include<iostream>
    using namespace std;
    union uniontest {
        uniontest() :i(10) {};//拷贝构造 i为10
        int i;
        double d;
    };
    static union {
        int i;
        double d;
    };
    int main() {
        uniontest u;
        union {//匿名联合的定义
            int i;
            double d;
        };
        cout << u.i << endl;//输出uniontest联合的10
        ::i = 20;
        cout << ::i << endl;//输出全局静态匿名联合的20
        i = 30;
        cout << i << endl;//输出局部匿名联合的30
        system("pause");
        return 0;
    }

    explicit(显式)构造函数

    explicit修饰的构造函数可以用来防止隐式转换

    explicit使用

    #include<iostream>
    using namespace std;
    class test1 {
    public:
        test1(int n) {
            num = n;
        }
    private:
        int num;
    };
    class test2 {
    public:
        explicit test2(int n)
        {
            num = n;
        }
    private:
        int num;
    };
    
    int main() {
        test1 t1 = 12;//隐式调用其构造函数,成功
        //test2 t2 = 12;//编译错误,不能隐式调用其构造函数
        test2 t2(12);//显式调用成功
        system("pause");
        return 0;
    }

    friend友元类和友元函数

    能访问私有成员

    破坏封装性

    友元关系不可传递

    友元关系的单向性

    友元声明的形式及数量不受限制

    using

    using声明

    一条using声明语句一次只引入命名空间的一个成员,它使得我们可以清楚的知道程序中所引用的到底是那个名字。如:using namespace_name::name;

    构造函数的using声明(c++11)

    在c++11中,派生类能够重用其直接基类定义的构造函数

    class derived:base{
    public:
       using base::base;
       //.....  
    };

    如上using声明,对于基类的每个构造函数,编译器都生成一个与之对应(形参列表完全相同)的派生类构造函数。生成如下类型构造函数:

    derived(parms):base(args){ }

    尽量少使用using指示 污染命名空间

    一般来说,使用using明亮比使用using编译命令更安全,这是由于它只导入了制定的名称。如果该名称与局部名称发生冲突,编译器将发出指示。using编译命令导入所有的名称,包括可能并不需要的名称。如果与局部名称发生冲突,则局部名称将覆盖名称空间版本,而编译器不会发出警告。另外,名称空间的开放性意味着名称空间的名称可能分散在多个地方,这使得难以准确的知道添加了那些名称。

    ::范围解析运算符

    分类
    1全局作用符(::name):用于类型名称(类,类成员,成员函数,变量等),表示作用域为全局命名空间
    2类作用域符(class::name):用于表示制定类型的作用域范围是具体某个类的
    3命名空间作用域符(namespace::name):用于表示指定类型的作用域范围是具体某个命名空间的
    ::使用
    int count = 0;//全局(::)的count
    class A{
    public:
          static int count;//类A的count
    };
    int main(){
          ::count = 1;//设置全局的count的值为1
          A::count = 2//设置类A的count为2     
          int count = 0;    // 局部的 count
          count = 3;        // 设置局部的 count 的值为 3
          return 0;
    }

    enum枚举类型

    限定作用域的枚举类型

    enum class open_modes{imput,output,append};

    不限定作用域的枚举类型

    enum color{red ,yellow,green};

    enum{floatPrec=6,doublePrec = 10};

    decltype

    decltype关键字用于检查实体的声明类型或表达式的类型及值分类。语法:

    decltype(expression)

    decltype使用

    // 尾置返回允许我们在参数列表之后声明返回类型
    template <typename It>
    auto fcn(It beg, It end) -> decltype(*beg)
    {
        // 处理序列
        return *beg;    // 返回序列中一个元素的引用
    }
    // 为了使用模板参数成员,必须用 typename
    template <typename It>
    auto fcn2(It beg, It end) -> typename remove_reference<decltype(*beg)>::type
    {
        // 处理序列
        return *beg;    // 返回序列中一个元素的拷贝
    }

    引用

    左值引用

    常规引用,一般表示对象的身份

    右值引用

    右值引用就是必须绑定到右值(一个临时对象,将要销毁的对象)的引用,一般表示对象的值。

    右值引用可实现转移语义(move sementics)和精确传递(perfect forwarding),它的主要目的有两个方面:

      消除两个对象交互时不必要的对象拷贝。节省运算存储资源,提高效率

      能够更简洁明确的定义泛型函数

    引用折叠

    • X& &X& &&X&& & 可折叠成 X&
    • X&& && 可折叠成 X&&

    宏定义可以实现类似于函数的功能,但是它始终不是函数,而宏定义中括弧中的参数也不是真的参数,在宏展开的时候参数进行的是一对一的替换。

    成员初始化列表

    好处

    更高效:少了一次调用牧人构造函数的过程

    有些场合必须要用初始化列表:

      常量成员,因为常量只能初始化不能赋值,所以必须放在初始化列表里面

      引用类型,引用必须在定义的时候初始化,并且不能重新赋值,所以也要写在初始化列表里面

      没有默认构造函数的类类型,因为使用初始化列表可以不必调用默认构造函数来初始化,而是直接调用拷贝构造函数初始化。

    initializer_list列表初始化 C++11

    用花括号初始化器列表,列表初始化一个对象,其中对应构造函数接受一个std::initoalizer_list参数

    #include<iostream>
    #include<vector>
    #include<initializer_list>
    template<class T>
    struct s {
        std::vector<T> v;
        s(std::initializer_list<T> l) :v(l) {
            std::cout << "constructed with a" << l.size() << "-element list
    ";
    
        }
        void append(std::initializer_list<T> l) {
            v.insert(v.end(), l.begin(), l.end());
        }
        std::pair<const T*, std::size_t>c_arr() const {
            return{ &v[0],v.size() };//在return语句中复制列表初始化,这不适用std::initializer_list
        }
    };
    template<typename T>
    void templated_fn(T) {}
    int main() {
        s<int> s = { 1,3,2,4,5 };//复制初始化
        s.append({ 6,7,8 });//函数调用中的列表初始化
        std::cout << "size is now" << s.c_arr().second << " ints:
    ";
        for (auto n : s.v)
            std::cout << n << ' ';
        std::cout << '
    ';
        std::cout << "range-for over brace-init-list:
    ";
        for (int x : {-1, -2, -3})
            std::cout << x << ' ';
        std::cout << std::endl;
        auto al = { 10,11,12 };
        std::cout<< "The list bound to auto has size() = " << al.size() << '
    ';
        // templated_fn({1, 2, 3}); // 编译错误!“ {1, 2, 3} ”不是表达式,
                                 // 它无类型,故 T 无法推导
        templated_fn<std::initializer_list<int>>({ 1,2,3 });//ok
        templated_fn<std::vector<int>>({ 1,2,3 });
        system("pause");
        return 0;
    }
  • 相关阅读:
    LeetCode "Jump Game"
    LeetCode "Pow(x,n)"
    LeetCode "Reverse Linked List II"
    LeetCode "Unique Binary Search Trees II"
    LeetCode "Combination Sum II"
    LeetCode "Divide Two Integers"
    LeetCode "First Missing Positive"
    LeetCode "Clone Graph"
    LeetCode "Decode Ways"
    LeetCode "Combinations"
  • 原文地址:https://www.cnblogs.com/road-of-mike/p/10089240.html
Copyright © 2011-2022 走看看