C++知识总结(1)--变量和基本类型

最近打算看看《C++ primer》,重新复习C++的一些知识点,同时会添加部分在做牛客网编程题目时候记录的知识点。

变量和基本类型

  • endl操纵符的效果是结束当前行,并将与设备关联的缓冲区中的内容刷到设备中缓冲刷新操作可以保证到目前为止程序所产生的所有输出都真正写入输出流中,而不是仅停留在内存中等待写入流。
  • 注释界定符不能嵌套。它是以/*开始,以*/结束的。因此,一个注释不能嵌套在另一个注释之内。

基本类型

算术类型

算术类型所能表示的数据范围如下:

这里写图片描述

基本的字符类型是char,一个char的空间应确保可以存放机器基本字符集中任意字符对应的数字值。也就是说一个char的大小和一个机器字节一样

其他字符类型用于扩展字符集,如wchar_t, char16_t, char32_t。其中wchar_t确保可以存放机器最大拓展字符集中的任意一个字符。而后两种字符类型则是为Unicode字符集服务。

C++标准指定了一个浮点数有效位数的最小值,但是大多数编译器都实现了更高的精度。通常,float1个字(32比特)来表示,double2个字(64比特)来表示,long double3或4个字(96或128比特)来表示。此外,一般float和double分别有7和16个有效位。

除了布尔型和扩展的字符型之外,其他整型可以分为带符号的和无符号的。类型int、short、long和long long都是带符号的,在它们前面加上unsigned则可以得到无符号类型。其中类型unsigned int可以缩写为unsigned

字符型则分成3种:char、signed char 和 unsigned char。并且,char和signed char并不一样,而且字符的表现形式同样是两种,带符号和无符号,因为char类型会表现为这两种形式中的一种,具体是由编译器决定具体形式。

具体类型的选择建议如下:

这里写图片描述

类型转换

类型转换的过程如下:

这里写图片描述

注意,不能混合使用有符号数和无符号数,如果一个表达式中即包含有符号数和无符号数,那么有符号数会转换成无符号数来进行计算,如果这个有符号数还是负数,那么会得到异常结果。如下例子所示:

unsigned u = 10;
int i = -42;
// 输出-84
std::cout << i+i << std::endl;   
// 混合了无符号数和有符号数,如果int占32位,输出4294967264
std::cout << u+i << std::endl;  

上述例子最后一个输出说明了一个负数和一个无符号数相加是有可能得到异常结果的。32位的无符号数范围是0到4294967295

  • static_cast 的用法

    static_cast < type-id > ( expression )

    该运算符把expression转换为type-id类型,但没有运行时类型检查来保证转换的安全性。它主要有如下几种用法:

    ①用于类层次结构中基类(父类)和派生类(子类)之间指针或引用的转换。

    进行上行转换(把派生类的指针或引用转换成基类表示)是安全的

    进行下行转换(把基类指针或引用转换成派生类表示)时,由于没有动态类型检查,所以是不安全的。

    ②用于基本数据类型之间的转换,如把int转换成char,把int转换成enum。这种转换的安全性也要开发人员来保证。

    ③把空指针转换成目标类型的空指针。

    ④把任何类型的表达式转换成void类型。

    注意:static_cast不能转换掉expression的const、volatile、或者__unaligned属性。

  • C++中的static_cast执行非多态的转换,用于代替C中通常的转换操作。因此,被做为显式类型转换使用

    C++中的reinterpret_cast主要是将数据从一种类型的转换为另一种类型。所谓“通常为操作数的位模式提供较低层的重新解释”也就是说将数据以二进制存在形式的重新解释

  • dynamic_cast<>用于C++类继承多态间的转换,分为:
    1.子类向基类的向上转型(Up Cast)
    2.基类向子类的向下转型(Down Cast)
    其中向上转型不需要借助任何特殊的方法,只需用将子类的指针或引用赋给基类的指针或引用即可,dynamic_cast向上转型其总是肯定成功的

    **而向下转换时要特别注意:dynamic_cast操作符,将基类类型的指针或引用安全的转换为派生类的指针或引用。**dynamic_cast将一个基类对象指针(或引用)cast到继承类指针,dynamic_cast会根据基类指针是否真正指向继承类指针来做相应处理。这也是dynamic_cast与其他转换不同的地方,dynamic_cast涉及运行时类别检查,如果绑定到引用或指针的对象不是目标类型的对象,则dynamic_cast失败。如果是指针类型失败,则dynamic_cast的返回结果为0,如果是引用类型的失败,则抛出一个bad_cast错误。
    注意:dynamic_cast在将父类cast到子类时,父类必须要有虚函数。因为dynamic_cast运行时需要检查RTTI信息。只有带虚函数的类运行时才会检查RTTI。

