C++ primer 第二章变量和基本类型

记录一下C++变量和基本类型这一块的知识:
加粗代表重点;
加粗下划线代表在句子中可以独立出来理解:顶层const(top-level const),表示指针本身是个常量 。这句话虽然说是指针但对于不是指针的情况也试用。

指针和const

关于指针的常量有两种:
  • 指向常量的指针:指针指向一个同基本数据类型变量,那么将不可以通过指针修改变量的值,但是仍然能够使指针指向其他常量

  const double pi = 3.14;  // pi是个常量,它的值不能改变
  double *ptr = π     //错误:ptr是一个普通指针
  const double *cptr = π    //正确:cptr可以指向一个双精度常量
  *cptr = 42;        //错误:不能给*cptr赋值

  指针的类型必须与其所指对象的类型一致,但是有两个例外。第一种例外情况是允许令一个指向常量的指针指向一个非常量对象:
  double dval = 3.14;    //dval是一个双精度浮点数,它的值可以改变
  cptr = &dval;       //正确:但是不能通过cptr改变dval的值

  • 常量指针普通指针建议初始化,常量指针必须初始化,一旦初始化完成,它的值(地址)也就不能改变了!指针本身是一个常量,其意义是指针本身存储的地址是一个常量,而地址指向的对象并非常量,因此可以修改,我们通过如下方式定义:

   const double *const pip = π//这行代码定义了一个指向常量对象的常量指针,要理解它我们

                  //采用从右到左的顺序读,*const代表指针内容为常量,const

                  //double为该指针指向的对象类型。

   这类常量指针如果指向的对象类型并非常量就可以通过解引用符‘*’修改对象的值,如:

   *curErr=0;


顶层const 

  • 顶层const(top-level const),表示指针本身是个常量
  • 底层const(low-level const)  ,表示指针所指的对象是一个常量

  当执行对象的拷贝操作时,常量是顶层const还是底层const区别明显。其中,顶层const不受什么影响:

  i = ci;       //正确:拷贝ci的值,ci是一个顶层const,对此操作无影响
  p2 = p3;  //正确:p2和p3指向的对象类型相同,p3顶层 const的部分不影响

  另一方面,底层const的限制却不能忽视。当执行对象的拷贝操作时,拷入和拷出的对象必须具有相同的底层const资格,或者两个对象的数据类型必须能够转换。一般来说,非常量可以转换成常量,反之则不行:

  int *p= p3;     //错误:p3包含底层const的定义,而p没有
  p2 = p3;      //正确:p2和p3都是底层 const
  p2 = &i;      //正确:int*能转换成const int*
  int &r = ci;     //错误:普通的int &不能绑定到int常量上
  const int &r2 = i;  //正确:const int&可以绑定到一个普通int上


constexpr 和常量表达式

  常量表达式(const expression)是指值不会改变并且在编译过程就能得到计算结果的表达式。显然,字面值属于常量表达式,用常量表达式初始化的const对象也是常量表达式。后面将会提到,C++语言中有几种情况下是要用到常量表达式的。
  一个对象(或表达式)是不是常量表达式由它的数据类型和初始值共同决定。

constexpr变量

  C++11新标准规定,允许将变量声明为constexpr类型以便由编译器来验证变量的值是否是一个常量表达式。声明为constexpr的变量一定是一个常量,而且必须用常量表达式初始化。

字面值类型

  常量表达式的值需要在编译时就得到计算,因此对声明constexpr时用到的类型必须有所限制。因为这些类型一般比较简单,值也显而易见、容易得到,就把它们称为“字面值类型”(literal type)。

指针和constexpr

  必须明确一点,在constexpr声明中如果定义了一个指针,限定符constexpr仅对指针有效,与指针所指的对象无关:

  const int *p = nullptr;    // p是一个指向整型常量的指针
  constexpr int *q = nullptr;  // q是一个指向整数的常量指针 dsf

  那么constexpr int * 可以说是和int *const  意思接近。

  constexpr将它所定义的对象置为顶层const。


 

处理类型

  类型别名(type alias)是一个名字,它是某种类型的同义词。使用类型别名有很多好处,它让复杂的类型名字变得简单明了、易于理解和使用,还有助于程序员清楚地知道使用该类型的真实目的。
  typedef double wages;
  另一种写法
  using sI = Sales_item; //SI是Sales item的同义词
指针、常量和类型别名
  如果某个类型别名指代的是复合类型或常量,那么把它用到声明语句里就会产生意想不到的后果。例如下面的声明语句用到了类型pstring,它实际上是类型char*的别名:
  typedef char *pstring;
  const pstring cstr = 0;// cstr是指向char的常量指针
  const pstring *ps;//ps是一个指针,它的对象是指向char的常量指针上述两条声明语句的基本数据类型都是const pstring,和过去一样,const是对给定类型的修饰。pstring 实际上是指向char 的指针,因此,const pstring 就是指向char的常量指针,而非指向常量字符的指针。

