首页 > 编程语言 >C++ primer 5th 第二章 变量和基本类型 阅读笔记

C++ primer 5th 第二章 变量和基本类型 阅读笔记

时间:2023-02-23 23:45:01浏览次数:34  
标签:const 常量 int 5th C++ 类型 primer 指针 变量

第二章 变量和基本类型

第一节 基本内置类型

C++标准规定了算术类型尺寸的最小值,同时允许编译器赋予这些类型更大的尺寸。

比如:

类型 含义 最小尺寸
bool 布尔类型 未定义
wchar_t 宽字符 16位
char16_t Unicode字符 16位
char32_t Unicode字符 32位
short 短整型 16位

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

其它字符类型用于扩展字符集,如wchar_tchat16_tchar32_twchar_t类型用于确保可以存放机器最大扩展字符集中的任意一个字符;类型char16_tchar32_t则为Unicode服务(Unicode是用于表示所有自然语言中字符的标准)。

C++语言规定一个int至少和一个short一样大(大指的是最大位数),一个long至少和一个int一样大,一个long long至少和一个long一样大,其中,数据类型long long是在C++11中新定义的。

大多数计算机以2的整数次幂个bit作为块来处理内存,可寻址的最小内存块称为字节(byte)。存储的基本单元称为(word),通常由几个字节组成。在C++语言中,一个字节要至少能容纳机器基本字符集中的字符。大多数机器的字节由8比特构成,字则由32或64比特构成,也就是4或8字节。

对于浮点数的类型和精度,有:

类型 字数(位数) 有效数字
float 1(32) 7
double 2(64) 16
long double 3或4(96或128) 硬件的实现不同,精度各不相同

字符型被分为了三种:charsigned charunsigned char。和int不同,类型char和类型signed char并不一样。字符型有三种,但是字符的表现形式却只有两种:带符号的和无符号的。所以char实际上会表现为signed charunsigned char形式中的一种,具体是哪种由编译器决定。

unsigned char c=-1; // 假设char占8比特,c的值为255,因为unsigned char可以表示0到255共256个数字,所以最后的值是-1对256取模为255。

signed char c2=256; // 假设char占8比特,c2的值是未定义的,当我们赋给带符号类型一个超出它表示范围的值时,结果是未定义的。此时,程序可能继续工作,可能崩溃,也可能产生垃圾数据

intunsigned int相加的时候是int变成unsigned int的形式,然后再运算,即先进性类型转换。如果这里有int类型的-1和unsigned int类型的1相乘,结果本来希望是1,最后的结果为unsigned int的最大值。

for(unsigned u = 10; u >= 0; u--)会死循环,0之后是unsigned int的最大值。解决方法:

while(u > 0)u++;
while(u > 0) {
    u--;
}

某些一望而知类型的常量叫做字面值常量,比如1.0、21、024、0x14。

整型字面值具体的数据类型由它的值和符号决定。默认情况下,十进制字面值是带符号数;八进制和十六进制字面值既可能是带符号的也可能是无符号的。十进制字面值的类型是intlonglong long中能容纳当前值中尺寸最小的那个;八进制和十六进制字面值的类型是能容纳其数值的intunsigned intlongunsigned longlong longunsigned long long中的尺寸最小者。但如果均放不下,则会产生错误。

十进制字面值不会是负数,比如-21是一个负十进制字面值,那个负号并不在字面值之内,它的作用仅仅是对字面值取负值而已。

如果两个字符串字面值位置紧邻且仅由空格、缩进和换行符分割,则它们实际上是一个整体。当书写的字符串字面值比较长,写在同一行里不太合适时,就可以采取分开书写的方式。比如:

cout << "abc "
    "def" << endl;

通过添加前缀和后缀,可以改变整型、浮点型和字符型字面值的默认类型:

  • L作为前缀可表示宽字符型字面值,类型为wchar_t,比如:L'a'
  • u/U作为前缀可表示Unicode 16/32字符型字面值,类型为char16_t/char32_t
  • u8作为前缀可表示UTF-8字符串型字面值,类型为char

浮点型字面值中fF作为后缀时表示的类型为floatlL作为后缀时,类型为long double


第二节 变量

对象是指一块能存储数据并具有某种类型的内存空间

初始化不是赋值的一种!!!在C++中,初始化和赋值是两个完全不同的操作。

列表初始化:定义一个名为aint变量并初始化到0,以下的4条语句都可以做到这一点:

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

这是c++11新标准的一部分,用花括号来初始化变量的形式被称为列表初始化,无论是初始化对象还是为对象赋新值,都可以使用这样一组由花括号括起来的初始值。

