C++ Primer 5 之 变量与基本数据类型

一直在学习C/C++编程, 但是一直在零散的学,很多内容都是看了就忘,用的时候还想不起来 ,这很是让人头疼不已啊,于是打算在C++ Primer 5的基础上,系统的回顾一下在C++编程过程中可能遇到的各种知识点。当然本系列内容主要是基于C++ Primer 5 这本书来进行总结的,内容覆盖面及内容深度比较有限, 主要目的是为了系统的学习了解一下,如果能给同行的小伙伴提供点帮助,那就再好不过了,当然本系列内容纯属个人总结,如有不正之处,还请各路大神见谅,并帮忙指出和完善,小森哥儿在此跪谢!!!

关于变量和数据类型这部分,对于只要有点C/C++基础的学习者来说,这部分恐怕应该是最熟悉的一部分了大笑。罗列了一下感觉比较有必要注意的N处:      

(1). 调试程序时常常添加打印语句(cout<<endl;). 写入endl 的效果是结束当前行,并将于设备关联的缓冲区中的内容刷到设备中,缓冲刷新操作可以保证到目前为止程序所产生的所有输出都真正写入输出流中,否则,如果程序崩溃,输出可能还留在缓冲区中,从而导致关于程序崩溃位置的错误推断。

(2). 包含来自标准库的头文件时用尖括号(< >)包围文件名,否则,比如自己写的头文件,则用双引号包围。

(3). 基本内置类型包括算术类型和空类型,算术类型的尺寸在不同机器上有所差别,单C++标准规定的最小尺寸值是:bool-未定义,char-8,int-16,long-32,long long-64,float-6位有效数字,double-10位有效数字,long double-10位有效数字。

(4). 一个char的空间应该确保可以存放机器基本字符集中的任意字符对应的数字值,即一个char的大小和一个机器字节一样大,wchar_t,char16_t,char32_t 是扩展字符集,wchar_t 类型用于确保可以存放及其最大扩展字符集中的任意一个字符,类型char16_t 和char32_t 则为Unicode字符集服务(是用于表示所有自然语言中字符的标准)。

(5). C++中一个字节至少要能容纳机器基本字符集中的字符,大多数机器的字节由8比特构成,字则由32或64比特构成,也就是4字节或8字节。大多数计算机将内存中的每个字节与一个数字(被称为“地址” address,如736424、736425、736426、736427为内存中连续的4字节的地址)关联起来,即计算机可寻址的最小内存单位是字节,我们能够使用某个地址来表示从这个地址开始的大小不同的比特串,如地址为736424的那个字或地址为736427的那个字节等。                                                                                        

(6). 带符号和无符号类型。出去布尔型和扩展的字符型之外,其他的整型可以划分为带符号的和无符号的两种,int、short、long都是带符号的,在这些类型前加unsigned 得到无符号的类型。与其他整型不同,字符型被分为三种类型,char、signed char和unsigned char 。特别需要注意的是类型char和类型signed char 并不一样,尽管有三种,但是字符的表现形式却只有两种:带符号的和无符号的,类型char实际上会表现为上述两种形式之一,具体哪种邮编一起决定。 

(7)执行浮点数运算时尽量选用double,这是因为float通常精度不够而且双精度浮点数和单精度浮点数的计算代价几乎无差,事实上,对某些机器来说双双精度甚至比单精度还要快。long double 提供的精度在一般情况下是没有必要的 ,而且它带来的运算代价也是不容忽视的 。

(8) . 切勿混用带符号数和无符号数,因为如果表达式中既有有符号数又有无符号数类型时,当带符号数取值为负时会出现异常结果,因为带符号数会自动装换成无符号数。

(9). 由单引号括起来的一个字符成为char行字面值,由双引号括起来的另个或多个字符则构成字符串型字面值。 

(10). 在很多场合“对象”这个词频繁出现,通常它是指一块能存储数据并具有某种类型的内存空间。 如定义一个变量,该变量即可称为一个对象。 

(11). C++中由于采用等号=来初始化变量,会使人误以为初始化和赋值是同一种操作,其实不然,初始化不是赋值,初始化的含义是创建变量时赋予其一个初始值,而赋值的含义是把对象的当前值擦除,而以一个新值来替代。

