C++ primer 第二章-变量和基础数据类型

Hi!这里是山幺幺的c++ primer系列。写这个系列的初衷是,虽然在学校学习了c++,但总觉得对这门语言了解不深,因此我想来啃啃著名的c++ primer,并在这里同步记录我的学习笔记。由于我啃的是英文版,所以笔记是中英文夹杂的那种。另外由于已有一定的编程基础,所以这个系列不会含有太基础的知识,想入门的朋友最好自己啃书嘻嘻~

关于内置类型


算术类型

  • 整型:char,int,bool,short,long等
  • 浮点型:float,double等

void类型

关于bit/byte/word


On most machines a byte contains 8 bits and a word is either 32 or 64 bits, that is, 4 or 8 bytes.

关于signed/unsigned


basic character types

  • char
  • unsigned char
  • signed char
    PS:虽然有三种character types,但是只有两种representations: signed and unsigned。char类型使用其中的一种表示,这取决于编译器。

signed type

所有bit都用来表示value

数据类型使用建议


表示整数用int或long long

short通常太小,而实际上long often has the same size as int

算术表达式不要用char或bool

只用char和bool表示字符或者truth values。因为char在有些机器上是signed,有些则是unsigned,所以带char的计算很复杂。如果你需要用一个很小的整数,可以用signed char或unsigned char。

浮点运算用double而不是float

double精度更高而运算代价并没有比float高很多,在有些机器上甚至代价更低。

关于类型转换


类型不匹配的情况

  • 如果给unsigned类型的变量x赋超出范围的值v(比如,-1),则x = v mod 该类型能表达的值的个数。
    举个栗子:unsigned char x = -1; 一个8 bit的unsigned char范围是0-255,所以x = -1 mod 256 = 255.
  • 如果给signed类型的变量x赋超出范围的值,x是undefined。

加前/后缀类型转换


PS:加了前/后缀,不一定精准转换为图中所写的类型,比如加UL只是至少为unsigned long,如果变量的值不符合unsigned long范围,也可能是unsigned long long.

关于字面量


整型字面量

  • 20 十进制
  • 024 八进制
  • 0x14 十六进制

浮点型字面量

  • 栗子:3.14159 3.14159E0 0. 0e0 .001
  • 浮点型字面量的默认类型是double

字符(串)型字面量

  • 字符型字面量:单引号中一个字符(比如,'a')
  • 字符串型字面量:双引号中>=0个字符(比如,"Hello World")
    PS:The compiler appends a null character (’\0’) to every string literal.
  • 中间只含空格、tab或换行的两个字符串型字面量会合为一个
std::cout << "a really, really long string literal "
"that spans two lines" << std::endl; //等价于
std::cout << "a really, really long string literal that spans two lines" << std::endl;

布尔型字面量

  • true
  • false

指针型字面量

  • nullptr

关于初始化


初始化与赋值

Initialization and assignment are different operations in C++.

默认初始化

  • 形式:type variableName;
  • 如果定义了变量没有指定初值,则变量被默认初始化,内置/复合类型:初始值未定义;类:调用默认构造函数

直接初始化/构造初始化

  • 形式:type variableName(args);
  • 使用构造函数初始化

列表初始化

  • 形式:type variableName{args};
  • 若list initialize会导致信息丢失,则编译器不允许list initialize内置数据类型的变量,比如:
long double ld = 3.1415926536;
int a{ld}, b = {ld}; // error: narrowing conversion required
int c(ld), d = ld; // ok: but value will be truncated

拷贝初始化

  • 形式:type variableName=otherVariableName; 或 type variableName=type (args)
  • 先创建一个临时对象,让后调用拷贝构造函数创建对象variableName(有的编译器优化后不会创建临时对象,而是直接构造)
  • 拷贝初始化与直接初始化的区别见第十三章

值初始化

  • 形式:type variable();
  • 初始值是defined,一般是0或""等

默认初始化与值初始化的使用场景

  • 默认初始化
    • When we define nonstatic variables or arrays at block scope without initializers
    • When a class that itself has members of class type uses the synthesized default
      constructor
    • When members of class type are not explicitly initialized in a constructor initializer list
    • dynamically allocated objects are default initialized
  • 值初始化
    • During array initialization when we provide fewer initializers than the size of the array (还有resize的时候,新size大于现有元素个数时)
    • When we define a local static object without an initializer
    • When we explicitly request value initialization by writing an expressions of the form T() where T is the name of a type (The vector constructor that takes a single argument to specify the vector’s size uses an argument of this kind to value initialize its element initializer.)
    • 对map和unordered_map进行subscription且key不存在时,a new element is created and inserted into the map for that key. The associated value is value initialized.
    • 下面的代码中,p5 points to an int that is value initialized to 0
    shared_ptr p5 = make_shared();
    