这几句的区别:

  • 初始化四句都可以,但是赋值操作时,a(0)a{0}不合法,只能是a = (0)a = {0}
  • ()不会检查类型的问题,可以强制类型转换,但是{}会判断类型是否正确,如果不正确直接报错。

为了允许把程序拆分成多个逻辑部分来编写,C++语言支持分离式编译(separate compilation)机制,该机制允许将程序分割为若干个文件,每个文件可被独立编译。这需要有文件间共享代码的方法,一个代码可能需要使用另一个文件中定义的变量。一个实际的例子是cincout,定义于标准库,却能被我们写的程序使用。

为了支持分离式编译,C++语言将声明和定义区分开来。声明(declaration)使得名字被程序所知,一个文件如果想使用别处定义的名字则必须包含对那个名字的声明。而定义(definition)负责创建与名字关联的实体。即变量可以杯声明多次,但只能被定义一次。

声明变量但并不定义变量,使用extern,而且此时不能显式地初始化变量,因为显式初始化的声明会变成定义,即extern double pi = 3.1415926相当于让extern失效 。

C++是一种静态类型语言,其含义是在编译阶段检查类型。在C++语言中,编译器负责检查数据类型是否支持要执行的运算。

变量命名规范:

  • 变量名一般用小写字母
  • 用户自定义的类名一般以大写字母开头,比如Sales_item
  • 如果标识符由多个单词组成,则单词间应有明显区分,即用下划线分开或下一个单词首字母大写

现在有一个全局变量bmain函数中有一个局部变量b,那么还怎么访问那个全局的b呢?用::b,我们说过::是作用域操作符,因为全局作用域本身并没有名字,所以左侧为空时,向全局作用域发出请求获取作用域操作符右侧名字对应的变量。


第三节 复合类型

我们平时说的引用指的是左值引用。

引用就是起了个别名。

引用必须被初始化,因为和它的初始值对象要一直绑定在一起,没有自己的内存,所以引用并非对象。

比如ba的引用,c也是引用,可以用int &c = a;,也可以用int &c = b;

10这种常量得用const int &,不能用int &。00

引用与实际对象的类型必须得一样。

指针和引用的不同点:

  • 指针本身也是一个对象,允许对指针赋值和拷贝。
  • 指针的生命周期里可以指向不同的对象,引用不能变。
  • 指针无需在定义时赋初值,如果没有被初始化,会有个不确定的值。

赋值空指针的方法:

int *p1 = nullptr;  // C++11新标准引入的特殊类型字面值,可以被转成任意其他的指针类型
int *p2 = 0;
int *p3 = NULL;  // 需要先#include cstdlib

预处理器是运行于编译过程之前的一段程序,预处理变量不属于命名空间std,由预处理器负责管理,因此可以直接使用预处理变量而无需在前面加std::

新标准下最好使用nullptr,不尽量避免使用NULLNULL是老代码中用的,并且预处理程序直接把它变成0了。

指针是否相等看的是地址是否相等,而不是指的是不是一个东西,比如:

int a[3][4];
int *p1 = a[0];
int *p2 = &a[0][0];
cout << (p1 == p2); // 1

void *虽然可以接收任意类型的指针,但是操作有限,比如可以进行地址比较、作为函数的输入或输出、赋值给另一个void *,但是不能直接操作该对象,因为不知道类型,也就不知道可以怎么操作,在void *的视角来看,内存空间仅仅只是内存空间。

int* p1, p2可以这么写,但是会产生误导,因为这里的意思是p1int *p2int

指向指针的指针:

int ival = 1024;
int *pi = &ival;
int **ppi = &pi;
cout << **ppi << endl;  // 1024

ppi存的是pi的地址,pi存的是ival的地址。

因为引用不是一个对象,所以不存在引用的指针;但是指针是对象,所以存在指针的引用:

int i = 24, *p = &i;
int *&r = p;  // 注意次序,离变量越近的东西对变量的影响越大,所以是个引用,然后是指针的引用,最后是一个int类型指针的引用。
cout << *r << endl;  // 24

拓展:

int i = 24, *p = &i;
int j = 42;
int *&r = p;
cout << *r << endl;  // 24
p = &j;
cout << *r << endl;  // 42(跟着p走)

和:

int i = 24, *p = &i;
int j = 42;
int* const &r = &i;  // 这个是常量,所以要在*和&之间加个const,然后r和i的地址锁死
cout << *r << endl;  // 24
p = &j;
cout << *r << endl;  // 24,p动不影响r的值。

第四节 const限定符