字面值常量

定义:形如42的值被称为字面值常量

我们可以将整型字面值写作十进制数、八进制数和十六进制数。其中以0开头的代表八进制数,以0x或者0X开头的代表十六进制数。默认情况下,十进制字面值是带符号数,而八进制和十六进制可以是带符号也可以是无符号数。它们的类型都是选择可以使用的类型中尺寸最小的,并且可以容纳下当前数值的类型。如十进制可以使用的是int, long和long long,而八进制和十六进制还可以使用无符号类型的unsigned int,unsigned long 和unsigned long long

有两类字符是程序员不能直接使用的:一类是不可打印的字符,如退格或其他控制字符,因为它们没有可视的图符;另一类是在C++语言中有特殊含义的字符,如单引号、双引号、问号、反斜线,这些情况下需要用到转义序列,转义序列均以反斜线开始,C++语言规定的转义序列包括:

这里写图片描述

转义序列被当做一个字符使用。

对于字面值类型可以通过添加一些前缀和后缀来改变其默认类型,如下例子所示:

这里写图片描述

注意 ,指定一个长整型字面值时使用大写字母L来标记,这是由于小写字母l和数字1容易混淆。

变量

在C++11新标准中,变量初始化除了使用传统的如int a=2;这种方式外,还可以使用花括号进行初始化,这种初始化的形式称为列表初始化,形式如int a{2};,需要注意的是,对于内置类型的变量,如果使用列表初始化且初始值存在丢失信息的风险,则编译器将报错,例如:

long double ld = 3.14159;
// 错误: 转换没有执行,因为存在丢失信息的危险
int a{ld}, b = {ld};
// 正确:转换执行,但确实丢失了部分值
int c(ld), d = ld;

当然,如果定义变量时没有指定初值,变量将被默认初始化,会被赋予默认值,而默认值会由变量类型决定,并且定义变量的位置也会有影响。如果是内置类型的变量未被显式初始化,定义在任何函数体之外的变量被初始化为0。但是定义在函数体内部的内置类型变量将不被初始化,如果试图拷贝或以其他形式访问此类未定义的值将引发错误。

变量声明规定了变量的类型和名字,在这一点上定义与之相同,但是定义还申请存储空间,也可能会为变量赋一个初始值。如果想声明一个变量而非定义它,可以在变量名前添加关键字extern,而且不要显式地初始化变量,如下所示:

// 声明i而非定义i
extern int i;
// 声明并定义j
int j;

任何包含显式初始化的声明即成为定义,即使添加了关键字extern。此外,如果在函数体内部,试图初始化一个由extern关键字标记的变量,将引发错误。

变量能且只能被定义一次,但是可以被多次声明。

关键概念:静态类型

C++是一种静态类型语言,其含义是在编译阶段检查类型。其中,检查类型的过程称为类型检查

C++的标识符由字母、数字和下划线组成,其中必须以字母或下划线开头。标识符没有长度限制,但对大小写字母敏感。下面是一些变量命名的规范:

  • 标识符要能体现实际含义;
  • 变量名一般用小写字母,如index;
  • 用户自定义的类名一般以大写字母开头,如Sales_item;
  • 如果标识符由多个单词组成,则单词间应有明显区分,如student_loanstudentLoan

复合类型

复合类型是指基于其他类型定义的类型,如引用和指针。

引用

引用为对象起了另一个名字,其形式如int &refVal = ival;,其中ival是一个初始化的int类型变量,这里需要注意引用必须被初始化

定义一个引用后,对其进行的所有操作都是在与之绑定的对象上进行的,比如为引用赋值,同样会改变与引用绑定的对象。

引用只能绑定在对象上,而不能与字面值或某个表达式的计算结果绑定在一起,如int &ref = 10;这就是一个错误的例子。

引用传递不可以改变原变量的地址,但可以改变原变量的内容

引用的类型必须与其所引用对象的类型一致。但在初始化常量引用时允许用任意表达式作为初始值,只有该表达式的结果能转换成引用的类型即可。如:

int i = 42;
const int &r1 = i;  // 允许将常量引用绑定到一个普通int对象上
const int &r2 = 42; // 正确:r2是一个常量引用
const int &r3 = r1 * 2; // 正确: r3是一个常量引用
int &r4 = r1 * 2;       // 错误:r4只是一个普通的非常量引用
指针

指针是“指向”另外一种类型的复合类型。定义指针类型的方法是在变量名前使用*

空指针不指向任何对象,在试图使用一个指针之前代码可以首先检查它是否为空。下面是几个生成空指针的方法:

