第二章_变量和基本类型_PrimerC++

本文详细介绍了C++中的基本内置类型,包括算术类型(如整型、浮点型及其尺寸)、类型转换(涉及浮点与整型、有符号与无符号类型间的转换)和字面值常量。接着,讨论了变量的定义、声明、作用域和初始化,并讲解了复合类型如引用和指针的特性。此外,还深入探讨了const限定符的使用,包括常量引用、指针与const的关系以及顶层和底层const的概念。最后,文章提到了处理类型的方法,如类型别名、auto类型说明符和decltype类型指示符的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


1. 基本内置类型

这一部分内容要注意的东西其实不是特别多

1)算术类型

算术类型可以分为两类:整型(字符和布尔类型也算在内)和浮点型

算术类型的尺寸

值得注意一下

  • 字符型
    字符:char(8位)
    宽字符:wchar_t (16位)
    Unicode字符:char16_t(16位)、char32_t(32位)

  • 浮点型
    单精度float:6位有效数字
    双精度double:10位有效数字
    拓展双精度long double:10位有效数字
    在这里插入图片描述

    这里的有效数字是指按照IEEE标准的浮点数中按十进制可以保证的有效数字

    详细可以参考

    • https://blog.csdn.net/sinat_38972110/article/details/82117072?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task

    注意有效数字是指浮点数中尾数可以保证的精度

带符号类型与无符号类型

  • 字符型被分为了三种:char/unsigned char/signed char
  • 一般编译器决定char为带符号还是不带符号
  • int也类似,事实上这里牵扯到一个信息安全相关的知识,不带符号的更容易导致错误,但带符号的更符合现实逻辑。

建议:如何选择类型

  • 知晓不可能为负,选用无符号类型
  • 使用int执行整型运算,不够用则用longlong
  • 算术表达式中尽可能不要用到charbool
  • 执行浮点数运算选用double:计算时间相差不多(甚至更快)
2)类型转换

类型所能表示的值的范围决定了转换的过程:

  • 浮点数与整型之间的相互转换

    浮点数转为整型可能存在近似处理

    整型转为浮点数可能存在精度损失

  • 无符号数与有符号数之间的相互转换

    实际上二进制表示不发生改变,这样的转换有时可能导致不好的后果

    无符号数与有符号数混用将统一为无符号数

    无符号数的不谨慎使用可能产生不良后果,如下:

    for (unsigned int i = k; i--; i >= 0){	//for循环将无法终止
        do_something();
    }
    
3)字面值常量

整型和浮点型字面值

  • 整型:088八进制,88十进制,0x88十六进制
  • 浮点型:3.1415e0科学计数法,0..001

字符和字符串常量

转义字符

  • 转义字符中,如果\后跟着数字则表示八进制,如\123,表示八进制数123,最多为三位

指定字面值类型

在这里插入图片描述
以上为primer C++书中出现的指定字符和字符串字面值的前缀和后缀符号


2. 变量

1)变量定义

语法

  • 类型说明符 标识符;

初始值

  • 定义了后对象可以马上使用
    在这里插入图片描述

  • 初始化不等于赋值

    初始化的含义是创建变量时赋予其一个初始值,而赋值的含义是把对象当前的值擦除,以一个新的值来代替

  • 列表初始化

    利用花括号进行初始化,不仅适用于基本内置类型也适用于自定义对象。

    int units_sold = {0};

    • 利用列表初始化内置变量时,若初始值丢失,则可能存在风险。(即花括号中包含变量的情况)

    在这里插入图片描述

默认初始化

  • 在这里插入图片描述
  • 大多数类都支持默认初始化
2)变量声明和定义的关系
  • 声明:使得变量为编译器所知
  • 定义:负责创建与名字关联的实体

注意:

  • 如果只想声明一个变量而非定义,可以用extern关键字,而且不要显式初始化

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

    如果包含了初始化,则变成了定义。

    extern int i = 10; //定义

  • 函数体内部如果试图初始化一个extern变量,将引发错误。

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