const限定的变量不是说改变值会报错,是赋值操作就会报错,值和原值一样也会报错。注意:全局变量也不可以省略初始化而认为它是0,也会报错。

与非const限定变量相比,const能完成大部分操作,比如强制类型转换。

变量之间互相初始化,和它们是不是const无关,都不会报错,因为只传递值,一旦拷贝完成,二者便没有什么关系。

编译器在编译过程中把用到const变量的地方都替换成其对应的值。

默认情况下,const对象仅在文件内有效。我们如果希望只有一个该变量,剩下的文件都使用这一个const变量。解决方法是不管是声明还是定义都添加extern关键字,这样只需要定义一次就行了:

// a.cc文件中定义并初始化一个常量,该常量能被其他文件访问,需要用extern!!!
extern const int bufsize = func();

// a.h头文件中声明,和a.cc中的是同一个
extern const int bufsize;

常量的引用:const int &r1 = c1;,引用及其对应的对象都是常量。不能用非const类型引用指向const对象。因为不能通过引用来改变原const对象的值。反过来可以,因为改变int对象的值而使const引用的值改变无所谓,引用本身也不是对象。

Q:一个常量引用被绑在一个对象身上发生了什么呢?看下面的一串代码:

double dval = 3.14;
const int &ri = dval;
cout << ri << endl;  // 3

为了确保const int引用绑定的是个const int,编译器搞了个临时变量temp出来:

const int temp = dval;  // temp = 3
const int &ri = temp;  // ri绑定临时常量

对于const来说,指针的规则和引用一样,不能让普通的int指针指向const int常量。

int *const是常量指针(从右往左读),指的是从一而终都指向这一个东西。

我们定义让自己是常量的const叫做顶层const,让指向对象为常量的const为底层const

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

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

const int max_files = 20;  // max_files是常量表达式
const int limit = max_files + 1;  // limit是常量表达式,因为max_files被20替代,所以直接可以知道是21
int staff_size = 27;  // 不是const,所以不是常量表达式
const int sz = get_size();  // 因为不是编译过程能计算出来的,所以不是常量表达式

C++11新标准规定,允许将变量声明为constexpr类型以便由编译器来验证变量的值是否是一个常量表达式。声明为constexpr的变量一定是一个常量,而且必须用常量表达式初始化。对于constexpr int sz = size();,只有size是一个constexpr函数时,才是一条正确声明语句,而新标准允许定义一种特殊的constexpr函数,这种函数足够简单以使得编译时就可以计算其结果,这样就能用constexpr函数去初始化constexpr变量了。(见第六章)

总结:一般来说,如果认定变量是一个常量表达式,那就把它声明成constexpr类型。

算术类型、引用和指针都属于字面值类型,可以定义成constexpr类型。指针和引用虽然可以这样被定义,但是初始值受到严格限制,一个constexpr指针的初始值必须是nullptr或者0,或者是存储于某个固定地址中的对象。

函数体内定义的变量一般来说并非存放在固定地址中,因此constexpr指针不能指向这样的变量,定义于所有函数体之外的对象其地址固定不变,能用来初始化constexpr指针。(还真每次都不变)

但是constexpr指针指的是自己是常量,而不是所指对象为常量:constexpr int *q = nullptr; // q是一个指向整数的常量指针。即constexpr不管是不是指针都是顶层const

constexpr int i = 42;  // i是整型常量,和const的区别是会检查右侧是不是常量表达式
int j = 21;
constexpr const int *p = &i;  // 常量指针指向常量
constexpr int *p1 = &j;  // 可以指向非const

第五节 处理类型

复习:

typedef double wages;
typedef wages base, *p;  // base = double, p = double*

还可以用using,即别名声明来定义类型的别名:using SI = Sales_item;,就可以用SI代替Sales_item类型了。

注意,不是说typedef就是直接替换的意思!!!:

typedef char *pstr;
const pstr cstr = 0;  // 注意这里相当于:char *const ctr = 0; 即cstr为常量指针而非指针常量,直接换就错了
const pstr *ps;  // char **const ps; 即*挪到前面去

C++11新标注引入了auto类型说明符,可以让编译器替我们分析表达式所属的类型。auto让编译器通过初始值来推断变量的类型,显然auto定义的变量必须有初始值。

可以理解为auto换成一个词,这个词是类型。所以有:

auto i = 0, *p = &i;  // 正确,i为int,p为int *。
auto sz = 0, pi = 3.14;  // 错误,sz为int,pi为double。

auto初始化的右侧为引用的情况下,类型为本身变量对象的类型,而不是也是引用。

auto忽略掉顶层的const,而保留底层的const

