C++ Primer笔记 第二篇:C++基础——基本类型和变量

1、基本内置类型

  • 算术类型
     (1) 整数类型:short、int、long、char、bool
     (2) 浮点数类型:float、double
  • 空类型:void
1.1 内置类型的机器实现

对算术类型,C++标准只规定了各类型所占的最小尺寸,因此,在不同的机器上,同样的算术类型可能具有不同的尺寸。这一点和Java不同(Java规定了每种内置类型的具体尺寸,是平台无关的,这也是Java可移植性好的原因之一)。

1.2 选择类型的经验准则

和C语言一样,C++的设计准则之一也是尽可能地接近硬件,C++的算术类型必须满足各种硬件特质。一些选择类型的经验准则:

  • 当明确知道数值不为负时,选择无符号类型
  • 使用 int 执行整数运算:实际应用中,short 往往太小;long 往往和 int 尺寸一样大。int 不够用时,使用 long long
  • 算术表达式中不要使用 char、bool:不同机器是编译器对 char 的处理可能不一样,有些是有符号的,有些是无符号的。如果必须使用 char,请明确指明其类型是 signed char 或者 unsigned char
  • 浮点数计算使用 double:float通常精度不够,而且单双精度浮点数的计算代价相差无几。long double 一般用不到。
1.3 基本算术类型的转换
  • 非布尔类型的算术值赋给布尔类型时,非0为true,0为false;布尔值赋给非布尔类型时,true转为1,false转为0
  • 浮点数赋给整型时,只保留非小数部分;整型值转为浮点数时,小数部分记为0,如果该整数所占的空间超过浮点类型的容量,精度可能有损失。
  • 赋给无符号类型一个超出其范围的值时,结果是初始值对无符号类型表示数值总数取模后的余数。例如,8bit 大小的 unsigned char 可以表示0~255范围的值,如果赋给一个超出范围的值,结果是该值对256取模后的余数。因此,-1赋给8bit 大小的 unsigned char 的结果是255。注意:切勿混用有符号类型和无符号类型。 因为,如果表达式中既有带符号类型又有无符号类型,带符号类型会自动转换成无符号数,当带符号类型为负值时会出现异常结果。
  • 赋给有符号类型一个超出其范围的值时,结果是 未定义的(undefined) 。程序可能崩溃,也有可能产生垃圾数据。
1.4 字面值常量
    using namespace std;
    cout << "Hello World!" << endl;
    cout << 0.5 + 99 << endl;

上面代码片段中,“Hello World!” 和 0.5、99 就是所谓的字面值常量,指的是它们的值就是字面上呈现的样子,无法改变。

尽管我们没有为字面值常量明确指明其数据类型,但每个字面值常量都有对应的数据类型,否则计算机将不知道如何来存储这些字面值常量。

字面值常量的形式和值决定了它的数据类型:

  • 十进制整型字面值的类型是在满足能容纳当前值的前提下,int、long、long long 中尺寸最小的那个
  • 浮点型字面值默认是 double 类型。可以在字面值后添加后缀来表示其它浮点类型
  • 单引号括起来的一个字符是 char 型字面值
  • 双引号括起来的0个或多个字符是 字符串型字面值 。字符串字面值的类型本质上是由常量字符构成的字符数组。编译器在每个字符串的结尾处添加一个空字符(’\0’) [注意它和字符0(‘0’)的区别]。因此,字符串字面值的实际长度要比它的内容多1。
  • true 和 false 是布尔类型字面值
  • nullptr是指针字面值

2、变量

2.1 基本概念

变量的本质是一个具名的、可供程序操作的 存储空间。为了标识和操作这块存储空间,给它起个名字,这个名字叫做 变量名

int a;    // 定义
extern int b;   // 声明

因此,上述代码中的 a 是变量名,它标识了一块存储空间,这块存储空间存储的数据可以改变,所以本质上可变的是存储空间中的数据。但一般不说这么绕,直接说变量a。

  • 变量定义 是一个 申请存储空间,并 将变量名和申请的存储空间关联起来 的过程。C++中,使用的变量必须先定义,否则没有空间存储数据。
  • 变量声明 不申请存储空间,仅仅是告诉后面的程序,有这么一个某种类型的变量名。
    • 如果所有程序都在同一个源文件中,我们只需要变量定义就够了,此时变量定义同时也起到了声明的作用;变量声明主要的作用在于支持C++的分离式编译,实现代码共享(一处定义,多处使用)。比如在一个头文件中定义了变量b,我们在其它文件中使用这个变量b时,就需要先声明,告诉程序我们要用的变量b是已经定义好的。此时,在变量名之前添加关键字 extern 即可。
    • 变量只能被定义一次,但可以被多次声明。
  • 初始值是变量在定义时获得的一个特定值。此时我们说变量被 初始化 了。
    • 初始化和赋值是两个不同的概念。尽管很多时候我们使用 = 来初始化一个变量。强调:初始化不是赋值。初始化是创建变量时赋予其一个初始值;赋值是把变量的当前值擦除,然后用一个新值来替代。
    • C++中初始化有多种不同形式,不仅仅是=这一种。比如下面4条语句都能完成对变量的初始化:
      int t1 = 0;        // =初始化
      int t2 = {0};     // 列表初始化
      int t3{0};         // 列表初始化
      int t4(0);         // 列表初始化
      
    • 默认初始化:如果定义变量时没有指定初始值,则变量会被 默认初始化,也就是被赋予一个默认值。内置类型在函数外(全局变量)被初始化为0;在函数内(局部变量)不被初始化,其值是未定义的。

