C++Primer 第二章详细梳理

章节提要:

2.1 基本内置类型

   1)基本类型

1,注意区分无符号数和有符号数,对于char 类型,分为3种,unsigned char ,signed char ,char ,char 和其他两种之所以不一 样,是因为在一些机器上是char 是带符号的,一些机器上是不带符号的,这对于移植性有很大问题。尽量不要使用char做算术运算,如果实在需要,请指明是unsigne  char,还是signed char.

2,关于类型的选择:
①如果明确知道不会为负,使用无符号类型。  
②对于int型,小的使用int,实在比较大使用long long即可,short 太小,long和int一样大。   
③对于浮点型,一般使用double,因为float位数太小,而且对于运算速度和double相比没什么差别,long double精度太高,没有必要,而且由于long double精度太高,带来的运算代价太高。

   2)关于类型的转换

1.类型转换一般以等号左边的为基准
   bool —— int 真为1,假为0。
  int —— bool 0为假,负数和正数都为真。
   浮点型 —— int 只取整数,不四舍五入
3.对于无符号类型,赋值超出范围后,所得到的值是取模后的数。
4.对于有符号类型,赋值超出范围后,所得到的数是未定义的。
5.在一个表达式中,如果又有有符号数,又有无符号数,最终有符号数会被转换为无符号数。(最好别混用)
6.一个机器上short类型占16位,那么可赋值的最大数为2^15-1.

  3)字面值常量

         1.浮点数的表示用e或者E表示指数

         2. 每个字面值都有其默认的类型,比如 5的默认类型是10进制,5.42的默认类型是double。在进行赋值的时候,会进行类型 转换。例             如: float val=5.42;其中5.42默认是double类型的,在赋值前会进行类型转换,这种转换有时候会报错,因此可以修改字面值得默认类    型。

         一个典型的例子:使用C++11 新规则初始化一个数 

         

 float fVal{12.5};//由于12.5默认是double,现在要转换为float精度会损失,编译器就会报错。

         但是:

       

  float  fVal{12.5F};//12.5F默认类型是float,没有精度损失,编译器不会报错。

         3.类型修改:

                         前缀修改字面值默认类型:(一般用于字符)

                         u            Unicode 16字符
                         U            Unicode 32字符
                         L             宽字符
                        u8            UTF-8
                        0             8进制
                        0x             16进制

      2.后缀修改字面值默认类型:(一般用于数字)
   u || U        unsiged 用于整型
   l || L           long 用于整型
   ll || LL        long long 用于整型
   b || B          二进制
   f || F           float 用于浮点型
   l || L            long double 用于浮点型

2.2变量

   1)变量的定义

1.变量的定义: 一个基本数据类型+一组声明符(为什么是一组,因为声明符可以相互修饰)
2.建议初始化每个内置类型变量,指针。(优秀代码)
3.初始化和赋值完全不同,初始化时在创建这个变量的时候,赋予一个初始值,赋值是擦除原来的值,用一个新的值代替原来的。
4.区别初始化和赋值就是再看有赋值符号的时候,赋值符号左边有没有一个新的变量产生。
5.初始化的方式一般4种:
int units_sold=0;
int units_sold={0};
int units_sold(0);
int units_sold{0};

其中花括号叫列表初始化,C++ 11新特性,可以防止精度损失。

   2)变量的声明&定义
1.为了能将程序拆分开编辑,C++语言是支持分离式编译的。为了支持分离式编译。C++把变量的声明和定义区分开来。 声明 :使得名字为程序可知,一个文件如果想使用在别处定义变量,就必须有对这个变量的声明。声明不会分配内存。 定义:负责创建与名字相关联的实体。会分配内存。声明可以声明多次,但是定义只能定义一次。

2.声明一个变量在前面添加extern.例如extern int  i;

3.在声明一个变量的时候,不要初始化,这样会抵消extern的作用。

4.P41页(中文版)一句话:声明和定义的区别看起来也许微不足道,但是实际上却非常重要,如果要在多个文件中使用同一个变量,就必须将声明和定义分离。此时,变量的定义必须出现在一个文件中,而其他用到该变量的文件必须对其进行声明,却不能重复声明困惑:以前刚开始学C++的时候,喜欢用全局变量,在我的眼里,定义在非函数体内的变量就是全局变量。比如定义在Test.h 定义一个int i=0;然后其他文件包含这个头文件不一样也可以用么。为什么非要extern。嗯,后来去实践了一下,在编译链接的时候,会报错,重复定义同一个变量。而只在头文件extern 一个变量,然后在任何cpp文件中定义一次这个变量,就不会再报错了,同时不用include 其他文件,编译器会帮你找到你所声明的变量的定义的地方,这就是extern得作用。函数声明是默认extern的,这个得注意。

由此想到。static的作用。static的作用是使得这个变量只在这个文件中有效,和extern是相反的,因此在一个头文件定义一个静态变量,其他文件再包含这个文件,编译链接的时候也不会出错。

    3)标识符

标识符没什么好说的,命名规范:开头必须是字母或者下划线,整个标识符只可以由数字,字母,下划线组成。