const int ci = 5;
auto e = &ci;  // e是一个指向整数常量的指针(因为对常量对象取址就是让这个所指对象是常量,所以是底层const,应该保留,所以e是const int*。而如果是普通的int,这里就是int*。

如果auto前还有const,这个const为顶层的(很显然因为顶层被忽略,所以这里是补的顶层的const)。

而如果要让auto带上引用的话,可以用auto &。比如const auto &j = 42;,但如果auto &j = 42;就错了,会识别为int &j,于是报错。但是:

const int ci = 10;
auto &g = ci;  // 这个g就被识别为const int &,因为这个const同样是说被指向的对象是常量,所以为底层的const

希望从表达式的类型中推断出要定义的变量的类型,但是不想用该表达式的值初始化变量。C++11decltype,它的作用是选择并返回操作数的数据类型,并不实际计算表达式的值。比如:decltype(f() + 1) sum = x;规定sum的类型,甚至不用调用f()

decltype处理顶层constauto不同,decltype不会忽略const也不会忽略引用。引用从来都作为其所指对象的同义词出现,只有用在decltype处是一个例外

decltype(*p)的结果类型是int &

decltype中加括号是一种操作符,相当于取址,即可得到引用类型:decltype((i)) d;是错误的,因为为int &必须初始化。decltype((variable))一定是引用,而decltype(variable)只有当variable是引用的时候才是引用。

第六节 自定义数据结构

一般来说,最好不要把对象的定义和类的定义放在一起,一会定义类,一会定义变量,是一种不被建议的行为。

确保头文件多次包含仍能安全工作的常用技术是预处理器。之前涉及到的预处理功能为#include,当预处理器看到#include标记时会用指定的头文件的内容代替#include

C++程序还会用到的一项预处理功能是头文件保护符,头文件保护符依赖于预处理变量。预处理变量有两种状态:已定义和未定义。#ifdef当且仅当变量已定义为真,#ifndef当且仅当变量未定义时为真。一旦检查结果为真,则执行后续操作直至遇到#endif指令为止。

为了避免与程序中其他实体发生名字冲突,一般把预处理变量的名字全部大写。

标签:const,常量,int,5th,C++,类型,primer,指针,变量
From: https://www.cnblogs.com/fansoflight/p/17149907.html

相关文章

  • C++的内存模型
    C++的内存包含4个大区,它们分别是代码区、全局区、栈区和堆区。以下将对它们的分区进行进一步的阐述。代码区:对于一段代码,首先要经过编译之后生成可执行文件才能执行,在Wi......
  • C++学习(2)STL八股文
    1、STL实现原理及其实现STL提供了六⼤组件,彼此之间可以组合套⽤,这六⼤组件分别是:容器、算法、迭代器、仿函数、适配器(配接器)、空间配置器。STL六⼤组件的交互关系:a.容......
  • C++问题集
    const函数名后,加const使类的成员函数,不能修改类内成员。mutable可以突破const限制!在函数后面加const只能在类的成员函数中实现!普通的函数是无法进行这样的操作的!vo......
  • C/C++图书管理系统[2023-02-23]
    C/C++图书管理系统[2023-02-23](辅修)高级语言程序设计课程设计图书管理系统设计并实现一个学校图书馆的图书管理系统。具体要求:1、 图书信息和借阅信息等保存在文本文......
  • C++主函数参数
    学习C++主函数的参数输入,用于从commandline中读取参数,下面以读取视频文件为例进行说明#include<iostream>#include<fstream>#include<string>#include<opencv2/op......
  • C/C++宠物信息管理系统[2023-02-23]
    C/C++宠物信息管理系统[2023-02-23]计算机科学与技术专业课程设计任务书学生姓名专业班级学号题目宠物信息管理系统主要内容开发一个简单的宠物信息管理系统。要......
  • C++基础-2 const auto auto decltype....
                           ......
  • c++线程的使用
    c++11之后,c++语言提供了并发编程的语言支持。c++11增加了线程以及线程相关的类。c++11提供的线程类叫做std::thread,创建线程只需提供线程函数或者函数对象,并且可以指定参......
  • C++入门
    #include<iostream>usingnamespacestd;intmain(){ cout<<"helloworld"<<endl; return0;}一、C++中的头文件(一)climits头文件climits(在老式实现中为limit......
  • C/C++参考选题[2023-02-23]
    C/C++参考选题[2023-02-23]必选题参考:题目一学生成绩管理系统1功能描述设某班有n位同学,每位同学的数据包括以下内容:学号(字符串)、姓名(字符串)、数学成绩(整型)、程序设......