第2章 基本数据类型

第2章 基本数据类型

2.2 变量

变量提供一个具名的、可供程序操作的存储空间

变量能被声明多次,却只能被定义一次。

注:C++是一种静态类型语言,其含义是在编译阶段检查类型,检查类型的过程称为类型检查。

2.2.3 标识符

C++的标识符由字母、数字和下画线组成,其中必须以字母或下画线开头。C++保留部分名字供自身使用,这些不能用作标识符。用户自定义的标识符不可以连续出现两个下画线,也不能以下画线紧连大写字母开头,定义在函数体外的标识符不能以下画线开头

2.3 复合类型

复合类型是指基于其他类型定义的类型,本章主要介绍引用和指针

2.3.1 引用

引用为对象起了另外一个名字,引用类型引用另外一种类型。通常将声明符写成&d的形式来定义引用类型,其中d是声明的变量名:

int val=1024;
int &refVal=val;//refVal指向val(是val的另一个名字)
int &refVal;//报错,引用必须被初始化

引用即别名:引用并非对象,而是为一个已经存在的对象起了另外一个名字。定义引用后,对其进行的所有操作都是在与之绑定的对象上进行的。引用只能绑定在对象上,而不能与字面值或某个表达式的计算结果绑定在一起

int &refVal2=10; //错误:引用类型的初始值必须是一个对象
double dval=3.14;
int &refVal3=dval;//错误:此处引用类型的初始值必须是int类型,数据类型要对应

2.3.2 指针

指针是指向(point to)另外一种类型的复合类型。指针和引用的区别如下:

区别
相同点都实现了对其他对象的间接访问
不同点1、指针本身是一个对象,允许赋值和拷贝,而且在指针的声明周期内它可以先后指向几个不同的对象,而引用不是对象,而是绑定在对象上。
2、指针无需在定义时赋初值(在块作用域定义的指针如果没有初始化,值不确定),而引用则必须在定义时赋初值。

注:声明语句中指针的类型实际上被用于指定它所指向对象的类型,所有两者必须匹配(存在例外情况)。

double dval;
double *pd=&dval;//正确:初始值是指向double对象的地址
int *pi=pd;//错误:指针pi的类型和pd类型不匹配

void *指针

void *指针是一种特殊的指针类型,可用于存放任意对象的地址

注:不能直接操作void *指针所指的对象,因为我们并不直到这个对象是什么类型,也就无法确定能在这个对象进行哪些操作。

2.4 const限定符

const修饰的对象是常量,一旦创建后就不可以被修改,所以const对象必须初始化。

2.4.1 const的引用

可以把引用绑定到const对象上,称为对常量的引用。对常量的引用不能被用作修改它所绑定的对象。

int val;
const int &r=val;//常量引用

注:常量引用被绑定到另外一种类型上会产生非法行为

double dval=3.14;
const int &ri=dval;
//此处ri引用了一个int型数,但dval却是一个双精度浮点数。因此为了确保让ri绑定一个整数,编译器会将代码变下面形式

const int temp=dval;
const int &ri=temp;
//ri绑定了一个临时量对象,而非dval,非法行为

2.4.2 指针和const

指向常量的指针不能改变其所指对象的值,若想存放常量对象的地址,只能使用指向常量的指针。

const double pi=3.14;
double *ptr=π//错误:ptr是普通指针
const double *ptr2=π//正确:ptr2可以指向一个双精度常量
*ptr=42;//错误:不能给*ptr2赋值

上面指针章节中提到,指针的类型必须与所指对象的类型的一致,但存在例外。第一种例外就是允许一个指向常量的指针指向一个非常量对象:

double dval=3.14;
ptr2=&dval;//正确:但是不可以通过ptr2改变dval的值
const指针

指针是对象而引用不是,因此可以把指针本身作为常量。常量指针必须初始化,而且一旦初始化完成,则它的值(也就是存放在指针中的那个地址)就不能再改变了。把*放在const关键词之前以说明指针是一个常量,不变的是指针本身而非指针指向的那个值

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

2.4.3 顶层const

名词顶层const表示指针本身是个常量,而用名词底层const表示指针所指的对象是一个常量。更一般的,顶层const可以表示任意的对象是常量;底层const则与指针和引用等复合类型的基本类型部分有关。特殊的是,指针类型既可以是顶层const也可以是底层const。


int i=0;
int *const p1=&i;//常量指针 不能改变p1的值 顶层const
const int ci=42;//常量,不能改变ci的值,顶层const
const int *p2=&ci;//指针常量,允许改变p2的值,但不允许改变ci的值,底层const
const int *const p3=p2;//靠右的const是顶层const,靠左的是底层const,不允许改变p3和ci的值
const int &r=ci;//用于声明引用的cosnt都是底层const

执行对象的拷贝操作时,常量是顶层const还是底层const区别明显。其中顶层const不受什么影响:执行拷贝操作并不会改变被拷贝对象的值,因此,拷入和拷出的对象是否是常量都没什么影响。

i=ci;//正确:拷贝ci的值,ci是一个顶层const,对操作无影响
p2=p3;//正确:p3和p2指向的对象类型相同,p3底层const的部分无影响

然而,底层const的限制却不能忽略。当执行对象的拷贝操作时,拷入和拷出的对象必须具有相同的底层const资格或者两个对象的数据类型必须能够转换。一般来说,非常量可以转换成常量,反之不行。

int *p=p3;//错误:p3包含底层const的定义,而p没有
p2=p3;//正确:p2和p3都是底层const
p2=&i;//正确:int *可以转换成const int *
int &r=ci;//错误:常量不可以转换成非常量,普通的int&不能绑定到int常量
const int &r2=i;//正确:const int&可以绑定到一个普通int上,非常量转换成常量

