【C++学习笔记】处理类型和自定义数据结构

一、处理类型
1.1 类型别名
  类型别名(type alias)是一个名字,它是某种类型的同义词,使用类型别名可让类型名字变得简单明了、易于理解和使用,以及清楚地知道使用该类型的目的,有两种方法定义类型别名,分别是:
  ①使用关键字typedef,关键字typedef是作为声明语句中的基本数据类型的一部分出现,含有typedef的声明语句定义的不再是变量而是类型别名。

typedef double wages;         //wages是double的同义词
typedef wages base,*p;        //base是double的同义词,p是double*的同义词

  另外一种定义类型别名的方法是使用别名声明(alias declaration)来定义类型的别名,使用关键字using作为别名声明的开始,其后紧跟别名和等号,即把等号左侧的名字规定为右侧的别名:

using SI = Sales_item;         //SI是Sales_item的同义词

  如果某个类型别名只带的是复合类型或变量,那么我们不能尝试地把类型别名换成它本来的样子用来理解该语句的含义,这样是错误的!

typedef char *pstring;
const pstring cstr = 0;        //cstr是指向char的常量指针
const char *cstr = 0;          //这是对上面语句的错误理解!
const pstring *ps;             //ps是一个指针,它的对象是指向char的常量指针

  const是对给定类型的修饰,而pstring实际上是指向char的指针,因此const pstring是指向char的常量指针。

1.2 auto类型说明符
  编程时常常需要把表达式的值赋给变量,这要求在声明变量时清楚知道表达式的类型,但往往很难做到这一点,C++11新标准引入了auto类型说明符,它可以让编译器通过初始值来推算变量的类型,因而auto定义的变量必须有初始值,同时需要注意的是auto可以在一条语句中声明多个变量,但是所有变量的出啊是基本类型必须都一样。

auto item = val1 + val2;        //item初始化为val1和val2相加的结果
auto sz = 0, pi = 3.14;         //错误,sz和pi的类型不一致

  编译器推断出来的auto类型有时候和初始值的类型不完全一样,编译器会适当地改变结果类型使其更符合初始化规则。

int j = 0, &r = j
auto a = r;                     //a是一个整数,因为r是j的别名,j是一个整数
const int ci = j, &cr = ci;
auto b = ci;                    //b是一个整数,ci的顶层const被忽略
auto c = cr;                    //c是一个整数,cr是ci别名,ci是顶层const
auto d = &j;                    //d是一个正向指针,整数的地址就是指向整数的指针
auto e = &ci;                   //e是指向整数常量的指针,auto不会被忽略低层const

  还可以将引用的类型设为auto,此时原来的初始化规则仍然适用:

auto &g = ci;                    //g是一个整型常量引用,绑定到ci
auto &h = 42;                    //错误,不能为非常量引用绑定字面值
const auto &j = 42;              //正确

1.3 deltype 类型说明符
  有时我们想从表达式的类型推断出定义的变量类型,但是不想用表达式初始化变量,C++11新标准引入了第二种类型说明decltype,它的作用是选择并返回操作数的数据类型,下面的语句中编译器并不实际调用函数,只是返回假如调用f的话其会返回的那个类型。

decltype(f()) sum = x;         //sum的类型就是函数f的返回类型

  decltype处理顶层和引用的方式与auto有些不同,如果decltype使用的表达式是一个变量,则decltype返回该变量的类型(包括顶层const和引用在内):

const int ci = 0, &cj = ci;
decltype(ci) x = 0;            //x的类型是const int
decltype(cj) y = x;            //y的类型是const int&,y绑定到变量x
decltype(cj) z;                //z是一个引用,必须初始化

  如果decltype使用的表达式不是一个变量,则decltype返回表达式结果对应的类型,如下,r是一个引用,decltype(r)的结果是引用类型,但是如果想让结果类型是r所指的类型,可以把r作为表达式的一部分,即r+0,那么这个表达式的结果是一个具体值而不是引用;另一方面解引用指针可以得到指针所指的对象,而且还能给这个对象赋值,因此decltype(*p)的结果类型是int&,即得到引用类型;以及decltype所用的表达式,如果给变量加上一层或多层括号,那么编译器就会把它当成一个表达式,因为变量是一种可以作为赋值语句左值的特殊表达式,所以这样的decltype就会得到引用类型。

int j = 42, *p =&j, &r = j;
decltype(r+0) b;               //正确,加法的结果是int,故b是一个为初始化的int
decltype(*p) c;               //错误,c是int&,必须初始化
decltype((j)) d;               //错误,d是int&,必须初始化

二、自定义数据结构
2.1 定义Sales_data类型
  我们编写类以关键字struct开始(还可以用关键字class来定义自己的数据结构),紧跟着类型名和了体,类体由花括号包围形成一个新的作用域,类内部定义的名字必须唯一,但可以与类外部定义的额名字重复,类体右侧表示结束的花括号必须写一个分号:

struct Sales_data { /* ... */ };

2.2 编写自己的头文件
  一般地,类不定义在函数体内,当函数体外部定义类时,在各个指定的源文件中可能只有一处该类的定义,为了确保各个文件中类的定义一致,类通常被定义在头文件中,而且类所在的头文件的名字应与类的名字一样,例如库类型string在名为string的头文件中定义,Sales_data类应定义在Sales_data.h的头文件中。
  头文件通常包含哪些只能被定义一次的实体,如类、const和constexpr变量等,头文件也经常用到其他头文件的功能,例如Sales_data类包含一个string成员,所以Sales_data.h必须包含string.h头文件,同时使用Sales_data类的程序为了能操作类成员需要再一次包含string.h头文件,这样使用Sales_data类的程序先后两次包含了string.h头文件,所以可以在书写头文件时做适当处理,使其遇到多次包含时也能安全和正常地工作。
  确保头文件多次包含仍能安全工作的常用技术是预处理器(preprocessor),如果之前已经用到了一项预处理功能#include,当预处理器看到#include标记时机会用制定的头文件的内容代替#include。
  C++程序还会用到一项预处理功能是头文件保护符(header guard),头文件保护符依赖于预处理变量,预处理变量有两种状态:已定义和未定义。#define指令把一个名字设定为预处理变量,#ifdef当且仅当变量已定义时为真,#ifndef当且仅当变量未定义时为真,一旦检查结果为真,则执行后续操作直到#endif制定为止。

#ifndef SALES_DATA_H
#define SALEA_DATA_H
#include 
strcut Sales_data{
   std::string bookNo;
   unsigned units_sold = 0;
   double revenue = 0.0;
};
#endif

  整个程序中的预处理变量包括头文件保护符必须唯一,通常的做法是基于头文件中的类的名字全部大写来构建保护符的名字,一确保唯一性,头文件及时没有被包含在其他任何头文件中,也应该设置保护符,这是一个良好的习惯。

参考文献:
①C++ Primer 第五版。

你可能感兴趣的:(C++学习笔记)