前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >《C++Primer》第二章 变量和基本类型

《C++Primer》第二章 变量和基本类型

作者头像
TOMOCAT
发布2020-10-28 11:35:35
5290
发布2020-10-28 11:35:35
举报
文章被收录于专栏:懂点编程的数据分析师

写这篇文章的目的

身为C++的零基础初学者,短期内把《C++Primer》啃下来是一个比较笨但是有效的方法,一方面可以掌握比较规范的C++语法(避免被项目中乱七八糟的风格带跑偏),另一方面又可以全面地了解C++语法以及C++11新标准(后续要做的事情就剩下查漏补缺,不断完善自己的知识体系)。

个人感觉从零学习一门新知识比较好的方法是快速了解知识的全貌,然后构建自己的知识地图,后续不断地补充相应的细节。

由于《C++Primer》和大多数的教科书一样废话连篇,因此想要精炼一下每篇文章的内容再打印成pdf,方便温故知新。

全文链接

基本内置类型

1. 内置类型的及其实现
  • 字节byte:可寻址的最小内存块,大多数机器的字节由8比特构成
  • 通常float32位来表示,double64位来表示;一般floatdouble分别有716个有效位
2. 如何选择类型
  • 明知数值不可能为负时则选用无符号类型
  • 一般用int执行整数运算(因为short太短而long一般与int有相同的尺寸),如果你的数值超过了int的表示范围则选用long long
  • 执行浮点数运算时选用double,一方面是因为float精度不够,另一方面是因为双精度浮点数和单精度浮点数的计算代价相差无几
3. 类型转换
  • 当我们赋给无符号类型一个超过它表示范围的值时,结果是初始值对无符号类型表示数值总数取模后的余数。比如8比特大小的unsigned char可以表示0~255,如果我们将-1赋给它将会得到255
  • 当我们赋给带符号类型一个超出它表示范围的值时,结果是未定义的,程序可能会继续工作、崩溃,也可能产生垃圾数据
  • 当一个算数表达式中既有无符号类型又有int值时,int型的变量会被转化为无符号类型,结果可能是出乎意料的:
代码语言:javascript
复制
// 切勿混用带符号类型和无符号类型
unsigned u = 10;
int i = -42;
std::cout << i + i << std::endl; // -84
std::cout << i + u << std::endl; // 如果int占32位则输出4294967264
4. 字面值常量literal
  • 整型和浮点型字面量:20十进制;024八进制;0x14十六进制;3.14159E0浮点型
  • 字符和字符串字面量:'a'表示一个字符;"a"字符串字面量包含字母a和空字符\0

变量

1. 初始化
  • 含义:当对象在创建时获得了一个特定的值:则我们说这个对象被初始化了initialized
  • 初始化不等于赋值:初始化指的是创建变量时赋予其一个初始值,而赋值指的是把对象的当前值擦除并用一个新值替代
  • 列表初始化:C++11新标准的一部分,用花括号来初始化变量,这种方法有一定的优势:当使用列表初始化且初始值存在丢失信息的风险时则编译器将报错
  • 默认初始化:如果定义变量时没有指定初值,则变量将被默认初始化。对于内置类型而言,定义于任何函数体之外的变量被初始化为0,但是定义在函数体内部的内置变量将不被初始化
  • 绝大多数类都支持无须显式初始化而定义对象,这样的类提供了一个合适的默认值,而某些类要求每个对象都显式初始化
2. 变量声明与定义的关系

C++支持分离式编译机制,该机制允许将程序分割为若干个文件,每个文件可独立编译。

为了支持分离式编译,C++将定义和声明区分开。其中声明规定了变量的类型和名字,定义除此功能外还会申请存储空间并可能为变量赋一个初始值。如果想声明一个变量而非定义它,就使用关键字extern并且不要显式地初始化变量:

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

代码语言:javascript
复制
extern int i; // 声明i而非定义i
extern int i = 1; // 定义i, 这样做抵消了extern的作用 

复合类型

1. 引用

C++11中新增了“右值引用”,而我们这里讲的引用指的是“左值引用”。

  • 引用必须初始化
  • 引用本身并非对象,它是一个已经存在的对象的别名
  • 因为引用本身不是对象,所以不能定义引用的引用
2. 指针

指针只可能是以下四种情况:

  • 指向一个对象
  • 指向紧邻对象所占空间的下一个位置
  • 空指针
  • 无效指针

试图拷贝或者以其他方式访问无效指针的值都会引发错误,编译器并不会负责检查此类错误。空指针不指向任何对象,在试图使用一个指针之前最好先判断它是否为空。C++11中得到空指针最直接的方法就是字面值nullptr

