C++Primer第5版学习笔记(一)

 

C++Primer第5版学习笔记(一)
第一、二章的重难点内容
       这个笔记本主要记录了我在学习C++Primer(第5版,中文版)的过程中遇到的重难点及其分析。因为第一、二章都比较简单,因此这里合并这两章我遇到的问题。
       第一章 开始
       这一章在第一部分之前,是一个helloworld式的章节,包含基本的函数,io流以及类的介绍。依靠这一章的内容可以创建第一个完整可被编译的cpp文件。
       知识点1:P19,1.5,文件重定向(英文版22页)

       可以在windows下的cmd中或者mac,linux系统的终端窗口中用输入命令的形式执行程序并使它从一个文件中读入数据,再把标准输出改为输出到另一个文件里。

       如果 我们编译好的程序为a.exe,程序需要输入两个整数,整数之间用逗号间隔,然后需要检测到EOF(End_Of_File,文件结束符)程序才会停止。这时为了文件重定向,我们可以新建一个文本文档(b.txt),接着我们在文档里记录”1,2“然后保存起来,保存后系统会在这个文件的末尾自动添加文件结束符。然后再新建一个空白文本文档(c.txt),使a.exe,b.txt,c.txt在同一个目录下。这时打开终端,转到这个目录,输入a <b.txt>c.txt回车确认。之后我们就实现了文件重定向了。
      但是不被缓冲的cerr的数据没法被重定向到指定文件里。这是终端的命令提示符的行为,并不是C++语法中的一部分。
      (交叉引用:P23,第一章小结)cerr和clog的内容都不会马上输出,都会先被存储到缓存区里面,因此你可以在缓存区被刷新之前,指定(重定向)它们输出的位置。而cerr是直接输出的,因此只能做标准输出,不能重定向。读cin,程序意外终止,使用endl都会刷新缓存区。
     
       第一章补充说明的内容根据C++Primer这本书的设定,有一些书上的内容并非C++语法,但是在书中起到了辅助我们编程和理解概念的作用,这部分的内容在笔记里将成为补充的部分被记录下来。
       1.关于练习的情景假设
       为了练习编写程序,C++primer这本书很多地方都用到了假定我们要编程处理一个书店的问题。并在此问题上慢慢扩展,最后实现这个书店的问题的编程。这个问题的假定是这样的:首先,书店程序将记录所有的交易记录。每条销售记录都会包含一下内容:ISBN号,用来记录本次交易的书的isbn号,isbn是书的唯一标识。之后,每条交易记录还含有本次交易的书的单价和销售本数。书店销售记录支持的操作包括查看书的销量,平均价格,计算每本书的销售额度,录入和读取每本销售记录等等。
        另外,我们能够对销售记录结构体进行的操作包括:读取书目的isbn号,把两个销售记录累加起来。输入输出一组交易记录。
       2.关于一些经常出现的词汇
       当我们说类型的时候,其实是在说一个对象的属性,或者多个对象的共同属性。当我们在说对象的时候,往往是说按照类型分类存在的变量实体。比如a是b类型的对象。b是a的类型。这种说法会在学习C++的过程中被多次提到。C++默认的类型加上我们自己定义的类型有很多,我们可以把任何概念使用这门语言的类型等设施抽象到代码中。所以一切都是对象这种说法就指的是这种通过语言提炼事物的基本信息,并把这种可以量化的信息记录保存和抽象的特征。
       3 .IDE
       IDE是指集成开发环境。如何使用代码生成可执行程序,如何在各个系统和各个系统的各种版本支持C++,怎样提供调试功能,这些点就是IDE解决的问题。基本上这部分的知识和C++语法没有关系,反过来,对C++和一些计算机知识了解的越多,就越能够理解IDE的各种功能和作用。因此,书中只是简单地提到了IDE的概念,关于如果下载,安装IDE,配置IDE或者使用IDE的特性不在此书的知识体系范围之内,应该另行学习使用IDE。因此书上只建议IDE应该选择起码支持C++11的版本。
       4 .回车键
       不同系统版本对按下回车键这个动作的处理是不一样的。通常我们只是想在控制台里敲入回车键来确认我们的输入,但是有的系统里也同时提供了一个回车键作为字符(换行符)输入。遇到一些复杂的情况回车键的使用可能导致未定义的行为。在第三章我们将看到,可以使用getline从输入流中读取换行符(回车)并抛弃它。因此,谨慎的使用回车键是很重要的。
       5 .进制转换和原反补码的概念
      不同进制的手动转换和原码反码补码这些概念都是一般高校C++课程中的考察范围内概念,不过C++Primer这本书没有对此特殊说明,因此在这里特别地补充这些概念。
      1.十进制整数转换为R进制整数。
       这里介绍除R取余法,要把数转换成哪个进制的数,R就是多少。这个方法把要转换为R进制的十进制整数不断除R,直到商为0为止,先计算出的余数就是被转化进制数的低位,后计算出的余数就是被转化进制数的高位。例如把10进制68转为二进制数,先68/2,得34,余0。因此对应的二进制数倒数第一位是一个0;34/2=17.......0,因此二进制数的倒数第二位也是0,17/2=8....1,倒数第三位是1;8/2=4.......0,倒数第四位是0;4/2=2.......0,因此倒数第五位是0;2/2=1......0,因此倒数第六位是0;1/2=0........1;商为0,停止这个过程,最高位为1.因此,对应的二进制数为1000100。完成进制转换。
     2.十进制纯小数转换为R进制纯小数。
     纯小数就是整数部分为0的小数。十进制纯小数可以不断乘R直到小数部分为0或者小数位数到达要求的精度对应的小数位数,这个方法也被叫做乘R取整法。比如把0.2135转化成对应的二进制数。首先0.3125*2=0.625。取整数部分0,这个0就是最高位。然后0.625*2=1.25。取整数部分1。剩下的部分是0.25,0.25*2=0.5,取整数部分0。0.5*2=1.0,取整数部分1。结果就是0.0101。
    综上所述,把一个整数部分不为0,小数部分也不为0的十进制数转化为R进制数只需要把这个数拆成整数部分为0的纯小数和小数部分为0的整数,分为转换并加在一起就行了。
     3.R进制数转化为10进制数。
     R进制数每一位乘R的数的权值次幂,得到的结果就是对应的十进制数,比如二进制数1101.11转换为10进制等于1*2^3+1*2^2+0*2^1+1*2^0+1*2^-1+1*2^-2。这个数就是十进制的13.75。
     4.二,八,十六进制的互相转换
     因为二、八、十六进制之间有内在联系,因此可以很方便的互相转换。每位八进制数相当于三位二进制数;每位十六进制数相当于四位二进制数字。因此,遇到一个二进制数转化为八/十六进制数,我们可以把这个二进制数划为3/4个数字一组的方式转换为对应的八/十六进制数字。比如有二进制数字1000100,把这个二进制数转化为八进制数。首先分组,3位数字一组。这个数就变成了001  000   100,每三个二进制数转为一个八进制数,100转为八进制是4,000转为八进制是0,001转为八进制数是1,因此这个数就相当于八进制的104。
    类似地,反过来每一个八/十六进制都是3/4个二进制数。比如十六进制的F7。首先7转为十进制是7, F转为十进制是15 。然后 7转为四位2进制就是0111,15转为四位二进制数就是1111。合起来就是11110111。
    另外,数值在机器中的表示也是非常重要的,下面是有关概念的介绍:
    原码:把符号位化为0或者1,之后把符号位和数值的绝对值在一起编码,之后得到的编码就是原码。一般把正数的符号位设为0,负数的符号位设为1。对于一个纯小数,整数位为符号位。
    反码:正数的反码是它本身,负数的反码是它去掉符号位之后按位取反。
    补码:正数的补码是它本身,负数的补码是负数的反码加一。符号位参与运算。
 

       第I部分:C++基础

       第二章  变量和基本类型
       C++Primer由四部分组成,这里是比较基础的第一部分,这部分共有六章。

       第二章主要讲变量,变量类型,定义变量的各种情况和规则以及变量之间互相转换的规则。

       知识点2:P30,2.1.1,C++语言关于类型的规定(英文版32页)

       C++语言的基本类型的设定与硬件紧密相关,因此很多类型的内存尺寸也都只是给了一个范围,其实各家IDE(LLVM,GCC,Visaul C++)的实现都是在范围内,具体的实现细节都是不确定的。

       其中bool最小尺寸未定义,char最小尺寸是8位,wchar_t和char16_t的最小尺寸都是16位,char32_t的最小尺寸是32位,int的最小尺寸是16位,long和long long的最小尺寸分别是32位和64位,对于浮点型数据的表现尺寸是按照精度计算的,其中float的最小尺寸精度是小数点后6位,double和long double的最小尺寸精度则是小数点后10位(实际可能比这个精度要大一些)。int不得小于short,long不得小于int,long long不得小于long。float,double,long double也应该是精度递增(或者相同)的关系。
       知识点3:P32,2.1.2,类型转换 (英文版35页)
       程序自动执行的类型转换操作发生在程序里IDE预期我们使用A类型但是实际上我们使用B类型的时候,B 类型的对象会自动转换为A类型的,如果没法转换,程序就会报错。
       我们先看赋值操作里表达式里面发生的自动转换,赋值操作A=B中,等号左边的A被叫做左值,B被叫做右值,程序期待事情是你给定的右值和左值类型完全相同。如果不相同,这里就会发生强制的类型转换,即把B的类型转化为A的类型。如果把一个超出左值类型表达范围的数赋值给左值,左值又是一个无符号类型,比如unsigned char c=-1;这时-1(整型,负的),右值会转化为无符号字符型,初始值对无符号类型表示数值总数取模,然后求余数,这个余数就是转化后的数。
       因为C++没有明确规定有符号类型的数应该如何表示,因此如果把一个超出左值类型表达范围的数赋值给左值,左值又是一个有符号类型,这种行为的结果是不一定的。我们把这种不确定造成结果行为叫做未定义行为。
        知识点4:P36,2.1.3,转义序列 (英文版39页)
       字符的转义序列可以为\后面加上最多3个8进制数字,或者\x后面加上最多两个16进制数字。数字转换成10进制后的大小不得超过字符集的限定范围。
        知识点5:P55,2.4.1,const的引用和指针 (英文版62页)
        const是用来声明常量的标识符,代表我们不能够使用const后声明的变量名更改变量的值。如有前提const int i=32;,则之后给i赋值的语句就都是错误的。但是在int a=0;const int &p=a;的前提下,给a赋值的语句却是正确的,因为a不被const修饰而声明,这仅仅意味着,我们不能够通过p这个名称更改p的实体——a的值。由此可见,我们允许把引用绑定到const对象上,但是必须用const int &i=ci;这种形式,其中ci可以是也可以不是一个const对象,但是这个绑定操作之后i的值是不可更改的。

         对一个常量的访问也可以用指向常量的引用或者指向常量的指针实现,不同的是,指向常量的引用是常量的一个别名,不能够被赋值,不过指向常量的指针本身是一个对象,它的本身值可以更改,不过指向的对象的值不能够被更改。

        如果不想让指针指向的对象被更改,可以用const指针,int a=1;int *const p=a;这样指针本身的值不可以被改变但是它指向的对象的值是可以被改变的。如const int *const p=a;这种语句使指针本身和它指向的对象值都不会被改变。
         知识点6:P57,2.4.3,顶层const (英文版64页)
      顶层const是对const而言的,“顶层”可以用来修饰const状态的形容词。一个const使对象本身的值固定,这个const就被称为顶层const,一个const是对象指向或引用的对象成为固定值,这个const就被称为底层const。顶层和底层const对拷贝来说密切相关,有相同底层const资格的两个对象才能够互相拷贝,而且顶层const声明变量之后不允许再次改变const的值。int p,const int *a=&p;这种语句中的const就是底层const。

      像int v1=9;const int *p=&v1;int *p2=p;这种语句如果能够通过编译,那么我们就可以使用p2的性质改变p1指向的常量的值,但是常量的值是不能够被改变的,因此这种变相改变常量的值的表达式都是错误的。可以通过分析const级别得到表达式中常量是否被更改,从而判断语句的正确性。

      说到底,顶层底层说的是对拷贝控制的约束。总的规则就是“不能改变常量的值”。因此“拷入和拷出的对象都要有相同的底层const资格,或者两个对象数据类型必须能转换”,例如,有int *p1,const int *p2;。p1没有底层const,p2有底层const。p1=p2;这时const int*不能转换成int *(如果转换,就违反了“不能改变常量的值这一约束条件”),因此p1=p2;不合法。p2=p1;int * 能够转换成const int *,因此p2=p1合法。
      知识点7:P58,2.4.4,constexpr (英文版65页)

      我们在了解constexpr之前,应该先了解常量表达式。所谓常量就是固定的量,那么常量表达式就是值固定不变的表达式,这里“值固定不变”,指的是程序编译阶段,常量表达式的值就能被确定下来之后也不能对其进行任何种方式的修改。因此这个固定,是编译之后固定的。像cout<<1234<<endl;中的1234,就是常量表达式,显然,字面值是常量表达式。

       constexpr的作用之一就是帮助程序员在IDE的提示下查看一个赋值语句是不是常量表达式。使用的方式包含在声明语句里面,形如constexpr 变量类型 变量名=右值;如果右值是一个常量,这条语句就是正确的。在所有函数体外声明的全局变量的地址就符合“在编译期间能确定,编译后值不被改变”这两个条件,因此也属于常量。
    另外,用constexpr声明的指针(比如,constexpr int *p=&v1;中的*p,相当于int *const p=&v1;   )都是顶层const,即指针本身值固定。但是指针指向的内容是可以变的。引用也一样。
 
     知识点8:P61,2.4.4,类型别名 (英文版68页)
       使用typedef int zhengxing;这种对简单的类型名进行替换的方式无疑是非常直观并且好理解的,但是在涉及到复杂的类型名的时候往往会出现各种各样的问题。比如typedef char *Pstring;这条语句是不是就意味着我们看到Pstring就可以用char *替换呢。其实并不是,实际上类型别名不只是替换的规则,要复杂很多。比如我们遇到const Pstring a;的时候,按照替换的规则,这条语句就相当于const char * a;这里的const这种情况下是底层const,但是结果并不是这样的,这条语句正确的等同语句应该是char *const a;是一个顶层const,即指针本身是一个常量。让我们来分析一下为什么是这个样子,不是简单的替换就行了。 typedef char *Pstring; 这条语句就是说Pstring是一个类型别名,它是什么类型的类型别名呢?Pstring是 指向char的指针的类型别名 ,也就是说,这个类型修饰的对象必须是一个指针,这个指针也必须指向char而不能指向别的什么东西,比如,不能指向const char。我们再看看const Pstring a;这个语句,首先a一定是指向char的指针。所以这个前面的const应该是用来修饰这个指针本身。也就是说,这个指针是常量指针而非指向常量的指针。这一点非常重要。 const char * a 这个语句里面,实际上类型是const char,*是声明符的一部分。我们说过,定义一个变量由两部分组成,类型名和声明符,声明符可以是*或者&加上变量名的形式。而类型别名只是给类行一个别名,至于声明符是怎样并不管。因此在有const的情况下,就可以看出来这两者之间的区别还是很明显的。
 
       知识点9:P61,2.5.2,auto和decltype类型声明/指示符 (英文版69页)
       auto变量通过初始化语句,计算出右值的类型,并推导出左值的类型。这个过程中auto将会忽视顶层const和引用类型,可用const auto &a=i;这种方式显式地指出了:指出要推导的结果是带顶层指针属性的或者是引用属性的。auto推导多个值时,这些值的类型必须是一样的。因为auto是利用初始化赋值,因为此它的行为基本上也和初始化有关。
       decltype不通过计算,只通过推算出变量应有的值,表达式本身应有的值和函数的返回值来推导类型。对于变量类型,decltype保留顶层const和引用的属性。对于表达式,解引用表达式(如:int i=1;   int *p=&i;    decltype (*p) a=i;中的*p,对p解引用是int &类型的)和带括号的表达式,(如:decltype ((a+1)) c=i;)的结果都将是引用类型。因为decltype通过处理表达式得到结果,因此更详细的内容在第四章将会被提到。有的表达式返回左值,有的表达式返回右值,返回左值的表达式在decltype类型推导下得到的将是引用的结果。
     知识点10:P65,2.6.1,用关键字struct自定义数据结构 (英文版73页)
      使用struct关键字定义类的形式如struct 类名{数据成员类型1  数据成员名1;数据成员类型2  数据成员名2;};C++11规定可以给类内成员提供类内初始值用于初始化用我们自定义类创建的对象实例中的成员的值。形式如下:
struct MyClass
{
     int student=0;
     float numbers=1;
};
 
       第二章补充说明的内容:
       1.对于已知的类型范围的编程假定
       为了很多情况下我们都会默认我们开发机的一些环境就是客户机的使用环境。因为类型本身是依赖于机器的,因此这里也应该使用固定的类型值比如int32_t类型。另外,使用防越界溢出的良好编程风格也对程序有很大帮助。使用C++语言编程时,可以执行仍然是我们需要时刻考虑的问题。
       2.不同语境下对象这个词的不同含义
      通常情况下对象指一块能存储数据的内存空间,但是当我们想区分命名的对象和未命名的对象的时候,通常把命名了的对象叫做变量,相应地,这时并列提出的对象可能指的是普通的内存区间;当我们区分对象和值的时候,又衍生出来了左值和右值的概念(第4章),这时会把对象所具有的属性作为对象,对象本身的内容作为值的说法被表述。因为一切都可以叫做对象,所以这是一个意义广泛的概念。通常交流中我们想表达具体的概念的时候也会使用对象这个词汇来表达我们的想法。但这时候要联系上下文和语境来猜测这个表达出来“对象”究竟指的是什么。
       3.关于extern
       extern外部变量声明其实是在IDE进行编译的时候告诉IDE,这有一个外部变量你要去别的地方找。因此我们应该掌握编译链接这套流程才能够更加方便的会用extern。假设有一个头文件a.h,这个头文件里面定义了int aaa=0;还有一个源文件b.cpp。这个b.cpp里面使用了extern int aaa;这样的语句,那么这个b.cpp是编译不了的。因为头文件如果不被别的源文件引用,是不参与被编译为obj的过程的,一旦它不参与这个过程,它里面声明的aaa这个全局变量其实就不存在,因此在b.cpp里面外部生命一个不存在的变量自然就是非法的。另外,使用extern也要和static做区分并考量它在别的文件中会不会造成内存污染等问题。这里应该掌握分离式编译的编译和链接特性再使用extern比较好。

转载于:https://www.cnblogs.com/bugrabbit/p/5092681.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值