第二章 变量和基本类型
数据类型是程序的基础:它告诉我们数据的意义及我们能在数据上执行的操作
2.1 基本数据类型
C++定义了一套包括算术类型和空类型在内的基本数据类型
2.1.1 算术类型
算术类型:
- 整型
- 浮点型
布尔类型的取值是真或者假
带符号类型和无符号类型:
除去布尔型和扩展的字符之外,其他整型可以划分为 带符号的和无符号的。带符号的可以表示正数、负数、0,无符号类型仅可以表示大于等于0的值。- 类型 int、short、long、和 long long都是带符号的
- 类型 unsigned int、unsigned short、unsigned long和 unsigned long long都是无符号的
如何选择类型
- 当明确知晓数值不可能为负时,选用无符号类型
- 使用int执行整数运算。如果数值超过int的数值范围,则选用long long
- 在算术表达式中不要使用char或bool,只有在存放字符或者布尔值时才使用他们
- 执行浮点数运算选用double
2.1.2 类型转换
对象的类型定义了对象能包含的数据和能参与的运算,其中一种运算被大多数类型支持,就是将对象从一种类型转换为另一种类型。
类型所能表示的值的范围决定了转换的过程:
- 当我们把一个非布尔类型的算术值付给布尔类型时,初始值是0 则结果为false,否则结果为true
#include <iostream>
using namespace std;
int main()
{
int intVal=12;
bool boolVal=intVal;
cout<<"boolVal:"<<boolVal<<endl;
return 0;
}
结果:
boolVal:1
- 当我们把一个布尔值赋给非布尔类型时,初始值为false 则结果为0,初始值为true 则结果为1
#include <iostream>
using namespace std;
int main()
{
bool boolTrueVal=true;
bool boolFalseVal=false;
int intTrueVal=boolTrueVal;
int intFalseVal=boolFalseVal;
cout<<"intTrueVal:"<<intTrueVal<<endl;
cout<<"intFalseVal:"<<intFalseVal<<endl;
return 0;
}
结果:
intTrueVal:1
intFalseVal:0
- 当我们把一个浮点数赋给整数类型时,进行了近似处理。结果值将仅保留浮点数中小数点之前的部分。
#include <iostream>
using namespace std;
int main()
{
double doubleVal=3.1415926;
int intVal=doubleVal;
cout<<"intVal:"<<intVal<<endl;
return 0;
}
结果:
intVal:3
- 当我们把一个整数值赋值给浮点类型时,小数部分记为0。如果该整数所占的空间超过了浮点类型的容量,经度可能有损失。
#include <iostream>
using namespace std;
int main()
{
int intVal=120;
float doubleVal=intVal;
cout<<"doubleVal:"<<doubleVal<<endl;
return 0;
}
结果:
doubleVal:120
这地方在输出结果的时候给格式化了
- 当我们赋给无符号类型一个超出它表示范围的值时,结果是初始值对无符号类型表示数值总数取模后的余数。
#include <iostream>
using namespacestd;
int main()
{
//unsigned int的最大值为 4294967296
unsigned int intVal;
intVal = 9999999999999999999;
cout << "intVal:" << intVal << endl;
//取模
unsigned intMod = 9999999999999999999 % 4294967296;
cout << "intMod:" << intMod << endl;
return 0;
}
结果:
intVal:2313682943
intMod:2313682943
- 当我们赋给带符号类型一个超出它表示范围的值时,结果是错误值。(此处应注意)
#include <iostream>
using namespace std;
int main()
{
int intVal=9999999999999999999;
cout<<"intVal:"<<intVal<<endl;
return 0;
}
结果:
intVal:-1981284353
2.1.3 字面值常量
一个形如42的值被称作字面值常量。
- 整型和浮点型字面值
20 //十进制
024 //八进制
0x14 //十六进制
- 字符和字符串字面值
'a' //字符字面值
"Hello world!" //字符串字面值
- 转义序列
名称 | 字符 | 名称 | 字符 | 名称 | 字符 |
---|---|---|---|---|---|
换行符 | \n | 横向制表符 | \t | 报警(响铃)符 | \a |
纵向制表符 | \v | 退格符 | \b | 双引号 | \" |
反斜杠 | \ | 问号 | \? | 单引号 | \’ |
回车符 | \r | 进纸符 | \f |
- 指定字面值类型
L'a' //字符串类型字面值,类型是wchar_t
u8"hi" // utf-8字符串字面值
42ULL //无符号整型字面值,类型是unsigned long long
- 布尔字面值和指针字面值
//布尔字面值是 true 和 false
bool trueVal=true;
bool falseVal=false;
//nullptr是指针字面值(指针模块)
int* pIntVal=nullptr;
2.2 变量
变量提供一个具名的,可供程序操作的存储空间。
个人理解就是:有很多存放不同物品的仓库,我把每个仓库都取上名字。
2.2.1 变量的定义
变量定义的基本形式:类型说明符 由一个或多个变量名
int sum=0,value=0,units_sold=1; //sum value和units_sold都是int
Sales_item item; //item类型是Sales_item
- 初始值
//price先被定义并赋值,随后被用于初始化discount
double price=109.99,discount=price*0.16;
//age 先定义 然后赋值
int age;
age=10;
- 列表初始化
int units_sold=0;
int units_sold={0};
- 默认初始值
如果定义变量时没有指定初值,则变量被默认初始化,此时变量被赋予了默认值。
全局变量在声明时会把初始化;局部变量在声明时不会被初始化
2.2.2 变量声明和定义的关系
- 声明:使得名字为程序所知。(例如:做饭的时候需要有汤勺,锅,刀等等。只是有这些东西,用不用另说)
- 定义:负责创建于名字关联的实体。(例如:做饭的时候使用到的汤勺,锅等等)
2.2.3 标识符
C++的标志符由 字母、数字、下划线组成,其中必须以字母或下划线开头。
- 变量命名规范
- 标志符要能体现实际含义。
- 变量名一般用小写字母,如index,不要用Index或者INDEX。
- 用户自定义类型一般以大写字母开头,如Sales_item。
- 如果标识符由多个单词组成,则单词间应有明显区分,如student_loan或studentLoan。
对于命名规范来说,若能坚持,必将有效
命名规范不是c++语言硬性规定的,只是在写代码的时候便于识别和理解的约定,团队可以根据情况自行规定风格。可以参考 Google、微软等大公司的风格。
- c++关键字
- C++操作符替换名
2.2.4 名字的作用域
作用域是程序的一部分,在其中名字有其他特定的含义。C++语言中大多数作用域以花括号分隔。(说的不明不白的)
个人理解:作用域就是名字的使用范围。例如:学校里的班级,在这个学校里可以直接说哪个班,但是在学校以外就要指定学校名称+班级名称。
- 嵌套作用域
作用域能彼此包含,被包含的作用域成为内层作用域,包含着别的作用域的作用域称外层作用域
2.3 符合类型
符合类型是指基于其他类型定义的类型。
2.3.1 引用
引用 为对象起了另一个名字,引用类型引用另外一种类型。通过将声明符写成&d的形式定义引用类型,其中d是声明的变量名。
int iVal=1024;
int &refVal=iVal; //refVal指向iVal
int &refVal2; //报错:引用必须被初始化
- 引用即别名
引用并非对象,相反的,它只是为一个已经存在的对象所取的另外一个名字。
对引用类型的所有操作都是在与之绑定的对象上进行的。
2.3.2 指针
- 指针是执行另外一种类型的符合类型。与引用类似,指针也实现了对其他对象的间接访问。
- 指针本身就是一个对象,允许对指针赋值和拷贝。与引用不同。
- 指针无需在定义时赋值。
- 获取指针的地址
&:取地址符
int iVal=20;
int *p=&iVal; //p 存放变量iVal的地址,或者说p指向变量iVal的指针
- 指针值
指针值应属于下列4中状态之一:
- 指向一个对象。
- 指向紧邻对象所占空间的下一个位置。
- 空指针,意味着指针没有指向任何对象。
- 无效指针,依旧是上述情况之外的其他值。
- 利用指针访问对象
*:解引用符(操作符*)
int iVal=40;
int *p=&iVal;//p存放着变量iVal的地址
cout<<"*p:"<<*p<<endl;//由符号*得到指针p所指向的对象
- 空指针
空指针:不指向任何对象的指针。
int *p1=nullptr;//等价与 int *p1=0;
int *p2=0;
//需要引入 cstdlib头文件
int *p3=NULL;
//上方三者等价
- 赋值和指针
int i=42;
int *pi=0; //pi被初始化,但没有指向任何对象
int *pi2=&i;//pi2被初始化,存有i的地址
int *pi3; // pi3被定义,值无法确定
pi3=pi2; //pi3和pi2指向同一个对象i
pi2=0; //pi2不指向任何对象
- void* 指针
void*是一种特殊的指针,可用于存放任意对象的地址。
double obj=3.14,*pd=&obj;
void *pv=&obj;//正确:void* 能存放任意类型对象的地址
pv=pd; //pv可以存放任意类型的指针
2.3.3 理解符合类型的声明
- 定义多个变量
int* p1,p2; //p1是指向int的指针,p2是int 容易产生误导
int *p1,p2; //推荐写法
- 指向指针的指针
int iVal=1024;
int *pi=&iVal; //pi指向一个int类型的数
int **ppi=π //pi指向一个int型的指针
- 指向指针的引用
int i=1024;
int *p; //p是一个int型指针
int *&r=p; //r是一个对指针p的引用
r=&i; //r引用了一个指针,因此给r赋值&i就是令p指向i
*r=0; //解引用r得到i,也就是p指向的对象,将i的值改为0
2.4 const限定符
const: 用const修饰的变量的值不能被改变。
- 初始化和const
int i=1024;
const int ci=i; //正确:i的值被拷贝给了ci
int j=ci; //正确:ci的值被拷贝给了j
2.4.1 const的引用
- 初始化和对const的引用
int i=1024; //定义i
const int &r1=i; //正确:允许将const int&绑定到一个普通int对象上
const int &r2=2048; //正确:r1是一个常量引用
const int &r3=r1*2; //正确:r3是一个常量引用
int &r4=r1*2; //错误:r4是一个普通的非常量引用
2.4.2 指针和const
- 指向常量的指针 即指针常量
const double pi=3.14;//pi是一个常量,它的值不能改变
double *ptr=π //错误:ptr是一个普通指针
const *cptr=π //正确:cptr可以指向一个双精度常量
*cptr=1024; //错误:不能给*cptr赋值
也就是说 指针保存的地址是能改变的,保存的地址指向的对象是不可以变的
- 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:指针所指的对象是一个常量
2.4.4 constexpr和常量表达式
- 常量表达式:值不会改变并且编译过程就能得到计算结果的表达式。
const int max_files=20; //max_files是常量表达式
const int limit=max_files+1; //limit是常量表达式
int staff_size=27; //staff_size不是常量表达式
const int sz=get_size(); //sz不是常量表达式
- constexpr变量
C++11新标准规定,运行将变量声明为constexpr类型以便由编译器来验证变量的值是否是一个常量表达式。
constexpr int mf=20; //20是常量表达式
constexpr int limit=mf+1; //mf+1是常量表达式
constexpr int sz=size(); //只有当size是 一个constexpr函数时,才是一个正确的声明语句
- 指针和constexpr
在constexpr生命中如果定义了一个指针,限定符constexpr仅对指针有效,与指针所指的对象无关。
const int *p = nullptr; //p是一个指向整型变量的指针
constexpr int *q = nullptr; //q是一个指向整数的常量指针
// p和q相差甚远,p是一个指向常量的指针,而q是一个指针常量,
// 其中关键字constexpr把它所定义的对象置为顶层const
2.5 处理类型
2.5.1 类型别名
类型别名 是一个名字,它是某种类型的同义词。
- 定义类型别名
- typedef:
typedef double wages; //wages是double的同义词 typedef wages base, *p; //base是double的同义词,p是double*的同义词
- using:
using SI=Sales_item; //SI是Sales_item的同义词
- 指针、常量和类型别名
typedef char *pstring;
const pstring cstr=0; //cstr是指向char的常量指针
const pstring *ps; //ps是一个指针,它的对象是指向char的常量指针
2.5.2 auto类型说明符
C++11新标准引入了auto类型说明符,用它就能让编译器替我们去分析表达式所属的类型。
int i=0,&r=i;
auto a=r; //a是一个整数(r是i的别名,而i是一个整数)
auto一般会忽略掉顶层const,同是底层const会保留下来
int i=0,&r=i;
const int ci=i,&cr=ci;
auto b=ci; //b是一个整数(ci的顶层const特性被忽略掉了)
auto c=cr; //c是一个整数(cr是ci的别名,ci本身是一个顶层const)
2.5.3 decltype 类型指示符
C++11新标准引入了第二种类型说明符declytype,它的作用是选择并返回操作数的数据类型
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&, y绑定到变量x
decltype(cj) z; //错误:z的类型是一个引用,必须初始化
- decltype和引用
int i=1024,*p=&i,&r=i;
decltype(r+0) b; //正确:加法的结果是int,因此b是一个(未初始化的)int
decltype(*p) c; //错误:c是int&,必须初始化
2.6 自定义数据结构
2.6.1 定义Sales_item
struct Sales_item
{
string bookNo;
unsigned int units_sold=0;
double revenue=0.0;
}salesItem,*salesItem;
2.6.2 使用Sales_item类
#include <iostream>
#include <string>
using namespace std;
struct Sales_item
{
string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
}saleItem,*p_SalesItem; //此处声明一个实例,和一个指针引用
int main()
{
saleItem.bookNo = "C++从入门到精通";
saleItem.units_sold = 100;
saleItem.revenue = 89;
cout << "bookNo:" << saleItem.bookNo<<"\t unit_sold:"<<saleItem.units_sold<<"\t revenue:"<<saleItem.revenue << endl;
p_SalesItem = new Sales_item();
p_SalesItem->bookNo = "Effective C++";
p_SalesItem->units_sold = 200;
p_SalesItem->revenue = 99;
cout << "bookNo:" << p_SalesItem->bookNo << "\t unit_sold:" << p_SalesItem->units_sold << "\t revenue:" << p_SalesItem->revenue << endl;
delete(p_SalesItem);
return 0;
}
2.6.3 编写自己的头文件
#define: 把一个名字设定为预处理变量
#ifdef : 当且仅当变量定义时为真(以 #endif为止)
#ifndef: 当且仅当变量未定义时为真(以 #endif为止)
#ifndef SALES_ITEM_H
#define SALES_ITEM_H
#include <string>
using namespace std;
struct Sales_item
{
string bookNo;
unsigned units_sold=0;
double revenue=0.0;
}saleItem,*p_saleItem;
#endif // SALES_ITEM_H
下面的头文件和上面的等价
#pragma once //此文件只引用一次
#include <string>
using namespace std;
struct Sales_item
{
string bookNo;
unsigned units_sold=0;
double revenue=0.0;
}saleItem,*p_saleItem;