1.变量之间的运算形式依赖于变量的数据类型,如i = i + j;当 i 和 j 是整型或者浮点型,则代表两个数的相加,如果是第一章所说的Sales_item类型,那么就是这两个变量的成分相加(如果书号相同则在卖出数量上相加)。
2.使用Int执行整数运算。在实际应用中short常常显得太小而long一般和int有一样的尺寸。如果你的数值超过了Int表示的范围,选用long long。
3.执行浮点运算选用double,因为float通常精度不够而且双精度浮点数和单精度浮点数的计算代价相差无几。对于某些机器来说,双精度运算甚至比单精度还快。Long double提供的精度在一般情况下是没有必要的,况且它带来的运行时消耗也不容忽视。
4.程序的自动类型转换:
1).当把一个浮点数赋值给整数类型时,进行了近似处理。结果值将仅仅保留浮点数中小数点之前的部分。
2).当我们赋给无符号类型一个超出它表示范围的值时,结果是初始值对无符号类型表示数值总数取模后的余数。如unsigned char c = -1,unsigned char的表示范围为0-255的值,则用256%abs(-1)=255,所以实际结果为255.
3).当我们赋给带符号类型一个超出它表示范围的值时,结果是未定义的(undefined)。
如signed char c2 = 256.(范围一般是-128-127),则c2的值是未定义的。
4).给一个无符号数unsigned int(unsigned)赋一个负值,相当于这个负数加上无符号数的模。
5).当一个算数表达式既有无符号数又有Int值时,那个int值就会转换为无符号数。
5.写程序要避免无法预知和依赖于实现环境的行为(可移植性低)。
6.for循环里的i++,++i什么的都是在循环体执行之后才执行的。所以如果要把这样的语句执行先于循环体,可以用while循环。
7.在一个算数表达式中,切勿混用带符号类型和无符号类型,其中带符号类型为负的话会先把带符号类型转换为无符号类型。
//字面值常量
7.5.整形字面值可以用十进制,八进制,十六进制来表示,分别为12,012(八进制以0开头),0x12/0X12(16进制以0x或0X开头)
8.单引号‘ ’ 只能括单个字符,双引号“ ”可以括字符串。
9.字符串其实是由常量字符构成的数组,对于字符串,编译器会在其结尾处添加一个'\0'空字符,如“AB”其实是由'A','B' and '\0'三个字符构成的数组。
10.如果两个字符串字面值位置紧邻且仅由空格、缩进和换行符分隔,则它们实际上是一个整体。
//变量
1.对象是具有某种数据类型的内存空间。变量提供一个具名的、可供程序操作的存储空间。
2.初始化和赋值的区别:初始化不是赋值,初始化的含义是创建变量时赋予其一个初始值,而赋值的含义是把对象的当前值擦出,而以一个新值来替代。
3.列表初始化(C++11)新标准:int units_sold{0}; int units_sold={0}.
4.当用于内置类型的变量时,这种列表初始化形式有一个重要特点:如果我们使用列表初始化且初始值存在丢失风险(比如把long double的值赋值给int值,如果int 的变量使用列表初始化,则它会报错),则编译器将报错。
5.如果定义变量时没有指定初值,则变量被默认初始化,此时变量被赋予了“默认值”。默认值到底是什么由变量类型决定,同时也被定义变量的位置影响。如果是内置类型的变量未被显示初始化,它的值由定义的位置决定,定义于任何函数体之外的变量被初始化为0;定义在函数体内部的内置变量类型将不被初始化,则它的值是未定义的,如果试图拷贝或以其他形式访问此类值将引发错误。
6.如果想声明一个变量就在变量名前添加关键字extern,而且不要现实地初始化变量,任何包含了显示初始化的声明即成为定义。
7.在函数体内部,如果试图初始化一个由extern 关键字标记的变量,将引发错误。
8.其他用到某变量的文件必须对其进行声明,但绝对不能重复定义。
add.C++是一种静态类型(statically typed)语言,就是在编译阶段检查类型。因为不同的类型决定了不同的运算方式和操作,不指定类型就编译器就无法判断实体的类型。
//标识符(变量名什么的)
9.用户自定义的标识符中不能连续出现两个下划线,也不能以下划线紧连大写字母开头。此外定义在函数体外的标识符不能以下划线开头。
10.约定俗称的命名规范:(1)变量名一般用小写字母 (2)自定义类名以大写字母开头(3)单词之间用一个下划线分开
//作用域(scope)
11.大多数作用域都以花括号分隔
int main() //main定义于所有花括号之外,它拥有全局作用域(global scope)
{
int sum = 0;//sum定义于main函数所限定的作用域之内,出了main函数所在的块就无法访问了,所 //以sum拥有块作用域(block scope)
for (int val = 0;val <= 10;++val) //val定义于for语句内,只在for语句之内可以访问,在 //main函数其他地方不能访问它。
sum += val;
cout << sum<<endl;
return 0;
}
12.作用域中一旦声明了某个名字,它的内层作用域都能访问该名字,同时,允许在内层作用域中重新定义外层作用域已有的名字。
13.当作用域操作符的左侧为空时,像全局作用域发出请求获取作用域操作符右侧名字所对应的变量。::reused 为全局变量reused.
//复合类型(compound type)
指基于其他类型定义的类型。
//引用(reference)
14.引用为对象起了另外一个名字,必须为&val的形式定义,它只是为一个已经存在的对象所起的另外一个名字。
15.为什么引用一定要初始化:在定义引用时,程序把引用和它的初始值绑定(bind)在一起(int &refval = ival;//引用并初始化后refval和ival两个变量均存在,和普通变量的初始化不同,普通变量的初始化时将初始值拷贝到新建的对象中),所以必须初始化。
16.绑定了引用之后对引用进行操作refval=2;相当于ival=2;此时输出ival的值为2.
17.引用一旦定义后就不能再改变,且引用本身不是一个对象,所以不能定义引用的引用,即int &&refval.
18.引用只能绑定在对象(object)上,而不能与字面值或某个表达式的计算结果绑定在一起,即不能int &refval = 10;
19.无论是改变refval还是ival的值,它们俩的值始终都是一样的。
//指针(pointer)
20.指针的定义为*val的形式。
22.指针存放的是某个对象的地址,指针本身也是一个对象,它的值为指向的那个值的地址,想要获取该地址,就要使用取地址符&。
23.因为引用不是对象,所以不能定义指向引用的指针。
24.int *p表示定义一个指向Int对象的指针p。
25.int ival = 42;
int *p = &ival;//定义一个指针类型p,初始化为ival的地址
cout << *p << endl;//使用解引用符“*”,来访问指针p所指向的对象,这里就相当于输出ival
*p = 0;//*p其实就是ival,这一句就相当于ival = 0;
cout << ival <<" "<<p<< endl;//输出ival的值和p的值(ival的地址)
add:int *p = &1024;是错误的,因为1024并不是一个对象,系统没有为它分配一个内存,所以没有地址。
26.在C++11标准中生成空指针最好使用int *p = nullptr;当你不清楚指针该指向何处时,可以把它初始化为控制针,这样程序就能检测并指导它没有指向任何具体的对象了。
add:还可以用 int *p=0;生成空指针//右边为字面常量0,不能是int i =0;int *p=&i;这样并不是空指针。
add2: int i = 0;
int *pl = 0;
int *pl2 = &i;
cout << "pl: " <<pl<< endl;//空指针地址为00000000
cout << "pl2: " << pl2 << endl;//变量值为0的地址为变量的地址,变量值相同的变量,系统分配给它们的地址不同。
27.void *p是一种特殊的指针类型,可用于存放任意对象的地址,我们不能直接操作void *指针所指的对象,因为我们不知道这个对象到底的是什么类型,也就无法确定能在这个对象上做哪些操作。就是不能以*p的形式对它所指object进行操作。
//理解复合类型的声明
add:一条声明语句由一个基本数据类型和一个声明符列表组成。
28.通过*的个数可以区分指针的级别。即**表示指针的指针,***表示指针的指针的指针。
int ival = 1024;
int *pi = &ival;
int **ppi = π //必须用**表示ppi为一个指针的指针(可以理解为int**是一个指向指针的类型,如果右边的pi不是指针也会报错),不能用int *ppi = π这虽然在逻辑上是对的,但在C++中这是不对的,
cout << ival << " 地址: " << pi << " 地址存放的位置: " << ppi << endl;
29.通过两个解引用符**可以得到最原始的变量。如上一句可以改为
cout << **ppi << " 地址: " << *ppi << " 地址存放的位置: " << ppi << endl;
30. int *pi ;//生成了一个指针pi
int *&r=pi; //指向指针的引用,这个引用就需要是指针类型所以用int *&r
//const限定符
31.const对象一旦创建后其值就不能在改变,只能对其执行不改变其内容的操作,所以const对象必须初始化。
add:const对象的取址并不改变const,所以是可以的。
//const的引用
32.对常量对象的引用必须由一个常量引用定义(const int i =1024; const int &re = i;),re无法修改,因为i是一个常量对象。
//初始化和对const的引用
33.初始化常量引用时,允许任意表达式作为初始值,只要该表达式的结果能转换成引用的类型即可。如(int i =42;const int &re = i;这里和前面的引用不同,普通引用必须左右两边对象类型相同,而这里一边是const int 一边是Int)
34.这样的引用也是不可以改变的,re = 1024;是错误的(因为re本身不是对象,它其实就代表了它所引用的const)。这里应该是把int 转换成了const int.如果要改变i的值,只要不是通过re就可以改变。
//指针和const
35.指向const的指针和const指针:指向const的指针const int *p =&i;(const int i = 1024;)因为它指向的是一个const,所以这个const不能被改变(*p =202;是错误的,这里试图改变i的值),但是p本身也是一个对象,它的值(i的地址)是可以改变的(p=293810;是对的);而const指针(int a =42;int *const p = &a;),此时p是一个常量(所以必须初始化),它的值是永远不变的,但是a的值可以通过其他方式改变。
//顶层const
36.顶层const为本身就是一个const,底层const为指向const的指针或者引用。只有和const有关才有顶层const和底层const的概念,int a = 3;就不包含const元素,既不是顶层const也不是底层const。
//constexpr常量表达式
37.constexpr只值不会改变并且在编译过程就能得到计算结果的表达式。const int i=10;i是常量表达式,const int sz = get_size(); sz不是常量表达式,因为它的具体值直到运行时才能获得。
38.constexpr表示这玩意儿在编译期就可以算出来(前提是为了算出它所依赖的东西也是在编译期可以算出来的)。
39.一个constexpr指针的初始值必须是nullptr或0,或者是存储于某个固定地址中的对象。
40.constexpr把它锁定义的对象置为了顶层const(本身为一个const)。
41.constexpr指针不能指向函数体内定义的变量,因为他们并非存放在固定地址中,相反,定义于所有函数体之外的对象其地址固定不变,能用来初始化constexpr指针。
//处理类型
//类型别名
42.它是某种类型的同义词,便于理解。
43.有两种方法可以定义类型别名:
(1)关键字typedef
typedef double wages;//wages 是double的别名
typedef wages base,*p;//base是double的同义词,p是double*的同义词
可以把这个语句看成一个定义语句,typedef为基本数据类型,double为类型(相当于变量),wages为类型别名(变量初始化)。
(2)在C++11新标准中,可以使用“别名声明(alias declaration)”
using SI = Sales_item; //SI是Sales_item的同义词
using为关键字,此声明语句把等号左边的定义为右边类型的别名。
44.当类型别名指代的是复合类型时,把它用到声明语句时要十分小心:
typedef char *pstring; //pstring 是 char*的别名,即指向字符的指针
const pstring cstr=0; // 如果把“指向字符的”这个修饰词去掉,那const pstring就是常量 // 指针的意思,所以cstr就是指向char的常量指针
const pstring *ps; //指向指向char的常量指针的指针
//auto类型说明符
45.要把表达式的值赋值给变量就要知道表达式的数据类型,然而这并非那容易。C++11引入了auto类型说明符,它可以让编译器通过初始值来推算变量的类型。auto 定义的变量必须有初始值auto item = val1 + val2;//由val1+val2的结果来确定item的类型。
46.auto也能在一条语句中声明多个变量,不过要求所有变量的初始基本数据类型都一样。
auto i = 0 ,*p=&i;//正确,p为整型指针
auto sz = 0 ,pi=3.14;//错误,sz和pi的基本数据类型不一样。
47.auto一般会忽略顶层const,同时底层const会被保留
int i = 0;
const int ci = i ,&cr=ci;
auto b = ci; //b是一个整数(ci的顶层const(本身是常量)特性被忽略掉了
auto d = &i; //d 是一个整形指针(整数的地址就是指向整数的指针)
auto e = &ci; //e是一个指向整数常量(获取的是const int ci 的地址)的指针
const auto f = ci ; //f为 const int (从右到左理解)
48.如果引用的类型为auto,原来初始化的规则仍适用
auto &g = ci; //g是一个整型常量引用,绑定到ci(顶层const特质仍然存在)
//decltype类型指示符
49.希望从表达式的类型推断出要定义的变量的类型,但是不想用该表达式的值初始化变量。decltype(f()) sum = x; // sum的类型就是函数f的返回类型。
50.如果decltype使用的表达式是一个变量,则它返回该变量的类型(包括顶层const和引用在内):
const int ci=0,&cj=ci;
decltype(ci) x = 0;//x的类型为const int
decltype(cj) y = x; //y的类型是const int&,y绑定到x ,在这里cj是作为引用本身出现,而其他情况是作为它所引用的变量ci出现。
如果想让decltype返回的结果是引用所指的类型,可以用decltype(re+0)
51.int *p = &i; decltype(*p) 返回的类型是 int&,因为可以通过*p=10;修改i的值,有一种引用的关系。
52. decltype((variable)) 双层括号返回的类型永远都是引用,而decltype(variable)一层括号则根据variable的类型来确定返回类型。
//自定义数据结构
53.每一个类的对象都包含所有类的成员。
54.C++11新标准规定可以为数据成员提供一个类内初始值。没有初始值的成员将被默认初始化。
55.为了确保各个文件中类的定义一致,类通常被定义在头文件中,而且类所在头文件的名字应与类的名字一样。(把Sales_data类定义在名为Sales_data.h的头文件中)
56.确保头文件多次包含仍能安全工作的常用技术是预处理器(preprocessor),预处理器实在编译之前执行一段程序。
57.还有一项预处理功能是头文件保护符,头文件保护符依赖于预处理变量。
#ifdef SALES_DATA_H //#ifdef当且仅当变量已定义时为真,#ifndef当且仅当未定义为真
#define SALES_DATA_H //#define把一个名字设定为预处理变量
#include<string>//为了避免与程序中的其他实体发生名字冲突,一般把预处理变量的名字全部大写
struct Sales_data {
string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
};
#endif //一旦上述检查为这,则执行后续操作指导遇到#endif指令为止