3、复合类型

复合类型是在其它类型基础上定义的类型。

声明变量的语法: 基本数据类型 声明符列表
声明符为变量起了个名字(变量名),并指定该变量是和基本数据类型相关的某种类型。
因此,在基本内置类型的声明语句中,声明符就是变量名,此时变量的类型就是声明中的基本数据类型。而在复合类型的声明语句中,声明符更加复杂。下面介绍两种常用的复合类型,引用指针

3.1 引用(reference)类型

引用类型:引用另外一种类型。就像写论文时引用文献中的论据,一个引用类型的变量r简称一个引用)引用另一个类型变量s中的内容,代表变量r的内容来源于另一个变量s
本质上,引用就是为变量s起了一个别名。打个比方,就像一个人既有大名又有小名,但不论叫大名还是小名,最终指向的都是这个人。

强调: 引用只是已有对象的别名。引用本身不创建对象。

引用必须初始化:
变量初始化时,初始值会被拷贝到新创建的对象中。而在声明引用时,由于引用并不创建对象,所以程序是把引用和它的初始值 (即它引用的对象) 绑定到一起,而不是拷贝初始值给引用。一旦初始化完成,引用将和它的初始值对象一直绑定在一起,无法重新绑定到另一个对象,因此引用必须初始化。
另: 因为引用本身不是一个对象,所以不能定义引用的引用。

int ival = 1024;
int &refVal = ival;   //声明引用,并初始化。refVal引用ival
int &refVal2;         //报错 ‘refVal2’ declared as reference but not initialized:引用必须初始化

引用的定义:

  • 为了和一般变量的定义区别开,引用的声明符在变量名之前加&前缀
  • 引用只能绑定在对象上,不能绑定在字面值和表达式的计算结果上
int &refVal1 = 10;     //错误:引用类型必须绑定在对象上
double val = 3.14;
int &refVal2 = val;    //错误:引用类型和绑定的对象的类型不匹配
3.2 指针(pointer)类型

与引用类似,指针也能实现对其他对象的间接访问。指针和引用的不同点在于:1.指针本身就是一个对象,因此可以对指针赋值和拷贝,从而指针可以先后指向不同的对象(非常量指针)。2.指针无须再定义时赋初值,在块作用域内如果指针未被初始化,也将拥有一个不确定的值。

指针的定义:

double *dp, dp2;

其中,声明符*dp是一个整体:dp是变量名,*说明这是一个指针,*只对dp生效;dp2是一个普通的double型变量。

在定义变量时,*表示定义一个指针变量;而在访问指针指向的对象时,*表示解引用符

空指针:
空指针(null pointer)不指向任何对象。生成空指针的方法:

// 下面3种方法本质上是等价的,都是给指针赋予一个初始值0
int *p1 = nullptr;      //使用字面值nullptr进行初始化
int *p2 = 0;            //将p2初始化为字面常量0
int *p3 = NULL;         //需要#include cstdlib

// 不能直接使用int变量给指针赋值,即使这个int变量的值为0
int zero = 0;
int *p4 = zero;         //报错:invalid conversion from ‘int’ to ‘int*’

void* 指针:
void*是一种特殊的指针类型,可存放任意对象的地址
void*指针主要用于指针比较、函数的输入输出等。不能直接操作void*指针所指的对象,因为不知道这个对象的具体类型,从而也就无法确定能在这个对象上进行哪些操作。

3.3 理解复合类型的声明

变量定义:
基本数据类型 声明符列表

在同一条定义语句中,基本数据类型只有一个,但声明符的形式却可以不同,从而一条定义语句可以定义出不同类型的变量

// 一条定义语句中,定义了int型变量a,int型指针b,int型引用c
int a = 1024, *b = &a, &c = a;

指向指针的指针:

int ival = 1024;
int *pi = &ival;    //pi指向一个int型数据
int **ppi = &pi;    //ppi指向一个int型的指针