(12). 初始化方式中,C++11新标准中,用花括号来初始化变量得到了全面的应用,这种初始化被称为列表初始化,如:

int units_sold ={0};

int units_sold{0};

当用于内置类型的变量时,这种初始化形式有一个重要特点,如果我们使用列表初始化且初始值存在丢失信息的风险,则编译器报错。

(13). 如果定义变量时没有指定初值,则变量贝默认初始化。如果内置类型的变量违背显式初始化,它的值由定义的位置决定,定义域任何函数体之外的变量初始化为0,但是,在函数体内部的内置类型变量将不被初始化,一个未被初始化的变量的值是未定义的,如果试图拷贝或者以其他方式访问此类值将引发错误,而且在程序中很难发现和调试。建议初始化每一个内置类型的变量,虽然并非必须这么做,但是如果不能确保初始化后程序安全,那么这么做不失为一种简单可靠的方法。

(14). 变量声明规定了变量的类型和名字,在这一点上与定义相同,但是除此之外,定义还申请空间,也可能会为变量赋一个初值。

             建议: 在对象第一次被使用的地方附近定义它是一种较好的选择,便于找到该变量的定义,并且有助于给它赋予一个合理的初值。

(15). 关于标示符的命名规范的建议:

       a). 标示符要能体现实际含义。

       b). 用户自定义的雷鸣一般以大写字母开头,如Sales_item。

       c). 如果标示符有多个单词组成,则单词应有明显的区分,如:student_loan、studentLoan,而不要使用studentloan。 

对于命名规范来说,若能坚持,必将有效。

(16). C++11标准新增了一种引用,所谓的“右值引用”,这种引用主要用于内置类型。而当我们使用术语“引用”时,指的是左值引用。 

(17). 定义引用时必须初始化,一旦初始化完成,引用将一直和它的初始对象一直绑定在一起,无法令引用再重新绑定到另外一个对象上。引用本身不是一个对象,它只是一个别名。 

(18). 所用引用的类型都要和与之绑定的对象严格匹配,(两种例外情况除外),而且引用只能绑定在对象上,而不能与字面值或某个表达式的计算结果绑定在一起。 

(19). 指针与引用相比有很多不同点:其一,指针本身是一个对象,允许对指针赋值和拷贝,而且指针在其生命周期内可以先后指向几个不同的对象;其二,指针在定义时无需赋初值,和其他内置类型一样,在块作用域内定义的指针如果没有初始化,也将拥有一个不确定的值。 指针的类型也都要和它所指向的对象的类型严格匹配(两种例外情况除外)。

(20). 空指针不指向任何对象,生成一个空指针的方法如下:

int  *p1=nullptr;

int  *p2=0;

int  *p3=NULL;

得到空指针的最直接的方法就是使用字面值nullptr 来初始化指针,NULL为一个预处理变量,这个变量在头文件cstdlib 中定义,它的值就是0.当遇到一个预处理变量时,预处理器会自动的将它替换为实际值,因此用NULL初始化指针和使用0来初始化指针是一样的。在新标准下,现在的C++程序最好使用nullptr,同时尽量避免使用NULL。  使用未经初始化的指针时引发运行时错误的一大原因,有时错误很隐蔽,很难发现和调试,因此建议:初始化所有指针。

(21). void * 指针. void * 是一种特殊的指针,可以用来存放任意对象的地址,一个void *指针存放着一个地址,这一点和其他指针类似,不同的是,我们对该地址中到底是什么类型的对象并不了解。 例如: void * pv= &obj ; //obj 可以是任意类型的对象.  利用void*指针能做的事比较有限:拿它和别的指针比较、作为函数的输入或输出,或者赋值给另外一个void * 指针。不能直接操作void*指针所指的对象,因为我们并不知道这个对象到底是什么类型,也就无法确定能在这个对象上做哪些操作,概括来说,以void*的视角来看内存空间也就仅仅时内存空间,没办法访问内存空间中所存的对象。