int *p1 = nullptr;  // 等价于 int *p1 = 0;
int *p2 = 0;        // 直接将p2初始化为字面常量0
// 需要首先#include cstdlib
int *p3 = NULL;     // 等价于int *p3 = 0

第一种方法中使用字面值nullptr来初始化指针,这是C++11新标准引入的一种方法。第三种方法就是使用一个名为NULL的预处理变量来给指针赋值,这个变量在头文件cstdlib中定义,它的值就是0。

void* 是一种特殊的指针类型,可用于存放任意对象的地址。利用该指针能做的事比较有限:拿它和别的指针比较、作为函数的输入或输出,或者赋给另外一个void*指针。但是不能直接操作器指向的对象,因为并不知道这个对象是什么类型。

引用本身不是一个对象,因此不能定义指向引用的指针,但指针时对象,所以存在对指针的引用

int i=42;
int *p; // p是一个int型指针
int *&r = p;    // r是一个对指针p的引用
r = &i;         // r引用了一个指针,因此给r赋值&i就是令p指向i
*r = 0;         // 解引用r得到i,也就是p指向的对象,将i的值改为0

要理解r的类型到底是什么,最简单的办法就是从右向左阅读r的定义。离变量名最近的符号对变量的类型有最直接的影响,此例中是&r的符号&,所以r是一个引用,而声明符的其余部分用以确定r引用的类型是什么,此例中的符号*说明r引用的是一个指针。

  • 野指针是指向未分配或者已释放的内存地址
  • 使用free释放掉一个指针内容后,必须手动设置指针为NULL,否则会产生野指针。
两个指针之间的运算

只有指向同一个数组的两个指针变量之间才能进行运算,否则运算毫无意义

(1) 指针变量的相减

两指针变量相减所得之差是两个指针所指数组元素之间相差的元素个数,实际上是两个指针值(地址)相减之差再除以该数组元素的长度(字节数).

例如pf1和pf2是指向同一浮点数组的两个指针变量,设pf1的值为2010H,pf2的值为2000H,而浮点数组每个元素占4个字节,所以pf1-pf2的结果为(2000H-2010H)/4=4,表示pf1和 pf2之间相差4个元素。

注意:两个指针变量不能进行加法运算。例如,pf1+pf2是什么意思呢?毫无实际意义。

(2) 两指针变量进行关系运算
指向同一数组的两指针变量进行关系运算可表示它们所指数组元素之间的关系。例如:

  • pf1 = pf2 表示pf1和pf2指向同一数组元素;
  • pf1 > pf2 表示pf1处于高地址位置;
  • pf1 < pf2 表示pf2处于低地址位置。

指针变量还可以与0比较。设p为指针变量,则p==0表明p是空指针,它不指向任何变量;p!=0表示p不是空指针。

空指针是由对指针变量赋予0值而得到的。例如:

#define NULL 0
int *p = NULL;

对指针变量赋0值和不赋值是不同的。指针变量未赋值时,值是随机的,是垃圾值,不能使用的,否则将造成意外错误。而指针变量赋0值后,则可以使用,只是它不指向具体的变量而已。

常量指针和指针常量
  • const在*的左测,指针所指向的内容不可变,即*p不可变,是常量指针
  • const在*的右侧,指针不可变,即p++不被允许,是一个指针常量

const 限定一个对象为只读属性。
先从一级指针说起吧:
(1)const char p 限定变量p为只读。这样如p=2这样的赋值操作就是错误的。
(2)const char *p p为一个指向char类型的指针,const只限定p指向的对象为只读。这样,p=&a或 p++等操作都是合法的,但如*p=4这样的操作就错了,因为企图改写这个已经被限定为只读属性的对象。
(3)char *const p 限定此指针为只读,这样p=&a或 p++等操作都是不合法的。而*p=3这样的操作合法,因为并没有限定其最终对象为只读。
(4)const char *const p 两者皆限定为只读,不能改写。
有了以上的对比,再来看二级指针问题:
(1)const char **p p为一个指向指针的指针,const限定其最终对象为只读,显然这最终对象也是为char类型的变量。故像**p=3这样的赋值是错误的,而像*p=? p++这样的操作合法。
(2)const char * const *p 限定最终对象和 p指向的指针为只读。这样 *p=?的操作也是错的。
(3)const char * const * const p 全部限定为只读,都不可以改写。