各种情况的初始值

  • 对于内置类型和array:若在块内、非static、未给初始值,则执行默认初始化,其初始值未定义;否则执行值初始化,初始值为0
  • 对于复合类型:若为引用,声明时必须初始化,而且无法将引用重新绑定到另一个对象上;若为指针,执行默认初始化,其初始值未定义
    int *pi1 = new int;           //默认初始化;*pi1的值未定义
    int *pi2 = new int();         //值初始化为0;*pi2为0
    
  • 对于静态变量:执行值初始化,初始值为0
  • 对于STL对象:无论是默认初始化还是值初始化,都调用其默认构造函数,初始值都一般为空对象(比如 string 初始值是 “”)
    string *ps1 = new string;     //默认初始化为空string
    string *ps2 = new string();   //值初始化为空string
    
  • 对于类:无论是默认初始化还是值初始化,都调用其默认构造函数
  • 对于类中依赖于编译器合成的默认构造函数的内置类型成员:若未在类内被初始化,则执行默认初始化,其初始值未定义
    class X
    {
        int a;
    public:
        void ShowX(){cout << a ;}
        X() = default;
    };
    int main()
    {
       X xx;
       xx.ShowX();    //对象xx中的a成员的值被默认初始化,由于a是在块作用域内定义的,所以此处输出的值未定义
    
       return 0;
    }
    
  • 对于未被explicitly initialized in a constructor initializer list的类成员:执行默认初始化,其初始值未定义

declaration与definition

  • 对于初始化时初始值数量小于其维度的数组:剩下的元素会进行值初始化,初始值为0
  • 对于使用形如T()【T是一个类型】的表达式显示地请求值初始化的:执行值初始化
    std::string *pia1 = new int[10]();    //动态分配10个值初始化为0的int
    std::string *pia2 = new int[10];  //动态分配10个未初始化的int
    class A;
    A a=A();  //值初始化
    

二者区别

  • Variables must be defined exactly once but can be declared many times.
  • declare: 声明变量的名字和数据类型

栗子

extern int i; // declares but does not define i
int j; // declares and defines j

关于Static Typing


含义

Types are checked at compile time.

type checking

  • 编译器检查对变量的操作是否被其数据类型支持,若否,编译器生成error message,并且不会生成可执行程序
  • 因此,在使用变量之前必须先declare它

关于标识符


起名规则

  • 只能由数字、字母和下划线组成
  • 只能用字母和下划线开头
  • 不能包含两个连续的下划线
  • 用在开头的下划线不能紧跟一个大写字母
  • 定义在函数外的标识符不能用下划线开头

作用域

  • 栗子
int reused = 42; // reused has global scope
int main()
{
int unique = 0; // unique has block scope
// output #1: uses global reused; prints 42 0
std::cout << reused << " " << unique << std::endl;
int reused = 0; // new, local object named reused hides global reused
// output #2: uses local reused; prints 0 0
std::cout << reused << " " << unique << std::endl;
// output #3: explicitly requests the global reused (global scope has no name); prints 42 0
std::cout << ::reused << " " << unique << std::endl;
return 0;
}

关于复合类型


引用(本章只涉及左引用)

  • 引用是被引对象的别名,引用不是对象
  • 被引对象不可以是引用
  • 引用必须初始化
  • 引用一旦初始化,就无法改变引用对象了
  • 一般来说在初始化时 the value of the initializer is copied into the
    object we are creating. 但我们初始化引用时, we bind the reference to its initializer.
  • 引用不是对象
  • 栗子
int &r3 = i3, &r4 = i2; // both r3 and r4 are references
auto &h = 42; // error: we can't bind a plain reference to a literal
const auto &j = 42; // ok: we can bind a const reference to a literal

指针

  • 指针是对象
  • pointers defined at block scope have undefined value if they are not initialized.
  • 因为引用不是对象,所以它没有地址,因此无法定义指向引用的指针
  • 栗子
