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<int> p5 = make_shared<int>();
各种情况的初始值
- 对于内置类型和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 <string>
struct Sales_data {
std::string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
};
#endif