2.4.4 constexpr和常量表达式

常量表达式是指值不会改变并且在编译过程中就能得到计算结果的表达式。字面值属于表达式,用常量表达时初始化的const对象也是常量表达式。

int staff_size=27;//staff_size不是常量表达式
const int staff_size=27;//staff_size是常量表达式
const int sz=get.size();//sz不是常量表达式  sz具体值只有在运行时才能获得
constexpr变量

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

constexpr int mf=20;//20是常量表达式
constexpr int sz=size();//只有当size是一个constexpr函数时,才是一条正确的声明语句
字面值类型

常量表达式的值需要在编译时就得到计算,因此对声明constexpr时用的类型(字面值类型:算术类型、引用、指针)必须有所限制。

引用和指针都可以被定义成constexpr类型,但它们的初始值却受到严格限制。一个constexpr指针的初始值必须为nutllptr或0,或者存储于某个固定地址中的对象

注:定义在函数体内部的变量并非存放在固定地址中,因此constexpr指针不能指向这样的变量;定义在函数体外部的变量其地址固定不变,能用来初始化constexpr指针。除此,C++还允许函数定义一类有效范围超过函数本身的变量,这类变量和定义在函数体之外的变量一样有固定地址,因此,constexpr引用能绑定到这样的变量上,constexpr指针也能指向这样的变量。

指针和constexpr

在constexpr声明中若定义一个指针,限定符constexpr仅对指针有效,与指针所值对象无关。constexpr把它所定义的对象设置为了顶层const。constexpr指针既可以指向常量也可以指向一个非常量。

const int *p=nullptr;//指针常量,p是一个指向整数常量的指针,p可变,p所指对象内容不可以改变
constexpr int *q=nullptr;//常量指针,q是一个指向整数的常量指针,q不可变,q所指内容可以改变

int j=0;
constexpr int i=42;//i  j 定义在函数体外
constexpr const int *p1=&i; //p1常量指针,指向整型常量i
constexpr int *p2=&j;//p2常量指针,指向整数j

2.5 处理类型

2.5.1 类型别名

两种方法定义类型别名:typede和using

typedef double wages;//wages是double的同义词
using SI=Sales_item;//SI是Sales_item的同义词
指针、常量和类型别名

若某个类型别名指代的是复合类型或者常量,那么把它用到声明语句中就会产生意想不到的后果。例如下面的声明语句用到了类型pstring,他实际上类型char *的别名:

typedef char *pstring;
const pstring cstr=0;//cstr是指向char的常量指针
const pstring *ps;//ps是一个指针,它的对象是指向char的常量指针

正确理解是:pstring实际上是指向char的指针,所以const pstring就是指向char的常量指针,而非指向常量字符的指针。!!!!注:遇到类型别名的声明语句时,人们往往会错误地尝试把类型别名替换成本来的样子,如下:

const char *cstr=0;//是对const pstring cstr的错误理解

!!!这种理解是错误的,声明语句中用到了pstring,其基本数据类型是指针,但是用char*重写了声明语句后,数据类型变成了char,*成为了声明符的一部分,改写后声明了一个指向const char的指针。!!!!

2.5.2 auto类型说明符

C++11标准引入auto类型说明符,用它让编译器自己去分析表达式所属的数据类型。

auto能在一条语句中声明多个变量,所有变量的初始基本数据类型必须一样。

auto i=0,*p=&i;//正确:i是整数,p是整型指针
auto sz=0,pi=3.14;//错误:sz和pi的类型不一样
复合类型、常量和auto

auto一般会忽略顶层const,同时底层const则会保留下来。

int i=0;
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是一个指向整数常量的指针(底层const)

若希望推断出的auto类型是一个顶层const,则需要明确指出:

const auto f=ci;//ci是int(忽略了顶层const),f是const int

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

auto &g=ci;//g是一个整数常量引用,绑定到ci
auto &h=42;//错误:不能将非常量引用绑定到字面值
const auto &j=42;//正确:可以为常量引用绑定到字面值

2.5.3 decltype类型指示符

若希望从表达式的类型推断出要定义的变量的类型,但是不想用该表达式的值初始化变量。为了满子这一要求,C++11新标准引入了decltype类型说明符,它的作用是选择并返回操作数的数据类型。

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

编译器并不实际调用函数f,而是使用当调用发生时f的返回值类型作为sum的类型。

decltype和引用

decltype的结果类型与表达式形式密切相关。 !!!注:对于decltype使用的表达式来说,若变量名加上一对括号,则得到的类型和不加括号时会有不同 。decltype((variable))(注意时双层括号)的结果永远是引用,而decltype(variable)结果只有当variable本身是引用时才是引用。

decltype((i)) d;//错误:d是int&,必须初始化
decltype(i) e;//正确:e是一个(未初始化的)int
预处理器概述

确保头文件多次包含仍能安全工作的常用技术是预处理器,他由C++语言从C语言继承而来。预处理器是编译之前执行的一段程序,可以部分地改变我们所写的程序。#include就具有预处理功能,当预处理器看到#include标记时就会用指定的头文件的内容代替#include

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

#ifndef PROPROCESSOR_H
#define PROPROCESSOR_H
#include<string>

struct preprocessor
{
    /* data */
};

#endif

第一次包含preprocessor.h时,#ifndef的检查结果为真,预处理器将顺序执行后面的操作直至遇到#endif为止。此时PROPROCESSOR_H的值变为已定义,而且preprocessor.h也会拷贝到我们的程序中。若后面再一次包含preprocessor.h,则**#ifndef的检查结果为假,编译器会忽略#ifndef和#endif**之间的部分。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值