(22).  const 关键字用来对变量类型进行限定,这种变量的值不能被改变。因为 const 对象一旦创建后其值就不能改变,所以const对象必须初始化。

默认情况下,const 对象被设定为仅在文件内有效,当多个文件中出现了同名的 const 变量时,其实等同于在不同文件中分别定义了独立的变量。

(23). 对于这样一种情形:一个 const 变量,它的初始值不是一个常量表达式,但又确实有必要在文件间共享。这种情况下,我们不希望编译器为每个文件分别生成独立的变量,相反,我们想让这类 const 对象像其它(非常量)对象一样工作,也就是说只在一个文件中定义 const ,而在其他多个文件中声明并使用它。   解决方法是:对于 const 变量,不管是声明还是定义都添加 extern 关键字,这样只需要定义一次就够了。

(24). const 的引用。与普通引用不同的是,对常量的引用不能用作修改它所绑定的对象的值。常量引用是对 const 的引用。关于引用的第一种例外情况就是:在初始化常量引用时允许使用任意表达式作为初始值,只要该表达式的结果能转换成引用的类型即可。尤其,允许为一个常量引用绑定非常量的对象、字面值,甚至是一般表达式。

例如: int i=42;  const int &r1=i  ;   const  int &r2=42  ;   const int &r3=r1*2   ;   // 均正确

            int &r4= r1*2   ;      //错误,因为r4是一个普通的非常量引用 .

必须认识到,常量引用仅对引用可参与的操作做出了限定,对于引用的对象本身是不是一个常量并未作限定。因为对象也可能是个非常量,所以允许通过其他途径改变它的值。

(25). 指针和 const 。指向常量的指针 和 常量指针 是两个不同的内容。令指针指向常量就是指向常量的指针,指向常量的指针不能用于改变其所指对象的值,而若想存放常量对象的地址,只能使用指向常量的指针。关于指针的第一种例外情况是:允许一个指向常量的指针指向一个非常量对象,和常量引用一样,指向常量的指针也没有规定其所指向的对象必须是一个常量,所谓的常量指针仅仅要求不能通过改制真改变对象的值,而没有规定那个对象的值不能通过其他途径改变。常量指针就是把指针本身定义为常量,常量指针必须初始化,而一旦初始化完成,则它的值(也就是存放在指针中的那个地址),就不能改变了,即不变的是指针本身的值而非指向它的那个值,能否通过该常量指针修改其所指向的对象的值,则依赖于它所指向的对象的类型(对象的类型时变量就可修改,若是常量则不可修改)。

(26).顶层 const 和底层 const 。如上所述,指针本身是不是常量以及指针所指的是不是一个常量就是两个相互独立的问题。顶层 const 表示指针本身是个常量,底层 const 表示指针所指的对象是一个常量。  顶层 const 可以表示任意的对象是常量(即任意的数据类型均可以定义成顶层const),这一点对任何数据类型都适用如算术类型、类、指针等,底层 const 则与指针和引用等复合类型的基本类型部分有关,指针类型既可以是顶层const也可以是底层const。比较特殊的是引用本身不是对象,因此无法对引用定义顶层const,所以用于声明引用的const都是底层const 。

(27). 当执行对象的拷贝操作时,顶层const不受什么影响,底层const的限制却不能忽视,当执行对象的拷贝操作时,拷入和拷出的对象必须具有相同烦人底层const资格,或则两个对象的数据类型必须能够转换,一般来说,非常量可以转换成常量,反之则不行。

(28). 常量表达式,常量表达式是指值不会改变,并且在编译阶段就能得到计算结果的表达式,显然,字面值属于常量表达式,用常量表达式初始化的const对象也是常量表达式,在实际使用时,尽管要求如此却常常发现初始值并非常量表达式的情况。C++11新标准规定,允许将变量声明为 constexpr 类型以便由编译器来验证变量的值是否是一个常量表达式,声明为constexpr 的变量一定是一个常量,而且必须用常量表达式初始化。

(29). 常量表达式的值需要在编译时就得到计算,因此对声明constexpr时用到的类型必须有所限制。尽管指针和引用都能定义成constexpr,但他们的初始值却受到严格的限制,一个constexpr指针的初始值必须是nullptr 或者0,或者是存储于某个固定地址中的对象。