命名有两种风格:1.C风格全小写字母:int  stau_code    2.面向对象风格:stauCode.选择一种,坚持下去

    4)作用域

一般来说,作用域是用花括号分割开来的{};值得注意的是花括号{}是可以单独存在的,表示一个单独的作用域。

2.3复合类型

    1)引用

1.记得Effective C++的中文版,翻译者侯捷老师在前言里面提到,一些感觉中文翻译不够理想的,他便保留原文不译,其中reference便是其中一个。在一些其他书里面,reference被翻译为别名。争议存在就肯定有其道理,因此不要按照中文的引用的原意去理解这个类型~。

2.定义一个reference类型:一个类型说明符+&声明符,并被初始化。

3.由于reference类型是默认const类型的,而const类型在定义的时候必须初始化,且不可被“赋值”(严格的说这样的说法是错误的因为引用不是对象,但是可以这样理解)。

4.reference类型是只是并非一个Object,它只是一个名字,它的定义不会被分配内存,自身没有地址,它仅仅是其他对象的“绰号”。

5.由于第3点的原因,不能定义一个reference的reference类型。(reference的实现和指针相关,它没有地址,所以不能被reference)。

6.一定得注意的:除了一些特殊的情况,所有的引用类型必须都必须和与之绑定的对象类型严格匹配。不然同一个对象会拥有两种类型。

    2)指针

1.指针(pointer,point to)是用来实现对一个对象的另外一种访问方式。

2.不要混合指针和reference。指针是一个对象,它自己也有地址,不是默认const类型。

3.定义一个指针类型:一个类型说明符+*声明符,并被初始化。

4.注意:声明符必须放在每个变量前面。

5.P47页(中文版)有句话:因为引用不是对象,没有实际地址,所以不能定义指向引用的指针。读到这句话的时候,想了下,指向reference的指针不是应该会被指向被reference的对象么。为什么不可以,在VS上进行实验,也能运行。难道书上错了或者是编译器优化?后来,细细解读指向reference的指针:

char &*pStr=pStrCout
这才是指向reference的指针类型.

6.前面提到过,定义个指针或者内置类型的变量的时候,最好赋一个初始值。给指针赋没有确定的情况下最好赋为空。 0,NULL,nullptr.

    3)理解复合类型的声明

1.最重要的:变量的声明为一个类型说明符+一组声名符,声明符为一组并且可以相互修饰,例如上面的char &*,int *&.这种,记住一个原则:往往越靠近变量的声明符对变量的影响越大(从右到左)比如char &*,首先它是一个char类型的指针这个指针指向的是一个引用。
int *&首先它是一个int类型的引用,这个引用引用的对象是一个指针。

2.4 const限定符

1.记住const与非const是两种不同的类型,很多成员函数可以因为是否有const特性而重载,而很多人却忽略了这个事实。

   1)const引用

1.const限定符只是告诉编译器这个变量的值是不能被write,只要是不修改它的值,其他的任何操作都是正确的。

2.const对象,由于被定义后值不能再被改变,因此在定义的时候必须初始化。

3.如果想在多个文件中共享const对象,必须在变量定义之前添加extern。

4.const对象分为编译时常量和定义时常量,这两种常量有一定的区别,比如数组定义的时候,要定义一个定义时常量。

5.给一个普通引用,指针赋值的时候,等号右边不能是一个常量对象.记住,。(其实不止是不能是一个常量,由于指针的特殊性,给指针赋值的时候,一定要考虑好这个值是不是真的能赋值给这个指针)

6.const的特殊性:前面提到,一个引用引用的类型必须是一个对象。但是对于一个const引用是一个例外。比如想要获得一个对象的内容为42的对象的引用。我们正常的代码是int uVal=42;int &rVal=uVal;但是由于const的特殊性,编译器会帮我们做这个优化,我们可以直接这样定义:const int  &rVal=42;道理同样,编译起帮我做了int uVal=42这一步,问题来了,为什么const可以这样做,其他非const对象不能这样写。再看一个例子:double dVal=42.423;想要定义一个引用引用这个dVal,但是由于某些原因,这个引用需要是int类型的,我们会这样做:int uTemp=dVal; int &rVal=uTempl;在int uTemp=dVal发生了隐式转换,然后再赋值给这个int类型的引用,同上,对于const 对象,我们可以直接const int &rVal=dVal;编译器会帮我做上面的步骤,因为我们保证了dVal是不会改变的,如果改变rVal,但是rVal引用的实际是uTemp,dVal并没有改变。所以必须是const。

7.深入理解引用:可以通过引用看到赋值和初始化的区别:给一个非常量引用初始化的时候,类型必须严格一致,但是给在赋值的时候却可以发生类型转换。例如:

int i=0, &r1=i;
double d=0,&r2=d;
可以这样:r1=r2;r1=4.32;都不会发生错误。但是int &i=d;就会报错,这就是初始化和赋值的区别。

   2)指针与const