3)标识符

字母数字下划线组成,必须字母或下划线开头

命名风格可以参考谷歌开源项目文档: https://zh-google-styleguide.readthedocs.io/en/latest/google-cpp-styleguide/naming/

4)作用域

时间/空间两个维度考虑

需要明白这些概念:块作用域、全局作用域、嵌套作用域


3. 复合类型

1)引用
  • 左值引用与右值引用

    这里指的是左值引用

定义

  • **引用相当于为对象起了一个名字,引用类型引用另外一种类型。(即别名)**例子如下:

    int val = 124;
    int &refVal = val;	// 指向val
    itn &refVal2;	//报错 error: 'refVal2' declared as reference but not initialized
    

初始化

  • 定义引用时,程序把引用和初始值绑定,引用无法绑定另一个对象,故必须初始化

  • 用引用初始化引用是可以接受的

    int &refVal3 = refVal;
    
  • 错误的情况

    a.利用常量初始

    int &refVal = 10;
    

    报错:error: cannot bind non-const lvalue reference of type 'int&' to an rvalue of type 'int'

    b.利用不同类型的对象初始化:

    double a;
    int &b = a;
    

    报错:7:14: error: cannot bind non-const lvalue reference of type 'int&' to an rvalue of type 'int'

2)指针

与引用类似,指针也实现了对其他对象的间接访问,然而指针与引用相比又有很多不同:

  • 指针本身是个对象
  • 指针定义时可以不必赋初值

指针的定义

  • 取地址符:&

  • 不能用引用定义指针:引用不是对象,没有实际地址

  • 类型不匹配不可以定义

    double a = 3;
    double *b = &a;
    int *c = b;	//出错
    

    报错:error: cannot convert 'double*' to 'int*' in initialization

  • 不可以将指针通过int变量赋值,即使这个变量值为0

    int a = 0;
    int *ptr = a;
    

指针值

四种情况之一:

  • 指向一个对象
  • 指向紧邻对象的下一个位置
  • 空指针
  • 无效指针:这种情况可能在运行时出现难以察觉的错误

最好初始化所有指针:nullptr

利用指针访问对象

  • 解引用符
3)理解复合类型的声明

(这一部分可以多看看)

定义多个变量

  • 错误的观点:类型修饰符作用域某行定义的所有标识符

    实际上,只作用于相距最近的标识符,所以一般这样定义

    int *ptr, a;	//ptr为指针,a为整性
    

指向指针的指针

  • 指针也可以指向指针,含义是指向某块地址

    int val=1024;
    int *ptr = &val;
    int **ptr = &ptr;
    

指向指针的引用

  • 指向指针的引用为指针的别名

    int i = 42;
    int *p;
    int *&r = p;	// r为p的引用
    *r = &1;
    *r = 0;
    

4. const限定符

初始化和const

const对象的操作被限制在不会修改其值的操作上

初始化

  • const初始化

    const对象必须被初始化:error: uninitialized const 'c' [-fpermissive]

  • 利用const对象初始化其他对象:

    这里的j为一个const对象,无法被更改,但可以用于初始化(第3行)

    int i = 42;
    const int j = i;
    int k = j;
    

默认状态下,const只在文件内有效

  • 当以编译时初始化的方式定义一个const对象,编译器将编译过程中所有用到该变量的地方都替换为对应的值。

  • 如果某个const对象被多个文件使用,需要在每一个文件中都有这个变量的定义

  • 有时候某个const对象确实需要被多个文件使用,可以通过extern实现,无论是声明还是定义都加入extern关键字

    // file1.cpp
    extern const int bufSize = fcn();
    // file1.h
    extern const int bufSize;
    
1)const的引用

