C++ Primer 学习笔记(第二章 变量和基本类型)

目录

2.1 基本内置类型

2.2 变量

2.3 复合类型

2.4 const 限定符

2.5 处理类型

2.6 自定义数据结构

2.7 其他


第 2 章  变量和基本类型

2.1 基本内置类型

表 2.1:C++ 算术类型
类型 含义 最小尺寸
bool 布尔类型 未定义
char 字符 8位
int 整形 16位
long long 长整型 64位
float 单精度浮点数 6位有效数字
double 双精度浮点数 10位有效数字

 建议:①当明确知晓数值不可能为负,建议选用无符号类型。

            ②整数运算用 int,超过 int 表示范围时用 long long。

            ③算术表达式中,非必要不使用 char 和 bool。

            ④浮点数运算用 double 即可。 

Ⅰ)自动类型转换

1)将一种类型的数据赋值给另外一种类型的变量时就会发生自动类型转换。如:

float f = 10;    // 10是int类型,先转换为float类型10.000000,再赋给f
int m = f;       // f是float类型,先转换为int类型10,再赋给m
int n = 3.14;    // 3.14是float类型,先转换为int类型3,再赋给n

2)在不同类型的混合运算中,编译器也会自动地进行数据类型转换,将参与运算的所有数据先转换为同一种类型,然后再进行计算。转换规则如图:

C++ Primer 学习笔记(第二章 变量和基本类型)_第1张图片

注:如上图所示,当一个算术表达式中既有无符号数又有 int 值时,那么 int 值会转换成无符号数。因此,切勿混用带符号类型和无符号类型。

unsigned u = 10;
int i = -42;
std::cout << i+i << std::endl;    // 输出-84
std::cout << u+i << std::endl;    // 如果int占32位,输出4294967264

Ⅱ) 强制类型转换

格式:(type_name)expression    例如:

(float) i;
(int) (a+b);
(float) 10;

(double) x/y;    // 先将 x 转换为 double 类型,再除以 y。
(double) (x/y);  // 先计算 x/y,再转换为 double 类型。对于除法运算,若 x 与 y 都是整数,则运算结果也是整数,小数部分将被直接丢弃;若 x 与 y 有一个是小数,则运算结果也是小数。

注意:自动类型转换和强制类型转换,都只是本次运算临时性的,不会改变数据本来的类型或者值。

Ⅲ) 字面值常量

主要包括:整型和浮点型、字符和字符串、转义序列、布尔和指针等字面值常量,同时,还可以指定字面值类型。

表2.2 指定字面值的类型

字符和字符串字面值

前缀

含义

类型

u

Unicode 16字符

char16_t

U

Unicode 32字符

char32_t

L

宽字符

wchar_t

u8

UTF-8

char

整型字面值

浮点型字面值

后缀

最小匹配类型

后缀

类型

u or U

unsigned

f或F

float

l or L

long

l或L

long double

ll or LL

long long


2.2 变量

对于C++程序员,“变量”和“对象”可以互换使用。

Ⅰ) 变量定义基本形式: 类型说明符 变量名1,变量名2,...;

注意初始化和赋值的区别,常见的初始化形式有:

int a = 0;
int a = {0};
int a{0};
int a(0);

其中,通过花括号进行初始化的形式被称为列表初始化。 如果使用列表初始化且初始值存在丢失信息的危险,编译器将报错。

Ⅱ)变量的声明和定义:声明使得名字为程序所知,定义负责创建与名字相关的实体,申请内存空间。变量只能被定义一次,但是可以被多次声明。在C++项目开发中,声明一般统一放在头文件里面,在源程序中定义,并且建议在第一次使用变量时再定义。

extern int i;                // 声明 i,而非定义 i 
int j;                       // 定义 j
extern double pi = 3.1416;   // 因为有了显式初始化,extern 的作用被抵消,这里变成定义了,注意:只能作为全局变量的定义,即放在 main 函数外面。

2.3 复合类型

Ⅰ)引用:就是为对象起了另外一个名字(引用即别名),定义引用时就把引用和它的初始值绑定了,并且不能重新绑定,因此引用必须初始化,且初始值必须是一个对象而不是字面值。