函数体内定义的变量一般来说并非存放在固定地址中,因此constexpr指针不能指向这样的变量,相反的定义于所有函数体之外的对象其地址固定不变,能用来初始化constexpr指针。此外,有时允许函数定义一类有效范围超出函数本身的变量,这类变量和定义在函数体之外的变量一样也有固定地址,因此,constexpr引用能绑定到这样的变量上,constexpr指针也能指向这样的变量。 必须声明一点,在constexpr声明中如果定义了一个指针,限定符constexpr仅对指针有效,与指针所指对象无关。

(30). 类型别名,有两种方法可用于定义类型别名,传统的方法是使用关键字typedef,

如   typedef double  wages ;   //wages 是double的同义词

      typedef wages base   ;     // base 是double的同义词

C++ 11新标准规定了一种新方法,使用 别名声明 来定义类型的别名: using SI= Sales_item ; SI 是Sales_item 的同义词。

遇到一条使用了类型别名的声明语句时,人们往往会错误的尝试吧类型别名替换成它本来的样子,注意,这种理解是错误的!!! 

例:  typedef char *pstring;  // pstring 代表char指针类型

         const pstring cstr = 0;  // cstr 是指向char的常量指针

但是如果把pstring还原成原来的样子,则变成

         const  char * cstr =0;   //const char 成了基本类型,cstr 是一个指向字符常量的指针

则意思与原来截然不同。

(31). auto 类型说明符。C++新标准引入了auto类型说明符,用它就能让编译器替我们去分析表达式所属的类型,如:auto item=val1+val2;此时编译器根据val1和val2相加的结果来推断item的结果。auto一般会忽略掉顶层const,同时底层const值保留下来。注意,利用auto在同一条语句中定义多个变量是,各初始值的类型必须是同一个类型。

如:int i=0;

       const int ci=0;

       auto k=ci,&l=i;       //正确,顶层const特性被忽略掉,k是一个整数

       auto &n=i, *p2=&ci;  //错误,因为i 的类型时int,而&ci 的类型时const int

(32). 有时会遇到这种情况,希望从表达式的类型推断出要定义的变量的类型,但是不想用该表达式的值初始化变量,为了满足这一要求,C++11引入了第二种类型说明符:decltype。它的作用是选择并返回操作数的数据类型。在此过程中,编译器分析表达式并得到它的类型,却不实际计算表达式的值。

(33). 自定义数据类型:类。在定义类时。在类体右侧的表示结束的花括号后必须写一个分号,这是因为类体后面可以紧跟变量名以示对该类型对象的定义,所以分号必不可少。

(34)编写自己的头文件时,头文件中通常包含那些只能被定义一次的实体,如类,const和constexpr变量。头文件也经常用到其他头文件的功能,如一些系统内置库文件,这样在多个文件相互调用时,有可能在某个文件中某个头文件贝调用了多次,此时有必要在书写头文件时做适当的处理,使其遇到多次包含的情况也能安全和正常的工作,完成该项工作的常用技术是预处理器。它是C++语言从C语言继承而来,预处理器是在编译之前的一段程序,可以部分的改变我们所写的程序,经常用到的一项预处理功能#include,当预处理器看到#include标记时,就会用指定的头文件的内容代替#include。C++程序还会用到的一项预处理功能是头文件保护符头文件保护符依赖于预处理变量,预处理变量有两种状态:已定义和未定义。#define 指令吧一个名字设定为预处理变量,另外两个指令分别检查某个指定的变量是否已经定义:#ifdef 定期额仅当变量已经定义时为真,#ifndef 当且仅当变量未定义时为真,一旦检查为真,则执行后续操作直至遇到 #endif 指令为止。

头文件及时目前还没有被包含在其他任何头文件中,也应该设置保护符。头文件保护符很简单,程序员只要习惯性地加上就可以了,没有必要在乎你的程序到底需不需要。

                                                                                                                                                    





猜你喜欢

转载自blog.csdn.net/GuoSenZQ/article/details/46315757