指向指针的引用:
指针本身是一个对象,因此可以定义对指针的引用。

#include <iostream>

using namespace std;

int main()
{
    int i = 42;
    int *p;
    //int &r = p;    //r是一个指向int型变量的引用,无法和指针绑定
    int *&r = p;     //r是一个对指针的引用

    cout << "r=" << r << ", p=" << p << endl;
    cout << "*r=" << *r << ", *p=" << *p << ", i=" << i << endl;
    cout << "---------------------------------\n"<< endl;

    r = &i;
    cout << "r=" << r << ", p=" << p << endl;
    cout << "*r=" << *r << ", *p=" << *p << ", i=" << i << endl;
    cout << "---------------------------------\n"<< endl;

    *r = 0;
    cout << "r=" << r << ", p=" << p << endl;
    cout << "*r=" << *r << ", *p=" << *p << ", i=" << i << endl;

    return 0;
}

结果是:

r=0x400c61, p=0x400c61
*r=1961723208, *p=1961723208, i=42
---------------------------------

r=0x7fff8bbdec74, p=0x7fff8bbdec74
*r=42, *p=42, i=42
---------------------------------

r=0x7fff8bbdec74, p=0x7fff8bbdec74
*r=0, *p=0, i=0

示例代码中的 r 是对指针 p 的引用,我们应该从右往左阅读 r 的定义:离变量名最近的符号对变量的类型有最直接的影响,因此&告诉我们 r 是一个引用。声明符的其余部分用以确定 r 引用的类型是什么,例子中的*说明 r 引用的是一个指针。最后,基本数据类型部分指出 r 引用的是一个int型指针。

指向引用的指针:

引用本身不是一个对象,因此不能定义指向引用的指针

4、const限定符

const准确的含义是只读,即使用相同的值给一个const变量重新赋值也不行。const对象一旦创建后其值就无法改变,所以 const对象必须初始化

const int i = 42;
const int j = i;
j = i;      //即使用相同的值重新赋值也会报错:assignment of read-only variable ‘j’

const int k;    //const变量没有初始化,报错:uninitialized const ‘k’

编译器在编译过程中,会把使用到const变量的地方都替换成const变量相应的值。const变量默认只在文件内生效,如果需要在多个文件之间共享const变量,必须在const变量定义之前添加extern关键字,其它文件内使用该变量时,在声明之前也添加extern关键字,用于表明该变量的定义在别的文件中。

4.1 const和引用

把引用绑定在 const 对象上,称之为对常量的引用(reference to const)。和普通引用不同,对常量的引用不能用于修改其绑定的对象。

    const int ci = 1024;
    const int &r1 = ci;
    r1 = 42;   //error: assignment of read-only reference ‘r1’
    int &r2 = ci; //error: binding ‘const int’ to reference of type ‘int&’ discards qualifiers

const 对象不能被赋值,所以也就不能通过引用去改变ci。用ci初始化r2报错的原因是,假如该初始化合法,那么可以通过r2来改变它引用对象的值,这显然是错误的。

对常量的引用(reference to const)在工作中也常简称为**“常量引用”**。严格地说,由于引用本身不是一个对象,我们无法让引用本身恒定不变,所以并不存在常量引用;C++中无法改变引用所绑定的对象,从这一层意义上看的话所有的引用又都算常量。无论引用的对象是不是常量,都不会影响引用和对象的绑定关系本身。

通常,引用的类型必须和其所引用对象的类型一致。但有两个例外:
其一:常量引用初始化时,允许任何可转化成引用的类型的表达式作为初始值。

    int i = 42;
    // 允许为常量引用绑定非常量对象、字面值、表达式 
    const int &r1 = i;    //绑定非常量对象
    const int &r2 = 42;   //绑定字面值
    const int &r3 = r1 * 2;  //绑定表达式
    
    int &r4 = r1 * 2;  //错误:r4是普通的非常量引用
    const int &r5 = 3.14;   //正确,double类型可转化为 const int 类型
4.2 const和指针

**指向常量的指针(pointer to const):**不能用于改变所指对象的值。要想保存常量对象的地址,只能使用指向常量的指针。但指向常量的对象可以指向一个非常量的对象。

    const double pi = 3.14;   //pi是常量,值不能改变
    double *ptr = &pi;   //ptr是个普通指针,报错:invalid conversion from ‘const double*’ to ‘double*’
    const double *cptr = &pi;  //正确
    *cptr = 3.1415926;   //cptr指向常量,报错:assignment of read-only location ‘* cptr’
    double dval = 10.24;
    cptr = &dval;  //正确,指向常量的指针可以指向一个非常量对象