double dp, *dp2; // dp2 is a pointer to double; dp is a double
  • 空指针的定义方法
int *p1 = nullptr; // equivalent to int *p1 = 0; nullptr可转换为任何指针类型
int *p2 = 0; // directly initializes p2 from the literal constant 0
// must #include cstdlib
int *p3 = NULL; // equivalent to int *p3 = 0;
  • NULL:是一个preprocessor variable,在cstdlib中被定义为0;preprocessor在编译前运行,把NULL替换为0
  • 最好使用nullptr而不是NULL
  • 不能把整型变量赋给指针,即使该整型变量的值恰好是0
  • 定义指针时最好都要初始化
  • void型指针:can hold the address of any object;can be compared to another pointer;can be assigned to another void pointer

type modifier

  • *和&
  • 栗子
int* p1, p2; // p1 is a pointer to int; p2 is an int
int *p1, *p2; // both p1 and p2 are pointers to int

int *&r = p; // r is a reference to the pointer p
r = &i; // r refers to a pointer; assigning &i to r makes p point to i
*r = 0; // dereferencing r yields i; changes i to 0

PS:多个modifier叠在一起时,从右往左读更好理解些。

关于const


const的特殊属性

  • const变量必须初始化
  • the compiler will usually replace uses of the variable with its corresponding value during compilation
  • const变量默认是local to the file的,所以如果要多个文件共享一个const变量,需要在定义它时也加上extern,比如:
/* test.c */
int etn = 100; //非const,定义时不需加extern
extern const int bufSize = 10; //const,定义时要加extern
/* main.c */
extern int etn; //非const在declare时也要加extern
extern const int bufSize;

易混淆的const、&、*相关

  • const T x、T const x
    • 【将x定义为常变量,top-level const
    • 【必须初始化,且初始化后其值不能改变】
  • const T& x、T const& x
    • 引用常量的引用low-level const
    • 【不能通过x改变被引对象的值】
    • 【因为引用不是对象,所以没有自身是常量的引用(即真正的“常引用”),所以把引用常量的引用称为常引用
    • 【必须初始化,且初始化后其值不能改变】
  • const T* x、T const* x
    • 指向常量的指针low-level const
    • 【不能通过x修改其指向对象】
    • 【指向const变量的指针必须为const T*】
  • T* const x
    • 常指针top-level const
    • 【不能修改其自身指向位置】
    • 【必须初始化,且初始化后其值不能改变】
  • const T* const x、T const * const x
    • 【既不能修改其指向对象也不能修改其自身指向位置的指针】
  • const T*& x、T const*& x
    • 【对指向常量的指针的引用】
  • T* const& x
    • 【对T*的常引用】
  • T const * const & x
    • 【对const T*的常引用】

PS:引用常量的引用 和 指向常量的指针 都不限制被引/指对象是否能被修改,只是不能通过它们修改被引/指对象

top/low-level const的区别

  • 复制top-level const的对象时,其const属性会被忽略
int i = 0;
const int ci = 42;
i = ci; // ok: copying the value of ci; top-level const in ci is ignored
  • 复制low-level const的对象时,其const属性不会被忽略
const int ci = 42;
const int *const p3 = &ci;
int *p = p3; // error: p3 has a low-level const but p doesn't

int i;
const int *p2 = &ci;
p2 = &i; // ok: we can convert int* to const int*

int &r = ci; // error: can't bind an ordinary int& to a const int object
const int &r2 = i; // ok: can bind const int& to plain int

引用的类型必须匹配被引对象的类型,除了:

  • we can bind a reference to const to a nonconst object, a literal, or a more general expression
/*合法*/
int i = 42;
const int &r1 = i; // we can bind a const int& to a plain int object
const int &r2 = 42; // ok: r1 is a reference to const
const int &r3 = r1 * 2; // ok: r3 is a reference to const

double dval = 3.14;
const int &ri = dval; // (*)
/*不合法*/
int &r4 = r * 2;

double dval = 3.14;
int &ri = dval;

/*为什么不合法?因为(*)等价于:*/
const int temp = dval; 
const int &ri = temp;
/*在这种情况下,ri绑定了a temporary object. 如果ri不是const,我们可能会想通过ri来改变dval的值,但实际上改变的只是temp的值*/
/*PS:A temporary object is an unnamed object created by the compiler when it needs a place to store a result from evaluating an expression.*/
  • 第二种情况之后会讲