int a;
int &b = a;    // b其实只是a的一个别名,注意b不是对象。
int &c;        // 报错!引用必须初始化

Ⅱ)指针:

int a = 1;
int *p = &a;
int *p1 = nullptr;    // 空指针
void *p2 = &a;        // p2可以用来存放任意对象的地址,这里存放int类型的 a 的地址
std::cout << *p;

Ⅲ) 指针和定义的区别:指针本身是一个对象,允许赋值和拷贝,可以先后指向不同的对象,并且定义时不是必须初始化(不过建议初始化为 nullptr 或者 0)。

Ⅳ)指向指针的引用:从右往左阅读,离变量名最近的符号对变量的类型有最直接的影响,所以上面的r是一个引用,*说明引用的是一个指针。因此,r是指向int类型指针的引用。

int i = 42;
int *p;            // p是int型指针
int *&r = p;       // r是对指针p的引用
r = &i;
*r = 0;

2.4 const 限定符

const 限定符用来对变量的类型加以限定,如:const int bufSize = 512。

const int i = get_size();
const int j = 42;
const int k;                    // 错误,未初始化


int i = 42;
const int ci = i;
int j = ci;

默认状态下,const 对象仅在文件内有效。 要想只在一个文件中定义const,而在其他多个文件中声明并使用它,可以对于 const 变量不管是声明还是定义都添加 extern 关键字。

// file_1.cc 定义并初始化了一个常量,该常量能被其他文件访问
extern const int bufSize = fcn();

// file_1.h 头文件
extern const int bufSize;

Ⅰ)const 的引用:可以把引用绑定在 const 对象上,就像绑定在其他对象上一样,我们称之为对常量的引用。

const int ci = 1024;
const int &r1 = ci;    // 正确,引用和对应的对象都是常量

ri = 42;               // 错误! r1是对常量的引用,不能修改值
int &r2 = ci;          // 错误! 试图让一个非常量引用指向一个常量对象,如果合法,则可以通过改变r2来改变ci,显然不正确



int i = 42;
const int &r1 = i;      // 正确
const int &r2 = 42;     // 正确
const int &r3 = r1 * 2; // 正确
int &r4 = r1 * 2;       // 错误! 如果合法,则可以通过r4修改r1*2的值

Ⅱ)指针和const:

const double pi = 3.14;    // pi是个常量,值不能改变
double *ptr = π         // 错误! ptr是个普通指针,可能会改变pi
const double *cptr = π  // 正确
*cptr = 42;                // 错误! cptr不能赋值

double dval = 3.14;
cptr = &dval;              // 正确,但是不能通过cptr改变dval的值

 指向常量的指针和引用,不过是它们自以为指向了常量,因此会自觉地不去改变所指对象的值,但实际上它们所指对象的值可以通过其他方式改变。

Ⅲ)const 指针:由于指针也是对象,因此允许把指针定为常量,并且也必须初始化。

int a = 0;
int *const b = &a;                // b将一直指向a
const double pi = 3.14;
const double *const pip = π    // pip是一个指向常量对象的常量指针
*pip = 2.72;                      // 错误! pip是一个指向常量的指针

Ⅳ)顶层const: 表示指针(或任意对象)本身是一个常量;底层const:表示指针所指的对象是一个常量。

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

使用 constexpr 修改普通变量时,变量必须经过初始化且初始值必须是一个常量表达式。

constexpr int n = 1 + 1;
int u[n] = {1,2};
int url[10];
int url[6 + 4];
int length = 6;
int url[length];                  //错误,length是变量

constexpr int mf = 20;            // 20是常量表达式
constexpr int limit = mf + 1;     // mf + 1 是常量表达式
constexpr int sz = Size();        // 只有当Size是一个constexpr函数时才是一条正确的声明语句

constexpr 可以用于修饰函数的返回值,这样的函数又称为“常量表达式函数”。