auto类型说明符
  显然,auto定义的变量必须有初始值:
  auto item = vall + val2;// item初始化为val1和val2相加的结果
  auto也能在一条语句中声明多个变量。因为一条声明语句只能有一个基本数据类型,所以该语句中所有变量的初始基本数据类型都必须一样:
  auto sz = 0,pi = 3.14;//错误:sz和pi的类型不一致
复合类型、常量和 auto
  首先,正如我们所熟知的,使用引用其实是使用引用的对象,特别是当引用被用作初始值时,真正参与初始化的其实是引用对象的值。此时编译器以引用对象的类型作为auto的类型:
  int i =0,&r = i;

  auto a= r;  // a是一个整数(r是i的别名,而i是一个整数)
  其次,auto一般会忽略掉顶层const(参见2.4.3节,第57页),同时底层const则会保留下来,比如当初始值是一个指向常量的指针时:
  如果希望推断出的auto类型是一个顶层const,需要明确指出:
  const auto f = ci;  //ci的推演类型是int,f是const int


decltype

decltype,它的作用是选择并返回操作数的数据类型。在此过程中,编译器分析表达式并得到它的类型,却不实际计算表达式的值:

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

  编译器并不实际调用函数f,而是使用当调用发生时f的返回值类型作为sum 的类型。换句话说,编译器为sum 指定的类型是什么呢?就是假如f被调用的话将会返回的那个类型。

  需要指出的是,引用从来都作为其所指对象的同义词出现,只有用在 decltype处是一个例外,如上式:sum的类型绑定的是‘f()’,而一般的绑定应当绑定‘x’的类型

  如果表达式的内容是解引用操作,则decltype将得到引用类型。
  int i =42,*p = &i, &r = i;
  decltype(r + 0) b;//正确:加法的结果是int,因此b是一个(未初始化的)int

  因为r是一个引用,因此decltype(r)的结果是引用类型。如果想让结果类型是r所指的类型,可以把r作为表达式的一部分,如r+0,显然这个表达式的结果将是一个具体值而非一个引用。
  切记:decltype ((variable))(注意是双层括号)的结果永远是引用,而decltype(variable)结果只有当 variable本身就是一个引用时才是引用。

 


 

自定义数据结构

  struct Sales_data {/* ...*/ } accum,trans, *salesptr;
  struct sales_data {/*...*/ };//与上一条语句等价,但可能更好一些

  Sales_data accum,trans, *salesptr;
  分号表示声明符(通常为空)的结束。一般来说,最好不要把对象的定义和类的定义放在一起。这么做无异于把两种不同实体的定义混在了一条语句里,一会儿定义类,一会儿又定义变量,显然这是一种不被建议的行为。
  C++11新标准规定,可以为数据成员提供一个类内初始值(in-class initializer)。
  对类内初始值的限制:或者放在花括号里,或者放在等号右边,记住不能使用圆括号。

编写自己的头文件  

  使用sales data类的程序就先后两次包含了string.h头文件:一次是直接包含的,另有一次是随着包含sales_data.h被隐式地包含进来的。有必要在书写头文件时做适当处理,使其遇到多次包含的情况也能安全和正常地工作。

预处理器概述  

  确保头文件多次包含仍能安全工作的常用技术是预处理器(preprocessor),它由C++语言从C语言继承而来。预处理器是在编译之前执行的一段程序,可以部分地改变我们所写的程序。之前已经用到了一项预处理功能#include,当预处理器看到#include标记时就会用指定的头文件的内容代替#include。
  C++程序还会用到的一项预处理功能是头文件保护符(header guard),头文件保护符依赖于预处理变量(参见2.3.2节,第48页)。预处理变量有两种状态:已定义和未定义#define 指令把一个名字设定为预处理变量,另外两个指令则分别检查某个指定的预处理变量是否已经定义:#ifdef当且仅当变量已定义时为真,#ifndef当且仅当变量未定义时为真。一旦检查结果为真,则执行后续操作直至遇到#endif指令为止。
使用这些功能就能有效地防止重复包含的发生:
  #ifndef SALES_DATA_H
  #define SALES_DATA_H

  #include <string>struct Sales_data {
  std: :string bookNo;
  unsigned units_sold = 0;double revenue= 0.0;
  };
  #endif
  第一次包含sales_data.h时,#ifndef的检查结果为真,预处理器将顺序执行后面的操作直至遇到#endif为止。此时,预处理变量 SALES_DATA_H的值将变为已定义,而且sales data.h也会被拷贝到我们的程序中来。后面如果再一次包含Sales_data.h则#ifndef的检查结果将为假,编译器将忽略#ifndef到#endif之间的部分。

  头文件即使(目前还)没有被包含在任何其他头文件中,也应该设置保护符。头文件保护符很简单,程序员只要习惯性地加上就可以了,没必要太在乎你的程序到底需不需要。


posted @ 2022-08-18 00:50  ahab1016  阅读(44)  评论(0)    收藏  举报