指针的类型必须匹配被指对象的类型,除了:

  • we can use a pointer to const to point to a nonconst object
double dval = 3.14; // dval is a double; its value can be changed
const double *cptr = &dval; // ok: but can't change dval through cptr
  • 第二种情况之后会讲

常表达式

  • 其值不能改变,在编译时evaluate
  • 字面量和被常表达式初始化的常对象是常表达式
const int max_files = 20; // max_files is a constant expression
const int limit = max_files + 1; // limit is a constant expression
int staff_size = 27; // staff_size is not a constant expression
const int sz = get_size(); // sz is not a constant expression, the value of its initializer is not known until run time

constexpr

  • constexpr变量必须是const且由常表达式初始化,否则编译器就会报type error
  • constexpr函数must be simple enough that the compiler can
    evaluate them at compile time.
constexpr int mf = 20; // 20 is a constant expression
constexpr int limit = mf + 1; // mf + 1 is a constant expression
constexpr int sz = size(); // ok only if size is a constexpr function
  • 因为编译时要evaluate,所以constexpr变量的数据类型有限制,只能是literal types,比如算术类型、引用、指针等是,自定义类、标准库的IO、string等不是
  • constexpr指针/引用只能指向/引用有固定地址的变量,比如:
    • 定义在所有函数体外面的变量,其地址就是一个常表达式
    • functions may define variables that exist across calls to that function,这种特殊的局部变量地址也是固定的
  • constexpr是top-level const,即constexpr T*定义的是常指针,constexpr const T*定义的是指向常量的常指针
    PS:对于想用作常表达式的变量,最好定义为constexpr变量

关于类型别名


alias declaration

using SI = Sales_item; // SI is a synonym for Sales_item
using int_array = int[4]; // int_array is a name for the type “array of four ints”

typedef

typedef double wages; // wages is a synonym for double
typedef int int_array[4]; // 和using int_array = int[4]等价

const与alias declaration

typedef char *pstring; // pstring = char*
const pstring cstr = 0; // cstr是指向char的常指针
const pstring *ps; // ps是指向常指针的指针

关于auto


  • 让编译器为我们判断数据类型
  • 同一句类型需一致
auto i = 0, *p = &i; // ok: i is int and p is a pointer to int
auto sz = 0, pi = 3.14; // error: inconsistent types for sz and pi
  • 忽略top-level const,保留low-level const
const int ci = i, &cr = ci;
auto b = ci; // b is an int (top-level const in ci is dropped)
auto c = cr; // c is an int (cr is an alias for ci whose const is top-level)
auto d = &i; // d is an int*(& of an int object is int*)
auto e = &ci; // e is const int*(& of a const object is low-level const)
const auto f = ci; // deduced type of ci is int; f has type const int
auto &m = ci, *p = &ci; // m is a const int&;p is a pointer to const int

关于decltype


使用函数

decltype(f()) sum = x; // sum has whatever type f returns
  • 编译器并未调用f,但使用了f的返回类型

使用表达式

int i = 42, *p = &i, &r = i;
decltype(r + 0) b; // ok: addition yields an int; b is an (uninitialized) int
decltype(*p) c; // error: c is int& and must be initialized
  • 第二行:通过+ 0把引用变为非引用
  • 第三行:判断为引用类型

使用括号

// decltype of a parenthesized variable is always a reference
decltype((i)) d; // error: d is int& and must be initialized
decltype(i) e; // ok: e is an (uninitialized) int
  • 加括号-->变为引用

保留top-level const

const int ci = 0, &cj = ci;
decltype(ci) x = 0; // x has type const int
decltype(cj) y = x; // y has type const int& and is bound to x
decltype(cj) z; // error: z is a reference and must be initialized

关于自定义数据结构


结构体

struct Sales_data {
std::string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
};
  • 未初始化的成员会有默认的初始值,比如,bookNo=""

  • 类通常定义在头文件中【.h有变动,.cpp需重新编译】

关于preprocessor


  • 在编译前运行
  • 把#include替换为对应的头文件的内容
  • preprocessor variable名字全大写
#ifndef SALES_DATA_H
#define SALES_DATA_H
#include 
struct Sales_data {
std::string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
};
#endif

你可能感兴趣的:(C++ primer 第二章-变量和基础数据类型)