类似于普通引用的定义,我们可以将引用绑定到常量上,不同的是无法通过引用对常量进行修改。

  • 尝试修改会在编译时出错

    const int a = 10;
    const int &b = a;
    b = 11;
    

    error: assignment of read-only reference 'b'

  • 非常量引用不可指向常量

    int &c = a;
    

初始化和对const的引用

int a = 10;
const int &r1 = a;          // 正确
const int &r2 = 10;         // 正确
const int &r3 = r1 * 2;     // 正确
int &r4 = r1 * 2;           // 错误 error: cannot bind non-const lvalue reference of type 'int&' to an rvalue of type 'int'

对错误的理解

  • 先看一个问题

    double a = 10;
    const int &b = a; 
    cout << b << endl;
    

    程序将输出10,实际上编译器对该代码进行了修改,创建了一个临时变量用于保存该对象

    const int tmp = a;
    const int &b = tmp;
    
  • 如果这里的b不是常量,则允许了对b引用对象的修改,而b引用的对象将是一个常量,这是不被允许的

    因为既然声明了引用,便肯定想通过引用访问原变量

对const的引用可能引用一个非const对象

即const引用引用变量,这意味着无法通过引用修改变量

int a = 10;
const int &b = a; 
int c = a;
b = 0;	//  error: assignment of read-only reference 'b'
c = 0;
2)指针和const

指向常量的指针不能修改它指向的对象得值

要想存放常量对象得地址,只可以使用常量指针

const int a = 10;
const int *b = &a;
int *c = &a; 	// error: invalid conversion from 'const int*' to 'int*'

常量指针也可以指向变量对象,这意味着无法通过指针修改该变量

int a = 10;
const int *b = &a;
*b = 11;	// error: assignment of read-only location '* b'

const 指针

*放在const关键字之前意味着指针是一个常量,意味着指针无法指向其他变量。

int a = 10;
int b = 11;
int *const c = &a;
*c = 11;
c = &b;	//  error: assignment of read-only location '* b'
int *const d;	// error: uninitialized const 'd'

理解这些的最好方法是从右往左阅读,离得最近的关键字将定义变量的对象类型:

  • int *const:const指针
  • const int*:常量指针
3)顶层const

顶层const表示指针本身是个常量,底层const表示指向的对象视为常量(对于引用也同样适用)

int a = 10;
const int b = 11;	// 顶层const,不可改变b的值
int *const p1 = &a;  // 顶层const,p1不可指向其他变量
const int* p2 = &b;	 // 底层const,p2指向的对象视为常量
const int* const p3 = &a;

执行对象的拷贝操作时,常量是顶层const还是底层const区别明显:

  • 顶层const不受影响

    顶层const资格不同没有关系,可以正常执行。

    a = b;	// 正确
    p2 = p3; // 正确:p3顶层不受影响
    
  • 底层const的影响不可忽视

    拷入拷出的对象必须具有相同的底层const资格

    int *p = p3;	// 错误,p3为指向常量的指针
    int &r = b;		// 错误,b为常量
    const int& r2 = a;	// 正确,视r2为常量引用(不可改变值)
    
4)constexpr和常量表达式

常量表达式是指在编译过程中就可以得到计算结果的表达式,显然字面值属于常量表达式

一个对象是不是常量表达式由数据类型和初始值决定的:

const int a = 20;
const int b = a + 1;
int c = 10;	// 不是常量表达式
const int d = get_size();	// 不是常量表达式

constexpr常量

C++11标准规定可以利用constexpr关键字来声明变量使得编译器可以验证这个变量是否为常量表达式,声明为constexpr类型的变量一定是一个常量且必须常量表达式初始化。

例子如下:

constexpr int a = 10;
constexpr int b = a + 1;
constexpr int c = get_size();	//只有在constexpr关键字下才是常量

字面值常量

constexpr声明时用到的类型,称为字面值类型

算术类型、引用和指针都属于字面值类型,自定义类和库不属于字面值类型

