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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343