const 指针:和引用不同,指针本身是对象,所以指针本身可以是常量,即常量指针(const pointer)。和一般 const 对象一样,常量指针也必须初始化,而且一旦初始化,值就不能再改变。

*放在const关键字之前,用以说明指针是一个常量,指明不变的是指针本身的值而非指向的那个值。

    int errNumb = 0;
    int *const curErr = &errNumb;  //curErr将一直指向errNumb
    const double pi = 3.14;
    const double *const pip = &pi; //pip是一个指向常量对象的常量指针

如之前变量声明所述,声明的含义应该从右往左读:*const curErr是一个整体,可以理解为一个声明符。离变量名curErr最近的是符号是const,代表curErr本身是一个常量对象;其次是符号*,说明curErr是一个指针,合起来指明curErr是一个常量指针。最后,基本数据类型部分确定了curErr指向一个 int 对象。
按这样的套路来分析pip:声明符整体说明pip是一个常量指针;基本数据类型部分确定了pip指向一个 double 类型的常量。最终,我们确定,pip是一个指向常量的常量指针。

4.3 顶层/底层 const
  • **顶层const:**对象本身是常量,如const pointer
  • **底层const:**指针、引用所指的对象是一个常量

指针本身是一个对象,指针既可以是顶层 const 也可以是底层 const;引用本身不是对象,因此引用只能是底层 const。

为什么要区分顶层/底层 const ?
在执行对象的拷贝操作时,顶层 const 不受什么影响(特指拷贝的对象是顶层 const,拷入的对象当然不能是顶层 const)。
另一方面,当拷贝对象时,拷入和拷出的对象必须具有相同的底层 const
资格,或两个对象的数据类型可以转换
。通常,非 const 可以转换成 const,反之则不可。

    int i = 0;
    const int *const p = &i;  // 正确,非const可以转换成const
    int *p1 = p;     // 报错:p包含底层const的定义,而p1没有
    const int *p2 = p;   //正确,p和p2都是底层const,p的顶层const 部分不影响
    const int ci = 42;    //顶层const
    int &r = ci;          //非const不能绑定到const上
    const int &r2 = i;    //正确,非const可以转换成const
4.4 constexpr 和常量表达式

**常量表达式(const expression):**值不会改变并且在编译过程中就能得到计算结果的表达式。

字面值是常量表达式,用常量表达式初始化的 const 对象也是常量表达式。

5、处理类型

5.1 类型别名

两种方式:
  (1). C风格方式
    typedef 旧类型名 新类型声明符([修饰符]类型名);
    如:

typedef double wages;   // wages 是 double 的同义词
typedef wages base, *p; // base 是 double 的同义词,p是 double * 的同义词

  (2). 新标准方式
    using 新类型 = 旧类型;

注意:
复合类型的别名的理解方式,如下代码:

typedef char *pstring;
const pstring cstr = 0;
const pstring *ps;

typedefpstring指定为指向 char 的指针类型的别名。因此,在后面使用过程中,pstring就应该始终被理解为指针类型。把类型别名机械地进行文本替换,这种理解方式是错误的
类比const int a;,声明了int型的常量a,意味着a是int类型,它本身是不变的;同理,const pstring cstr;意味着cstr是pstring类型(指向char的指针),且本身是不变的,也就是说,这条语句指明cstr是一个指向char的常量指针。
类似地,ps是一个指向常量的指针,ps指向一个char型常量指针。

    typedef int *pstring;  //pstring是指针类型的别名
    const pstring *ps;    //ps是指向常量指针的指针

    int i = 42;
    int *p = &i;
    ps = &p;             //非const可以转换成const
    cout << "p=" << p << endl;           //p=0x7fff6fd55944
    cout << "*ps=" << *ps << endl;     //*ps=0x7fff6fd55944

    int j = 55;
    int *const q = &j;
    ps = &q;          //ps可以指向另一个对象,说明ps本身不是const的
    cout << "q=" << q << endl;         //q=0x7fff6fd55934
    cout << "*ps=" << *ps << endl;   //*ps=0x7fff6fd55934

    *ps = &i;   //报错:assignment of read-only location ‘* ps’
5.2 auto 类型说明符

C++新标准引入了auto类型说明符,可以让编译器通过初始值来推算变量的类型。因此,auto定义的变量必须有初始值。
auto会忽略顶层const (引用例外),保留底层const

auto i = 0, *p = &i;     //推算出i是整数,p是整型指针
auto sz = 0, pi = 3.14;   //错误,sz和pi类型不一致
const int ci = i, &cr = ci;
auto b = ci;     //b是整数,ci的顶层const被忽略
auto c = cr;     //c是整数,cr是ci的别名,ci的顶层const被忽略
auto d = &i;    //d是整型指针
auto e = &ci;   //e是指向整型常量的指针,对e而言,ci是底层const
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值