指针和引用都可以声明为constexpr,但是constexpr指针只可以被初始化为nullptr0,或是储存于某个固定地址之中的对象。

  • 如静态变量或全局变量可以用constexpr指针
  • 局部变量不可以用constexpr指针

指针和constexpr

在constexpr声明中如果定义了一个指针,限定符constexpr仅仅对指针有效,与指针所指对象无关。

const int *p = nullptr;	    // p是一个指向整型常量的指针
constexpr int *q = nullptr; // q是一个指向整数的常量指针
  • 关键的点在于constexpr将它定义的对象置为了顶层const

5. 处理类型

1)类型别名

类型别名是某种类型的同义词,使用类型别名的好处有:让复杂类型名字变简单、易于理解和使用

定义方法:

  • 1

    typedef double wage;
    typedef wage base, *p;
    
  • 2

    using SI = sales_item;	//利用别名声明来定义
    
2)指针、常量和类型别名

注意一点,如果某个类型别名指代的是复合类型或常量,用到声明语句里会产生意想不到的后果,例如:

typedef char *pstring;
const pstring ptr = 0;	// 这里ptr是一个指针,指向char的常量指针
// 错误理解:const char *ptr = 0;
// 这里的pstring的基本数据类型是指针
// char*重写了这个声明后,数据类型变成了char,*成了声明符的一部分
// 这意味着指向const char的指针
const pstring *ps;		// 这里ps是指向char*的常量指针
3)auto类型说明符

编程时常常需要将表达式的值赋给变量,这要求在声明变量的时候就清楚的直到表达式的类型,但这并不容易,于是可以利用auto类型说明符来处理这点,它可以让编译器替我们完成类型的说明。

复合类型、常量和auto

  • 编译器得到的类型和初始值的类型并不完全一样,编译器会适时改变结果类型使其更符合初始化规则。

  • 引用:auto类型将使得以引用初始化的对象类型为引用对象的类型

  • const:auto一般会忽略顶层const,而底层const会保留下来。

    int i = 0, &r = i;
    auto a = r;	// a为整型
    const int ci = i, &cr = ci;
    auto b = ci; // b为整型
    auto c = cr; // c为整型 
    auto d = &i; // d为整型指针
    auto e = &ci;// e为指向常量的指针(对常量对象取地址视为const)
    

    如果希望auto类声明的变量为顶层const,则需要明确提出

    const auto f = ci;	// f为常量
    

    还可以将引用类型设为auto,此时原来的初始化规则仍适用:

    auto &a = 42;		// 非法
    const auto &j = 42;	 // 合法
    
4)decltypel类型指示符

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

  • 编译器通过分析表达式来得到它的类型,却不计算实际的值

    decltype(f()) sum = x;	// f()的返回值类型为sum的类型
    

    这里编译器并非调用f(),而是使用它的返回值类型作为sum的类型。

  • decltype和auto的使用有所不同:返回该类型的变量(保留顶层和底层const)

    const int ci = 0, &cj = ci;
    decltype(ci)x = 0;	// const int
    decltype(cj)y = x;	// const int &
    decltype(cj)z;		// 错误:z为引用,必须初始化
    

decltype和引用

  • 如果decltype使用的表达式不是一个变量,则decltype返回表达式结果对应的类型。

    int i = 42, *p = &i, &r = i;
    decltype(i+0) b;	// 正确,b为整型
    decltype(*p) c;		// 错误,*p为引用,c也为引用,需要进行初始化。
    
  • 如果表达式的内容是解引用,则decltype将得到引用类型。如上面的decltype(*p) c;,c的类型为引用类型。

  • decltype的结果与表达式的形式密切相关

    如:加上括号和不加括号的区别

    变量名不加上括号,则得到的类型为该变量的类型;如果加上括号,将其看作表达式,最终将得到引用类型(永远如此)

    decltype((i)) d;	// 错误:d为int&类型,需要初始化
    decltype(i) e;		// 正确:e为int类型
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值