1.指向常量的指针:一个指针指向一个常量对象:const double pi=3.14;那么这个指针的定义:const double *ptr=π(还是按照前面所说的:离变量越近的声明符对变量影响越大:首先这是一个double类型的指针,这个指针指向的时一个常量)(指向常量的指针可以指向非常量对象)

2.常量指针:常量指针是说的一个指针,这个指针是一个常量。类似引用那样:double *const ptr=π(理解:ptr是一个常量,它的值不可以改变,这个常量是一double型的指针)。

3.如上说的,常量指针是一有类似引用的属性,它定义的时候必须初始化。

4.指向常量的指针和常量指针区别:

指向常量的指针所指向的对象是一个常量。*ptr不能改变,但是指针所指向的对象ptr能改变

常量指针是指针本身是一个常量,指针只能一直指向这个对象,不能改变,但是所指向的对象的值可以改变。


   3)顶层const

1.顶层const:对象本身是常量,底层const:所指对象是const(一般用于指指针或者引用)
2.顶层const比较随意,并且赋值和被赋值都没有要求,但是底层const必须注意:当等号左边是一个非底层const的时候,等号右边不能是一个非const对象,而当左边是一个底层const对象的时候,右边可以随意.

举例:const int c=1;   const int &r=c; //正确   int &rc=c;//错误 

   4)constexpr表达式(目前好像还是有些编译器不支持此关键字)

1.constexpr定义:[C++11] ①值不会改变 ②编译过程常量 

2.constexpr和const的区别:const 是指这个值是不会被改变的。而且分为编译时常量和运行时常量而constexpr是指在编译期期间就可以确定的常量,简单的说就是比const还const的常量,

3.constexpr可以用来修饰函数。例如{return 42;}这种函数,但是编译器并不负责检查这个constexpr是否修饰成功,人工检测方法:将这个函数拿来作为数组的大小。比如int array[fn()];若没有报错则表示修饰成功。

4.constexpr在用来修饰指针的时候,这个指针要么是必须初始化为空,要么指向的对象的地址是固定不变的(static对象,全局对象);

5.并没有所谓的指向constexpr对象的指针,constexpr只能修饰指针。constexpr int *q=nullptr; q是常量指针。

2.5 处理类型

   1)类型别名

1.类型别名有两种定义方法:

①:typedef double wages; // wages=double        typedef wage *p //p=double *

②using SI=double;//SI=double

2.值得注意的理解:

typedef char *pstring;

const pstring cstr=0;//注意其中cstr是一个常量指针

const pstring *ps; ps是一个指针,指向一个指向char类型对象的指针。

错误的理解:将pstring换成char *.比如  const char * cstr=0;按照这样的理解,cstr应该是一个指向常量的char类型的指针。。然而在typedef这个定义中,把char*作为一个整体取了一个类型别名pstring,在const pstring cstr=0;中,const是修饰的pstring这个整体,也就是说修饰的是指针而不是char。

   2)auto类型说明符

1.auto(自动类型说明符)是通过等号右边进而推断等号左边的类型。int ci=42;   auto index=ci;//index被推断为int;

2.auto会忽略顶层const特性而保留底层const特性(想想吧,它不得不这样做,要是不保留底层const特性,那么赋值便是错误的,那为什么要忽略顶层const特性呢,这是为了保留可扩展性,然用户自己选择是否真的需要const,万一只是需要一个变量呢,而真正需要const可以看第3条)

3.若想给auto加上顶层const特性:const int ci=42;   const auto f=ci;


   3)decltype 类型指示符

1.decltype(类型提取说明符)是通过表达式的返回类型而推断出类型。decltype(fon()) sum=x;(与auto不同之处,当想用这个表达式的返回值初始化这个变量的时候,用auto,只需要它的类型,而不需要这个值得时候,用decltype)。

2.decltype是类型提取,必须提取完全,因此他会保留所有的顶层和底层const特性。

3要给一个decltype修饰的变量加上const特性,const decltype(fon()) f=42;

4.值得注意的地方:int i=42,*p=&i,&r=i;

decltype(r+0) b;//因为r是一个引用类型,decltype得到的是引用类型,r+0返回的时int类型,则b是一个int类型。

decltype(*p) c;//c是一个引用。为什么:因为p为一个指针,而对这个指针解引用得到这个指针所指的变量,但是这个变量有个特性,就是可以作为表达式的左值,因此这是一个引用类型,更加详细的说明见第5条。

5.decltype((i)) e;//e是一个引用,因为对i加一个括号修饰符,decltype会把它提取为一个表达式,一个变量可以作为赋值语句的左值,这种类型和引用具有完全相同的特性,因此e被作为一个引用,

2.6自定义类型

   1)编写自己的头文件

1.不管需要不需要,最好给头文件加上防止多重定义的预处理符:

①:#ifndef XXXX

      #define XXXX

      #endif


②:#pragram once

2.头文件一般放的东西:

1.函数的声明

2.需要在多个文件中使用的变量的声明

3.类或者结构体

4.模版

5.inline函数

6.const变量

7.类函数成员,类数据成员的声明

8.预处理命令

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值