建议:初始化所有指针。访问未经初始化的指针相当于去访问一个本不存在的位置上本不存在的对象。如果指针所占空间中恰好有内容,而这些内容又被当做某个地址。我们就很难分清它是否是合法的了。因此建议初始化所有指针,并且尽量等定义了对象之后再定义指向它的指针。如果实在不清楚指针应该指向何处,就将它初始化为nullptr,这样程序就能检测并在非法引用时报错。

const限定符

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

1. 多个文件共享const对象

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

默认状况下,const对象仅在文件内有效。const int bufSize = 512;以编译时初始化的方式定义一个const对象时,编译器将在编译过程中把用到该变量的地方都替换成对应的值。如果我们希望只在一个文件中定义const然后在其他多个文件中声明并使用它。解决的方法是对于const变量无论是声明还是定义都使用extern关键字,这样就仅需定义一次了。

代码语言:javascript
复制
// file_1.cc 定义并初始化了一个常量,该常量能被其他文件访问
extern const int bufSize = fcn();
// file_1.h 头文件
extern const int bufSize; // 与file_1.cc中定义的常量是同一个
2. 常量引用

与普通引用不同的是,对常量的引用不能被用作修改它所绑定的对象。

代码语言:javascript
复制
const int c1 = 1024;
const int &r1 = c1;
3. 指针和const
  • 指向常量的指针不能用于修改其所指对象的值,要想存放常量对象的地址,只能使用指向常量的指针。
  • 常量指针:允许将指针本身定为常量,常量指针必须初始化
代码语言:javascript
复制
int errNum = 0;
int *const curErr = &errNum; // curErr会一直指向errNum
const double pi = 3.14159;
const double *const pip = &pi; // pip是一个指向常量对象的常量指针
4. 顶层const

指针本身是一个对象,它又可以指向另一个对象。因此指针本身是不是常量以及指针所指的是不是一个常量就是两个相互独立的问题。顶层const表示指针本身是一个常量,底层const表示指针所指的对象是不是一个常量。

当执行对象的拷贝操作时,拷入和拷出的对象必须具有相同的底层const资格。

5. constexpr和常量表达式

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

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

处理类型

1. 类型别名

类型别名type alias是一个名字,它是某种类型的同义词。它让复杂的类型名字变得简单明了、易于理解和使用。

代码语言:javascript
复制
// 传统方法
typedef double wages; // wages是double的同义词
// 新标准
using SI = Sales_item
2. auto类型说明符

C++11引入了auto类型说明符,可以让编译器通过初始值来推断变量的类型。需要注意的是,编译器推断出来的auto类型有时候与初始值的类型并不完全一样,编译器会适当地改变结果类型使其更加符合初始化规则。

  • 当引用作为初始值时,真正参与初始化的是引用对象的值
  • atuo一般会忽略掉顶层const,底层const会保留下来,比如当初始值是一个指向常量的指针。如果希望推断出的auto类型是一个顶层const时,需要明确指出:
代码语言:javascript
复制
const int ci = i;
const auto f = ci;

自定义数据结构

1. 定义一个不带有任何运算功能的数据结构
代码语言:javascript
复制
struct Sales_data {
    std::string bookNo;
    unsigned units_sold = 0;
    double revenue = 0.0;
};

Sales_data accum, trans, *salesptr;
2. 编写自己的头文件

为了确保各个文件中类的定义一致,类通常被定义在头文件中,而且类所在头文件的名字应与类的名字一样。

  • 头文件通常包含哪些只能被定义一次的实体,如类、constconstexpr变量等
  • C++会使用头文件保护符来防止包含多份相同的头文件。
代码语言:javascript
复制
#ifndef SALES_DATA_H
#define SALES_DATA_H

#include <string>

struct Sales_data {
    std::string bookNo;
    unsigned units_sold = 0;
    double revenue = 0.0;
};
#endif
  • C++C语言中的头文件如name.h重命名为cname,即去掉了.h后缀同时在前面加上字母c。一般而言C++程序员应该使用cname的头文件而非name.h的形式,标准库中的名字总能在命名空间std中找到,如果使用name.h则程序员不得不时刻牢记从属于C还是C++
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 写这篇文章的目的
  • 全文链接
  • 基本内置类型
    • 1. 内置类型的及其实现
      • 2. 如何选择类型
        • 3. 类型转换
          • 4. 字面值常量literal
          • 变量
            • 1. 初始化
              • 2. 变量声明与定义的关系
              • 复合类型
                • 1. 引用
                  • 2. 指针
                  • const限定符
                    • 1. 多个文件共享const对象
                      • 2. 常量引用
                        • 3. 指针和const
                          • 4. 顶层const
                            • 5. constexpr和常量表达式
                            • 处理类型
                              • 1. 类型别名
                                • 2. auto类型说明符
                                • 自定义数据结构
                                  • 1. 定义一个不带有任何运算功能的数据结构
                                    • 2. 编写自己的头文件
                                    领券
                                    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档