美文网首页
C++ 总结 (一、基础篇)

C++ 总结 (一、基础篇)

作者: xiaoyouPrince | 来源:发表于2020-08-11 22:41 被阅读0次

    C++ 总结 (一、基础篇)

    官网

    C++ 完全兼容C语言,但是有自己的语法特点,本文总结了C++的基础知识。记录如下

    基本输入输出

    命名空间。using namespace std; 或者使用 std::cout;

    cout << "nihao";
    cin >> a;
    getline(cin, string);

    函数 functions

    通常函数(C/C++),函数调用是值传递,外部实参将变量的值传给形参,函数内部创建新变量并copy实参的值。函数内部的操作不会影响外部实参。

    function_arguments.png

    引用传递的函数,C 和 C++ 不同,C语言中是参数类型为指针,如下void duplicate (int* a, int* b, int* c),函数内部根据指针获取外部变量。C++ 中有优化,如下 void duplicate (int& a, int& b, int& c) 两种方式都是引用传递。

    function_by_reference.png

    C++ 中是直接引用传递,函数内部参数与外界实参是同一个,此类函数通常用来修改外界变量值。

    效率问题
    采用值传递会copy传入的变量值,赋值给函数内部形参。这个操作对于基本数据类型没效率问题。
    对于 string 等符合类型有效率问题,这种情况使用引用传递

    如果不允许函数内部改写外部值,可在参数声明时加入 const 声明

    // 基本类型值传递
    void duplicate (int a, int b, int c)
    // 基本类型引用传递
    void duplicate (int& a, int& b, int& c) 
    
    // 符合类型值传递 - 有效率问题
    string concatenate (string a, string b)
    {
      return a+b;
    }
    
    // 符合类型引用传递 - 内部可以直接修改外部变量
    string concatenate (string& a, string& b)
    {
      return a+b;
    }
    
    // 符合类型引用传递 - 内部不可修改外部变量
    string concatenate (const string& a, const string& b)
    {
      return a+b;
    }
    

    内联函数

    C++ 中关键字 inline, 用于函数声明之前,它不会改变函数本身的行为。

    它的调用机制与一般的函数调用机制不同,会在调用的时候直接将函数展开(减少压栈,提高效率)执行。

    inline string concatenate (const string& a, const string& b)
    {
      return a+b;
    }
    

    注意
    inline 说明符只是告诉编译器此函数可以内联
    事实上,现代编译器为了优化效率会在合适的时机将函数内联,inline 说明符只是建议内联,编译器仍旧会根据实际情况才处理(如上函数会内联,如操作巨复杂的函数即时写了 inline 仍旧不会内联)。
    C++ 的内联操作本身也是委托给编译器处理的,编译器有最终的决定权。

    函数重载与模板函数

    函数重载 overload : 只函数名相同,但是参数类型不同,事实上是不同函数的函数。只有返回值类型不同不构成重载,系统会根据调用时候传入的参数类型找到正确的函数调用。

    int operate (int a, int b)
    {
      return (a*b);
    }
    
    double operate (double a, double b)
    {
      return (a/b);
    }
    

    上面 operate 就是被重载成了两个函数,实际上上开发中并不需要这样做。通常对同样的函数,传入不同参数我们希望是同样的操作,这可以用模板函数来实现,格式

    // 格式
    template <template-parameters> function-declaration
    
    // egg:class 是为了指定 typename,事实上可以class 可以换成 typename 关键字
    template <class SomeType>
    SomeType sum (SomeType a, SomeType b)
    {
      return a+b;
    }
    
    // 使用
    #include <iostream>
    using namespace std;
    
    template <class T>
    T sum (T a, T b)
    {
      T result;
      result = a + b;
      return result;
    }
    
    int main () {
      int i=5, j=6, k;
      double f=2.0, g=0.5, h;
      k=sum<int>(i,j);
      h=sum<double>(f,g);
      cout << k << '\n';
      cout << h << '\n';
      return 0;
    }
    

    还有一种无类型模板函数。类似函数中默认值。只是在模板函数中的默认类型是在编译期间确定的,使用的时候只能传递一个常量,在运行时是不可以改变的,如下。

    #include <iostream>
    using namespace std;
    
    // 第一个参数为模板指定参数,第二个参数为编译时期确定
    template <class T, int N>
    T fixed_multiply (T val)
    {
      return val * N;
    }
    
    int main() {
      // 使用时,只需要传第一个参数,第二个参数只能是常数
      std::cout << fixed_multiply<int,2>(10) << '\n';
      std::cout << fixed_multiply<int,3>(10) << '\n';
    }
    

    名称可见性与命名空间

    变量可以创建在不同作用域内: 全局/局部

    在同一个作用域内,只有一个实体可以使用某个具体的名字。C++ 中引入了命名空间概念来包含其他作用域的声明的实体,最著名的为 std. 通过使用 using namespace std; 可以在自己的代码中引用标准库中定义的函数、变量声明。

    命名空间可分开定义,它可以贯穿整个源码使用。命名空间可以设置别名。namespace new_name = current_name;

    // using namespace example
    #include <iostream>
    using namespace std;
    
    namespace first
    {
      int x = 5;
    }
    
    namespace second
    {
      double x = 3.1416;
    }
    
    int main () {
      {
        using namespace first;
        cout << x << '\n';
      }
      {
        using namespace second;
        cout << x << '\n';
      }
      return 0;
    }
    
    // ----- 
    5
    3.1416
    

    变量的存储类型

    变量的存储类型

    • 静态存储类型, 全局变量 - 程序生命周期只有一份内存,会被初始化为 0
    • 动态存储类型, 局部变量 - 运行时在作用域内随机分配的内存,内存存在随机数据(包括0)

    全局变量和命名空间作用域的变量 -> 其内存是在整个程序生命周期中分配的。它会被初始化为 0.
    局部变量是在其作用域内声明和使用的,在运行时中同样的内存可能被分配个多个变量,其值为不确定的(包括0)

    数组

    C++ 完全继承了 C 语言内建的数组功能。数组就是连续同一种类型的内存,可以通过下标访问。其声明格式type name [elements];,通常会在声明的时候进行初始化值,如 int foo [5] = { 16, 2, 77, 40, 12071 }; C++ 中允许声明赋值的时候不显示写明数组长度。

    int foo[] = { 10, 20, 30 };
    int foo[] { 10, 20, 30 };
    
    // 多维数组 - 本质上在内存中也是整体一维。分配15 个 int 长度。
    int jimmy [3][5];
    
    // 数组传参 - 最外围数组只传递指针,需要额外的数组长度的参数。
    void procedure (int myarray[])
    void procedure (int myarray[][3][4])
    

    数组作为参数的时候,由于无法复制的原因,会传递数组的指针,所以参数为数组的函数需要额外加一个数组长度的参数。多维数组入参,其外围数组可以无需标明数组容量,但是其他子集需要指明其容量。

    C++ 标准库数组

    C++ 继承了 C 语言的数组,同时也提供了标准库的数组实现。与C语言区别如下

    • 需要引入标准库头文件 #include <array>
    • 数组声明(基于模板函数的方式)
    • 提供了 .size() 等方便访问其长度的方法
    ----- 使用语言内建的数组 继承自 C   
    #include <iostream>
    using namespace std;
    
    int main()
    {
      int myarray[3] = {10,20,30};
    
      for (int i=0; i<3; ++i)
        ++myarray[i];
    
      for (int elem : myarray)
        cout << elem << '\n';
    }
    
    ----- 使用 C++ 标准库中的数组,
    #include <iostream>
    #include <array>
    using namespace std;
    
    int main()
    {
      array<int,3> myarray {10,20,30};
    
      for (int i=0; i<myarray.size(); ++i)
        ++myarray[i];
    
      for (int elem : myarray)
        cout << elem << '\n';
    }
    

    字符串

    字符串实际上就是字符数组。简单定义:char foo[20]; 声明并开辟了一个 20 char 长度的连续空间存储一序列字符,但默认其空间的值都是为定义的。它的容量可以存储“Hello”也可以存储“Merry Christmas”, 字符串默认尾部会加一个null结束符,其字符表示为\0

    c_strings1.png c_strings2.png

    字符串声明并初始化 && 字面量字符串

    // 声明并初始化 - 隐式指定字符串长度 - 末尾 '\0' 结尾
    char myword[] = { 'H', 'e', 'l', 'l', 'o', '\0' }; 
    
    // 字面量字符串 - 字面量字符串也是默认以 '\0' 结尾
    char myword[] = "Hello"; 
    

    注意
    因为字符串本质是字符数组,具有数组的限制: 它只能在初始化的时赋值,不能在之后对其变量进行赋值
    当然其内部值可以变,如 myword[2] = 'w';

    C++ 标准库中的 Strings

    普通字符数组类型的字符串是 C 语言字符串,C++ 标准库定义了 string 类来表示字符串,但是我们使用的字符串字面量的形式仍然是 C 语言风格的字符串,其结尾依旧是 '\0';

    在 C++ 标准库中两种字符串是共存且可以互相转换的。如下

    // strings and NTCS:
    #include <iostream>
    #include <string>
    using namespace std;
    
    int main ()
    {
      char question1[] = "What is your name? ";
      string question2 = "Where do you live? ";
      char answer1 [80];
      string answer2;
      cout << question1;
      cin >> answer1;
      cout << question2;
      cin >> answer2;
      cout << "Hello, " << answer1;
      cout << " from " << answer2 << "!\n";
      return 0;
    }
    ----------------------------
    What is your name? Homer
    Where do you live? Greece
    Hello, Homer from Greece!
    
    ----------------------------------
    char myntcs[] = "some text";
    string mystring = myntcs;  // convert c-string to string
    cout << mystring;          // printed as a library string
    cout << mystring.c_str();  // printed as a c-string 
    
    注意:string 类型的 c_str() 和 data() 两个方法是完全相等的
    

    指针

    指针是一种变量,存储其同类变量的地址值。如int *ptr = &a

    操作系统中内存是以字节为单位连续存储的。 定义的变量名是可以访问某个内存单位的名字,程序可以直接通过变量名访问对应的虚拟内存。

    变量被声明时,系统会将创建变量所需的内存地址赋值给变量名(换言之:变量名就是变量在内存中首地址)。

    取地址符(&) 和接触引用操作符 ()*

    • & is the address-of operator, and can be read simply as "address of"
    • * is the dereference operator, and can be read as "value pointed to by"
    foo = &myvar;
    
    myvar = 25;
    foo = &myvar;
    bar = myvar;
    
    reference_operator.png
    // baz 等于 foo 指针指向的值 
    baz = *foo;
    
    dereference_operator.png

    定义指针与简单使用

    指针定义 type *ptr;

    使用如下

    // more pointers
    #include <iostream>
    using namespace std;
    
    int main ()
    {
      int firstvalue = 5, secondvalue = 15;
      int * p1, * p2;
    
      p1 = &firstvalue;  // p1 = address of firstvalue
      p2 = &secondvalue; // p2 = address of secondvalue
      *p1 = 10;          // value pointed to by p1 = 10
      *p2 = *p1;         // value pointed to by p2 = value pointed to by p1
      p1 = p2;           // p1 = p2 (value of pointer is copied)
      *p1 = 20;          // value pointed to by p1 = 20
      
      cout << "firstvalue is " << firstvalue << '\n';
      cout << "secondvalue is " << secondvalue << '\n';
      return 0;
    }
    

    指针与数组

    数组本质上是一串连续内存。其数组名即其首元素的地址,即数组的指针

    int myarray [20];
    int * mypointer;
    
    // 数组名赋值给指针 - 是合法的
    mypointer = myarray;
    
    // 数组名内存只能指向声明时分配的内存 - 操作非法
    myarray = mypointer;
    

    因为指针是可以指向其他地址的,mypointer 可以重新赋值。但是 myarray 只能指向声明时候分配的标识数组的 20 个地址,并且永远无法改变。所以无法给 myarray 赋值,如上。

    指针指向数组首地址,其用法与数组一样,如下

    // more pointers
    #include <iostream>
    using namespace std;
    
    int main ()
    {
      int numbers[5];
      int * p;
      p = numbers;  *p = 10;
      p++;  *p = 20;
      p = &numbers[2];  *p = 30;
      p = numbers + 3;  *p = 40;
      p = numbers;  *(p+4) = 50;
      for (int n=0; n<5; n++)
        cout << numbers[n] << ", ";
      return 0;
    }
    // --- 10, 20, 30, 40, 50, 
    
    
    // 以下操作相同且合法
    a[5] = 0;       // a [offset of 5] = 0
    *(a+5) = 0;     // pointed to by (a+5) = 0  
    

    指针初始化 & 算术操作

    初始化类似变量的声明与初始化,如下

    int myvar;
    int * myptr = &myvar;
    
    int myvar;
    int * myptr = &myvar;
    
    int myvar;
    int * myptr;
    *myptr = &myvar;
    
    int myvar;
    int *foo = &myvar;
    int *bar = foo;
    

    指针的加减:其加减单位为类型所占字节长度。

    char *mychar;
    short *myshort;
    long *mylong;
    
    // 三种不同类型的指针执行 ++ 操作
    ++mychar;
    ++myshort;
    ++mylong;
    
    // ++ 操作符优先级高于 * 优先级
    *p++   // same as *(p++): increment pointer, and dereference unincremented address
    *++p   // same as *(++p): increment pointer, and dereference incremented address
    ++*p   // same as ++(*p): dereference pointer, and increment the value it points to
    (*p)++ // dereference pointer, and post-increment the value it points to 
    
    // 一个常见包含 ++ 和 * 操作符的语句
    *p++ = *q++;
    // 由于 ++ 优先级高,其整体操作等价于
    *p = *q;
    ++p;
    ++q;
    

    指针自加操作示意图


    pointer_arithmetics.png

    指针和 const

    指针可以通过地址访问变量,也可以修改其指向变量的值。

    const 修饰符可以限定只读指针:即只能读取变量值,无法修改变量值

    int x;
    int y = 10;
    const int * p = &y; // 注意 const 变量位置
    x = *p;          // ok: reading p
    *p = x;          // error: modifying p, which is const-qualified
    

    注意:
    const 修饰符,其修饰 int * 类型时,表明int 变量值不可变。其修饰变量 p 时,p 变量值不可变。
    const int * p = &y;int const* p = &y; 相同。

    // 仔细体会一下 const 具体修饰的位置
    int x;
          int *       p1 = &x;  // non-const pointer to non-const int
    const int *       p2 = &x;  // non-const pointer to const int
          int * const p3 = &x;  // const pointer to non-const int
    const int * const p4 = &x;  // const pointer to const int 
    

    常量指针使用 demo

    // pointers as arguments:
    #include <iostream>
    using namespace std;
    
    void increment_all (int* start, int* stop)
    {
      int * current = start;
      while (current != stop) {
        ++(*current);  // increment value pointed
        ++current;     // increment pointer
      }
    }
    
    void print_all (const int* start, const int* stop)
    {
      const int * current = start;
      while (current != stop) {
        cout << *current << '\n';
        ++current;     // increment pointer
      }
    }
    
    int main ()
    {
      int numbers[] = {10,20,30};
      increment_all (numbers,numbers+3);
      print_all (numbers,numbers+3);
      return 0;
    }
    

    指针与字面量字符串

    字面量字符串本质是不可变常量字符串,如下,指针 foo 指向的是 “hello” 首地址。

    const char * foo = "hello";
    
    pointer_assignment.png

    字符串访问某字符可以通过数组下标也可以通过指针访问

    *(foo+4)
    foo[4]
    

    指向指针的指针(二维指针 - 二维数组)

    指针同样可以被其他指针指向

    char a;
    char * b;
    char ** c;
    a = 'z';
    b = &a;
    c = &b; // c 就是指向指针 b 的指针。它是一个二维指针
    
    • c is of type char** and a value of 8092
    • c is of type char and a value of 7230
    • **c is of type char and a value of 'z'

    它们在内存中关系如下


    pointer_to_pointer.png

    void 指针

    void 指针即 void *, 它是不具备具体类型的指针,或者叫做任意类型指针。

    说白了就是某块内存的首地址,但是未指定具体类型,如 int */ char * 等多种类型它都可以直接指向,但是未明确具体每个元素的长度,如果使用就必须告知其每个元素的长度。如下:

    // increaser
    #include <iostream>
    using namespace std;
    
    void increase (void* data, int psize)
    {
      if ( psize == sizeof(char) )
      { char* pchar; pchar=(char*)data; ++(*pchar); }
      else if (psize == sizeof(int) )
      { int* pint; pint=(int*)data; ++(*pint); }
    }
    
    int main ()
    {
      char a = 'x';
      int b = 1602;
      increase (&a,sizeof(a));
      increase (&b,sizeof(b));
      cout << a << ", " << b << '\n';
      return 0;
    }
    
    //-------------
    y, 1603
    

    sizeof 操作符是一个编译器指令,在编译阶段会直接由编译器翻译成常数:如 sizeof(int) 在编译阶段直接会被翻译成常数 4,sizeof(char) 在编译阶段直接会被翻译成常数 1.

    无效指针 和 指向空的指针

    无效指针,也是通常说的野指针。指的是访问了无权限地址的指针。野指针在编译期间不会报错,因为它符合语法,但是运行时会因为访问了无分配权限的地址而导致崩溃。如下

    int * p;               // uninitialized pointer (local variable)
    
    int myarray[10];
    int * q = myarray+20;  // element out of bounds 
    

    空指针,指的是指向空的指针,类似于 OC 里面将指针指向 nil。C++ 中定义方法有三种,都代表将指针指向地址0;

    int * p = 0;
    int * q = nullptr;
    int * r = NULL;
    

    指针和函数

    通常,函数指针是用在将函数作为参数传递的时候,函数指针写法如 int (* minus)(int,int) = subtraction;。 具体使用如下:

    // pointer to functions
    #include <iostream>
    using namespace std;
    
    int addition (int a, int b)
    { return (a+b); }
    
    int subtraction (int a, int b)
    { return (a-b); }
    
    int operation (int x, int y, int (*functocall)(int,int))
    {
      int g;
      g = (*functocall)(x,y);
      return (g);
    }
    
    int main ()
    {
      int m,n;
      int (*minus)(int,int) = subtraction;
    
      m = operation (7, 5, addition);
      n = operation (20, m, minus);
      cout <<n;
      return 0;
    }
    
    // -------
    8
    

    动态内存

    通常,程序运行之前(编译时期)即已确定了其定义变量所需的内存容量。
    但是,对于用户从键盘录入的内容只能在运行时动态分配内存。

    对于动态内存的分配和释放,C++ 提供了 new 和 delete 关键字。

    开辟空间 newnew [], new 只能开辟一块相对类型的空间,new [] 可以开辟连续的一组空间

    int * foo;
    foo = new int [5];
    
    dynamic.png

    对于开辟空间,有时候会出错,出错可以使用两种方式处理,1.抛出异常,2.不抛异常,直接判断开辟的空间指针是否为nil,如下

    int * foo;
    foo = new (nothrow) int [5];
    if (foo == nullptr) {
      // error assigning memory. Take measures.
    }
    

    删除动态内存,deletedelete [],分别是删除一块空间和一块连续的内存,其中被释放的内存,必须是之前 new 出来的,或者是 nullPtr;

    delete pointer;
    delete[] pointer;
    

    看一个完整的 Demo

    // rememb-o-matic
    #include <iostream>
    #include <new>
    using namespace std;
    
    int main ()
    {
      int i,n;
      int * p;
      cout << "How many numbers would you like to type? ";
      cin >> i;
      p= new (nothrow) int[i];
      if (p == nullptr)
        cout << "Error: memory could not be allocated";
      else
      {
        for (n=0; n<i; n++)
        {
          cout << "Enter number: ";
          cin >> p[n];
        }
        cout << "You have entered: ";
        for (n=0; n<i; n++)
          cout << p[n] << ", ";
        delete[] p;
      }
      return 0;
    }
    
    // -------------
    How many numbers would you like to type? 5
    Enter number : 75
    Enter number : 436
    Enter number : 1067
    Enter number : 8
    Enter number : 32
    You have entered: 75, 436, 1067, 8, 32,
    

    需要注意的是: p= new (nothrow) int[i]; 开辟内存的数量取决于用户输入,如果输入特别大数字,可能就遇到开辟空间错误问题,所以需要做错误处理。

    Note
    C 语言的动态内存分配函数: malloc, calloc, realloc and free 在 C++ 环境之下完全兼容,但是内存的分配和释放都需要在同一套函数之内。

    数据结构(Data structures) - 结构体

    结构体是一组共用同一个名称的元素集合。其中每个元素叫做成员,成员可以有自己的类型和长度。通过结构体可以构成面向对象的基础,如

    // 语法规则 - 
    struct type_name {
    member_type1 member_name1;
    member_type2 member_name2;
    member_type3 member_name3;
    ...
    } object_names;
    
    
    // 使用1, 通过 struct product 定义一个新的 product 类型,后面可以直接使用新的product类型
    struct product {
      int weight;
      double price;
    } ;
    
    product apple;
    product banana, melon;
    
    // 使用2,直接在定义类型的同时,声明结构体变量 --- 完全等同于使用方式1
    struct product {
      int weight;
      double price;
    } apple, banana, melon;
    

    结构体变量可以直接通过点语法访问自己内部成员。一个完整使用结构体示例

    // example about structures
    #include <iostream>
    #include <string>
    #include <sstream>
    using namespace std;
    
    struct movies_t {
      string title;
      int year;
    } mine, yours;
    
    void printmovie (movies_t movie);
    
    int main ()
    {
      string mystr;
    
      mine.title = "2001 A Space Odyssey";
      mine.year = 1968;
    
      cout << "Enter title: ";
      getline (cin,yours.title);
      cout << "Enter year: ";
      getline (cin,mystr);
      stringstream(mystr) >> yours.year;
    
      cout << "My favorite movie is:\n ";
      printmovie (mine);
      cout << "And yours is:\n ";
      printmovie (yours);
      return 0;
    }
    
    void printmovie (movies_t movie)
    {
      cout << movie.title;
      cout << " (" << movie.year << ")\n";
    }
    
    // ---------------------
    Enter title: Alien
    Enter year: 1979
    
    My favorite movie is:
     2001 A Space Odyssey (1968)
    And yours is:
     Alien (1979)
    

    结构体定义的是一种类型,他们也可以用作数组的方式来构造他们的表、或者数据库,如下

    // array of structures
    #include <iostream>
    #include <string>
    #include <sstream>
    using namespace std;
    
    struct movies_t {
      string title;
      int year;
    } films [3];
    
    void printmovie (movies_t movie);
    
    int main ()
    {
      string mystr;
      int n;
    
      for (n=0; n<3; n++)
      {
        cout << "Enter title: ";
        getline (cin,films[n].title);
        cout << "Enter year: ";
        getline (cin,mystr);
        stringstream(mystr) >> films[n].year;
      }
    
      cout << "\nYou have entered these movies:\n";
      for (n=0; n<3; n++)
        printmovie (films[n]);
      return 0;
    }
    
    void printmovie (movies_t movie)
    {
      cout << movie.title;
      cout << " (" << movie.year << ")\n";
    }
    
    // ------------------
    Enter title: Blade Runner
    Enter year: 1982
    Enter title: The Matrix
    Enter year: 1999
    Enter title: Taxi Driver
    Enter year: 1976
     
    You have entered these movies:
    Blade Runner (1982)
    The Matrix (1999)
    Taxi Driver (1976)
    

    指针与结构体

    和其他类型一样,结构体也可以使用指向该类型的指针指向。

    struct movies_t {
      string title;
      int year;
    };
    
    movies_t amovie;
    movies_t * pmovie;
    
    // 可以将amovie的地址赋值给 pmovie 变量
    pmovie = &amovie;
    

    示例

    // pointers to structures
    #include <iostream>
    #include <string>
    #include <sstream>
    using namespace std;
    
    struct movies_t {
      string title;
      int year;
    };
    
    int main ()
    {
      string mystr;
    
      movies_t amovie;
      movies_t * pmovie;
      pmovie = &amovie;
    
      cout << "Enter title: ";
      getline (cin, pmovie->title);
      cout << "Enter year: ";
      getline (cin, mystr);
      (stringstream) mystr >> pmovie->year;
    
      cout << "\nYou have entered:\n";
      cout << pmovie->title;
      cout << " (" << pmovie->year << ")\n";
    
      return 0;
    }
    // ------------------
    Enter title: Invasion of the body snatchers
    Enter year: 1978
     
    You have entered:
    Invasion of the body snatchers (1978)
    

    结构体变量访问本身成员可以用点语法。
    结构体指针访问其成员变量需要箭头操作符(->).
    下图展示了点语法和(->)操作符的区别

    <div>Expression<span class="Apple-tab-span" style="white-space:pre"></span><span class="Apple-tab-span" style="white-space:pre"></span><span class="Apple-tab-span" style="white-space: pre;"></span></div> What is evaluated Equivalent
    a.b Member b of object a
    a->b Member b of object pointed to by a (*a).b
    *a.b Value pointed to by member b of object a *(a.b)

    结构体嵌套

    结构体内可以嵌套其他结构体

    struct movies_t {
      string title;
      int year;
    };
    
    struct friends_t {
      string name;
      string email;
      movies_t favorite_movie;
    } charlie, maria;
    
    friends_t * pfriends = &charlie;
    

    基于上面的定义,可以通过如下的代码访问具体结构体内容(最后两行代码是访问同样的成员)

    charlie.name
    maria.favorite_movie.title
    charlie.favorite_movie.year
    pfriends->favorite_movie.year
    

    其他类型 - 类型别名/共用体/枚举

    定义类型别名有两种方式: typedef && using

    格式: typedef existing_type new_type_name ;
    示例
    typedef char C;
    typedef unsigned int WORD;
    typedef char * pChar;
    typedef char field [50]; 
    
    格式: using new_type_name = existing_type ;
    示例
    using C = char;
    using WORD = unsigned int;
    using pChar = char *;
    using field = char [50];
    

    两种创建别名的方式本质相同,前者继承自 C 语言,后者为 C++ 提供,using 用于模板函数的时候更加通用。使用别名的优势:使用自定义类型,如果代码后期修改数据类型,可以直接在别名定义出修改。

    共用体 unions

    共用体是一种新类型,它很像像结构体但其内部成员共用同一块物理内存。整个共用体的内存大小取决于其最大的内部成员。由于内部成员共同占用同一块内存、所以改变任何一个成员变量的值都会影响整个结构的值。因为真实的内存对齐方式依赖于机器环境,共用体在移植性上可能有问题

    union mix_t {
      int l;
      struct {
        short hi;
        short lo;
        } s;
      char c[4];
    } mix;
    

    假定操作系统32位小端模式,int 占 4 字节,short 2 字节。上述代码的内存占用情况如下:

    union.png

    匿名共用体

    共用体定义在 class 或 struct 中可以不定义名称。

    // structure with regular union
    struct book1_t {
      char title[50];
      char author[50];
      union {
        float dollars;
        int yen;
      } price;
    } book1;
    
    // structure with anonymous union
    struct book2_t {
      char title[50];
      char author[50];
      union {
        float dollars;
        int yen;
      };
    } book2;
    
    // 两者访问内部变量有不同
    book1.price.dollars
    book1.price.yen
    
    book2.dollars
    book2.yen
    

    注意
    作为共用体成员,它内部的 dollars 和 yen 成员共用同一块内存,我们可以给 price 设置为 dollars 也可以设置为 yen,但是不能同时设置两者。

    枚举类型 - Enumerated types (enum)

    枚举类型是自定义的一组标识(成为枚举器),枚举类型的对象可以设置为任何枚举器的值。语法如下

    enum type_name {
      value1,
      value2,
      value3,
      .
      .
    } object_names;
    

    例如

    enum months_t { january=1, february, march, april,
                    may, june, july, august,
                    september, october, november, december} y2k;
    

    其中枚举变量 y2k 拥有12个可能的值。从第一个值手动赋值为1, 后面的值累加。

    每个枚举器都和用户赋的值可以进行隐式转换,即 january 与 1 可以互转。

    C++ 中的真正枚举类型

    C++ 中可以定义真正枚举类型: 每个枚举器没有对应的int 值,也不可以进行隐式转换。从而保证了枚举类型的安全性。

    定义方式为 enum class (or enum struct) 代替仅仅的 enum;

    enum class Colors {black, blue, green, cyan, red, purple, yellow, white};
    

    使用方式: 对于枚举器必须包装到自己的类型内(但 enum 也可以这样用,但是可选)

    Colors mycolor;
     
    mycolor = Colors::blue;
    if (mycolor == Colors::green) mycolor = Colors::red;
    

    可以限定枚举器类型和占用空间,方式为添加冒号 和 类型。如下:定义了 Eyecolor 为只有一个字节 char 类型(1个字节)。

    enum class EyeColor : char {blue, green, brown};
    

    end

    相关文章

      网友评论

          本文标题:C++ 总结 (一、基础篇)

          本文链接:https://www.haomeiwen.com/subject/kxsudktx.html