指针数组和数组指针
  • 区分int *p[n]; 和int (*p)[n];就要看运算符的优先级了。

    int *p[n]; 中,运算符[]优先级高,先与p结合成为一个数组,再由int*说明这是一个整型指针数组。

    int (*p)[n]; 中()优先级高,首先说明p是一个指针,指向一个整型的一维数组。

    例子:
    int *s[8]; //定义一个指针数组,该数组中每个元素是一个指针,每个指针指向哪里就需要程序中后续再定义了。

    int (*s)[8]; //定义一个数组指针,该指针指向含8个元素的一维数组(数组中每个元素是int型)。

  • 对于一个数组,如int a[10];,对其数组名进行加减,如果有取地址符和没有是有区别的:

    // 有取地址符,相当于每次增加整个数组的长度的倍数
    &a + i = a + i*sizeof(a);
    // 没有使用取地址符,只是数组名,则增加数组中元素的长度的倍数
    a + i = a + i*sizeof(a[0]);

const限定符

const对象一旦创建后其值就不能再改变,所以const对象必须初始化。

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

但如果想在多个文件之间共享const对象,必须在变量的定义之前添加extern关键字。

由于常量引用仅对引用可参与的操作做出了限定,对于引用的对象本身是否是一个常量未作限定。因此对象可能是一个非常量,可以通过其他途径改变它的值,如:

int i = 42;
int &r1 = i;    // 引用r1绑定对象i
const int &r2 = i;  // r2也绑定对象i,但是不允许通过r2修改i的值
r1 = 0;         // r1不是常量,所以可以修改i的数值
r2 = 0;         // 错误,r2是一个常量引用

顶层const可以表示任意的对象是常量,而底层const表示指针指向的对象是常量。当执行对象的拷贝操作时,常量是顶层const还是底层const区别明显。其中,顶层const不受什么影响。而对于底层const则是有所限制的,拷入和拷出的对象必须具有相同的底层const资格,或者两个对象的数据类型必须能够转换。一般来说,非常量可以转换成常量,反之则不行。

常量表达式是指值不会改变并且在编译过程中就能得到计算结果的表达式。

在C++11新标准中,规定了允许将变量声明为constexpr类型以便由编译器来验证变量的值是否是一个常量表达式,并且声明为这种类型的变量必须用常量表达式来初始化。

声明constexpr时用到的类型被称为字面值类型,前面介绍的算术类型、引用和指针都属于字面类型。注意,引用和指针可以定义成constexpr,但其初始值却受到严格限制,一个constexpr指针的初始值必须是nullptr或者0,或者是存储于某个固定地址中的对象。此外,constexpr声明中如果定义了一个指针,则该限定符仅作用于指针本身,而与指针所指对象无关,它会将指针变成一个顶层const,即指针本身是一个常量。而constexpr指针与其他常量指针类似,既可以指向常量也可以指向一个非常量。

类型别名

类型别名是一个名字,它是某种类型的同义词。

有两种方法可用于定义类型别名。传统的方法是使用关键字typedef

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

C++11新标准规定了一个新的方法,使用别名声明来定义类型的别名:

using SI = Sales_item;  // SI是Sales_item的同义词

这种方法用关键字using作为别名声明的开始,后面紧跟别名和等号,作用是将等号左侧的名字规定成等号右侧类型的别名。

auto类型说明符

C++11新标准引入了auto类型说明符,用它就能让编译器替我们去分析表达式所属的类型。它是让编译器通过初始值来推算变量的类型,所以,auto定义的变量必须有初始值

使用auto也能在一条语句中声明多个变量。因为一条声明语句只能有一个基本数据类型,所以该语句中所有变量的初始基本数据类型必须一样:

auto i = 0, *p = &i;    // 正确:i是整数,p是整型指针
auto sz=0, pi = 3.14;   // 错误:sz和pi的类型不一致

auto一般会忽略掉顶层const,同时底层const会保留下来。如果希望推断出的auto类型是一个顶层const,需要明确指出:const auto f = ci; // ci的推演类型是int,f是const int

decltype类型指示符

有时候希望从表达式的类型推断出要定义的变量的类型,但不想用该表达式的值初始化变量。为了满足这要求,C++11引入了第二种类型说明符decltype,其作用是选择并返回操作数的数据类型。如:

decltype(f()) sum = x; // sum的类型就是函数f的返回类型

decltype处理顶层const和引用的方式与auto有些不同。如果其使用的表达式是一个变量,则会返回该变量的类型,包括顶层const和引用在内:

这里写图片描述

如果decltype使用的表达式不是一个变量,则返回表达式结果对应的类型。如果表达式的内容是解引用操作,如decltype(*p) c;,则返回的是引用类型。

注意:如果decltype的表达式加上了括号,即如decltype((i));,其得到的结果是引用类型。

猜你喜欢

转载自blog.csdn.net/lc013/article/details/55047494