// 常量表达式函数:
// 整个函数的函数体中,除了可以包含 using 指令、typedef 语句以及 static_assert 断言外,只能包含一条 return 返回语句。
// 该函数必须有返回值,即函数的返回值类型不能是 void。
// 函数在使用之前,必须有对应的定义语句。普通的函数调用只需要提前写好该函数的声明部分即可(函数的定义部分可以放在调用位置之后甚至其它文件中),但常量表达式函数在使用前,必须要有该函数的定义。
// return 返回的表达式必须是常量表达式


constexpr int sum(int x, int y) {
    return x + y;
}

Ⅵ)字面值类型 :算术类型、引用和指针都是字面值类型,而自定义类、IO库、string类型则不属于字面值类型。

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

const int *p = nullptr;            // 指向 “整型常量” 的指针 
constexpr int *q = nullptr;        // 指向整数的 “常量指针”

2.5 处理类型

Ⅰ)类型别名:某种类型的同义词。传统方法是使用关键字typedef,新标准规定了一种新的方法,即使用别名声明using来定义类型的别名。

using SI = Sales_item;

特别要注意含有指针的类型别名,例如:

typedef char *pstring;        // char * 和 pstring等价,但不意味着简单的替换。
const pstring cstr = 0;       // cstr 是指向char的常量指针。
const pstring *ps;            // ps前面的*表示ps是一个指针,它的对象是“指向char的常量指针”。


const char *cstr = 0;         // 简单替换的结果:内容不可变为常量,但地址可变的指针变量。而原表达式的意思是常量指针,地址不变。

Ⅱ)auto 类型说明符:编译器替我们分析表达式所属的类型,auto 定义的变量必须有初始值。

auto item = val1 + val2;

auto i = 0, *p = &i;
auto sz = 0, pi = 3.14;        // 错误!sz和pi的类型不一样

注意auto对顶层和底层const的作用: 

int i = 0, &r = i; 
const int ci = i, &cr = ci;

// 忽略顶层const
auto b = ci;                // b是一个整数,忽略ci的顶层const特性
auto c = cr;                // c是一个整数,忽略cr(ci)的顶层const特性
auto d = &i;                // d是一个整型指针(因为&i是整数的地址)

// 保留底层const
auto e = &ci;               // e是一个指向整数常量的指针(对常量对象取地址是一种底层const)

// 如果希望auto类型是一个顶层const,可以明确指出(否则还是当成整数)
const auto f = ci;

// 将引用的类型设置为auto
auto &g = ci;
auto &h = 42;                // 错误! 不能为非常量引用绑定字面值(非常量引用,可以修改h,但是h绑定的是42)
const auto &j = 42;          // 正确,可以为常量引用绑定字面值

Ⅲ)decltype 类型指示符:选择并返回操作数的数据类型。

decltype((variable)) 的结果永远都是引用,decltype(variable)的结果只有当variable本身是一个引用时才是引用。

decltype (f()) sum = x;            // sum的类型就是函数f返回的类型
const int ci = 0, &cj = ci;
decltype (ci) x = 0;               // x的类型是const int
decltype (cj) y = x;               // y的类型是const int &
decltype (cj) z;                   // 错误:z是一个引用,必须初始化

int i = 42, *p = &i, &r = i;
decltype (r+0) b;                  // b为int型
decltype (*p) c;                   // 错误!解引用表达式,c的类型为引用,必须初始化,
decltype ((i)) d;                  // 双括号必为引用
decltype (i) e;                    // e是一个未初始化的int

2.6 自定义数据结构

Ⅰ)数据结构:一组数据以及相关操作的集合。

Ⅱ)类的定义:以struct或class开始,紧跟着类名和类体,花括号后面必须跟着一个分号。


2.7 其他

定义一般放在头文件中,头文件的书写一般规则:

#ifndef SALES_DATA_H
#define SALES_DATA_H


...


#endif

参考教程: C++11 constexpr:验证是否为常量表达式

                   C++11 constexpr和const的区别详解

                   C++ 常量

                   C++引用1

                   C++ 数据类型

                   C++ 变量类型

                   C++ 变量作用域​​​​​​

                   C++ 引用2

                   C++ 指针

你可能感兴趣的:(C++,Primer,c++,开发语言)