2.1.1算术类型
选择算术类型的原则:
1.若已知数值不为负,用unsigned类型
2.用int执行整数运算,用double执行浮点数运算
补充:
unsigned char表示0~255区间内的值
signed char表示-127~127之间的值
课后问题2.1.1
1.int、long、long long和short的区别:
int保证字节最少达到short,最大达到long,longlong最大
无符号类型和有符号类型的区别:
无符号类型能保存有符号类型数据的2倍(如补充中所述)
float和double的区别是什么:
float一般4个字节,double一般8个字节,后者表示范围比前者大
2.1.2类型转换
1.把非布尔运算的值给布尔运算,初始值非0为true,0则为false
bool a=42;//a为true
2.把布尔运算的值给整型int,true为1,false为0
3.把小数赋给int,则近似取整
int a=3.14//a的值为3
4.把整数赋给double,则保留整数部分,留一位小数
double b=3;//b的值为3.0
5.赋给无符号类型一个超出它表示范围的值,结果为初始值对无符号类型表示数值总数取模后的余数(负数加上数值总数的模)
unsigned char c=-1;//c为255
附:
求模运算和求余运算在第一步不同: 取余运算在取c的值时,向0 方向舍入(fix()函数);而取模运算在计算c的值时,向负无穷方向舍入(floor()函数)。
例1.计算:-7 Mod 4
那么:a = -7;b = 4;
第一步:求整数商c:
①进行求模运算c = [a/b] = -7 / 4 = -2(向负无穷方向舍入),
②进行求余运算c = [a/b] = -7 / 4 = -1(向0方向舍入);
第二步:计算模和余数的公式相同,但因c的值不同,
①求模时:r = a - c*b =-7 - (-2)*4 = 1,
②求余时:r = a - c*b = -7 - (-1)*4 =-3。
6.给带符号类型赋予一个超出它表示范围的值时,结果未知
2.1.3字面值
'c'为字符字面值,“abdf”为字符串字面值,其中字符串字面值还在字符串末端包含了空字符'\0',所以字符串实际长度为4+1。
还有整型和实型字面值
布尔字面值为true和false
指针字面值为nullptr
字面值能够实现强制类型转换:
仅列举常用的,
字符串转为字符:前缀加u8,如u8"hi!"
char转为wchar_t,如L'a'
整型字面值后缀U,转为unsigned;后缀L,转为long;后缀LL,转为long long
浮点型后缀F或f,转为float;后缀L,转为long double
附:科学计数法:
3*10E-2表示0.03
3.14e1表示把小数点向右移一位,即31.4
2.2变量
2.2.1
初始化的四种形式
int a=2;
int a={2};
int a{2};
int a(2);
特别地,对于long double
long double ld=3.1415926;
int a{ld};//错误,转换未执行,因为存在丢失信息(小数点后数字均被抹去)的风险
int b(ld);//正确,小数点后数字均被抹去,结果为3
默认初始化:
如果在定义变量时没有指定初值,那么变量会被默认初始化。
三条性质:
1、定义在任何函数体外的变量会被初始化为0。(string类型的字符串初始化为空字符串)
2、定义在函数体内部的变量不会被初始化。
3、类的对象未被初始化,则初值由类决定。
建议初始化每一个内置类型的变量
2.2.2
重点:声明和定义的区别:
extern int a;//声明,不占用内存,只说明变量的数据类型和名字,表示在某个文件里有该变量的定义
int a;//定义,默认初始化为0,占用内存
int a=2;//定义,初始化为2,占用内存
1.声明可以多次,定义只能一次。声明告诉编译器变量的名字和数据类型以便使用,但没有提供内存空间给该变量,因为默认在某个地方已经定义了变量。
2.声明用extern,且不能初始化。若extern加上初始化则会抵消作用,即仍为定义。
3.在函数体内部,试图初始化一个用extern关键字标记的变量,将会引发错误
2.2.3标识符
变量命名规范:
1.类名第一个字母大写
2.变量名一般用小写字母,表示不同含义的单词之间用下划线隔开,变量名需表示实际含义
3.不能下划线接大写字母,不能连续两个下划线,不能数字开头
2.2.4引用和指针(无论是引用还是指针,都必须指向同一类型的)
int a=20;
int& b=a;
引用:
引用,就是给变量a取一个别名b,对b的所有操作就是对a的所有操作,引用不是对象,不能引用一个值,只能引用一个变量,且一旦引用不能更改引用的目标。
使用引用时,引用的变量a必须要已经初始化了,因为引用会把变量名和初始值与别名相绑定,否则会报错。
指针:
指针是对象,能够赋值和计算,无需定义时赋值
特别地,有空指针和void*指针
空指针:
int* ptr1=nullptr;//nullptr是能够把指针赋值为空的字面值
int* ptr2=0;//也能够直接赋值0使其变为空指针
void*指针:
可以存放任何对象的地址,但不能访问地址指向的对象,可以使用void*指针进行赋值,作为函数的输入或输出,或者与其他指针比较,更像是一个存储空间的具象化
注意以下操作:
int i=0;
double* dp=&i;//错误,不同类型
int* ip=i;//错误,不能给指针赋值变量
int* ip=0;//正确,指针赋值可以为字面值
int* ip=&i;//正确
//已知p为指向int的指针
if(p)表示判断p是否为空指针
if(*p)表示判断p指向的对象是否为0
扩展:
指向指针的指针
int i=2;
int *p1=&i;
int **p2=&p1;
cout<<"direct value is "<<i<<endl;
cout<<"indirect value is "<<*p1<<endl;
cout<<"doubly indirect value is "<<**p2<<endl;
//输出结果均为2
指针的引用
int i=2;
int *p1=&i;
int *&p2=p1;
cout<<"direct value is "<<i<<endl;
cout<<"indirect value is "<<*p1<<endl;
cout<<"doubly indirect value is "<<*p2<<endl;
//结果均为2
对于指针的定义,从右往左读:如&p2表示是名为p2的引用,*表示引用的是指针,int表示指针指向int
2.4const
const int a=20;
a=30;//错误,const设置的变量不可改变值
int b=20;
const int c=b;//正确,可以用变量赋值给const变量
const用来设置不可改变的变量,但使用const时必须要初始化,否则报错
const只能在单个文件内共享,即若同一个const类型的变量出现在不同文件,视作不同文件独立的const变量,该const变量需要每个文件中单独定义。如果想要在多个文件之间共享一个const类型变量,即在一个文件中定义该变量,其他文件声明即可使用,则需要使用extern关键词:
extern const a=20;//程序file_1.cc中对于a的定义
extern const a;//程序file_1.h头文件中对于a的声明
2.4.1const的引用
int a=1;
const int b=2;
//const变量
const int c=1;//正确,可以用字面值给常量赋值
const int d=a;//正确,可以用变量给常量赋值
int e=b;//正确,可以用常量给变量赋值
c=2;//错误,不能改变c的值
a=2;
cout<<d<<endl;//此时d仍为1,因为初始化的时候常量就已经定了
//const引用
const int&e=1;//错误,引用必须给对象“取别名”
const int&f=a;//正确,a是对象
//不能直接修改f的值,但是可以修改a的值变相修改f的值
a=5;
cout<<f<<endl;
//f为5
const int&g=b;//正确
int&h=b;//错误,不能用非常量引用 引用常量,否则能够用h需改b的值
//const和指针
注意:
double val=3.14;
const int&a=val;
cout<<a<<endl;//a为3
注:只有引用能用double 赋值给const int&,指针不行(指针必须const int*对应int)
在以上过程实际为如下:
double val=3.14;
const int temp=val;//编译器为执行整数运算,创建临时量temp
const int&a=temp;
cout<<a<<endl;//a为3
创建了一个临时值来容纳val
2.4.2指针和const
//指向常量的指针
const double a=3.14;
double b=3.14;
double *p1=&a;//错误,不能通过p1修改a的值,故不能赋值
const double *p2=&a;//正确,且不能修改p2和*p2、a的值
const double *p3=&b;//正确
*p2=5;//错误
b=5;//*p3和b均为5,(只是不能通过指针去修改值,不代表指针指向的值不能变)
//常量指针
int a1=0;
int b1=2;
int *const ptr1=&a1;//ptr1为常量指针
ptr1=&b1;//错误,ptr1为常量,不能改
const double a=3.14;
const double *const ptr2=&a;//正确,ptr2为指向常量的常量指针
注意区分指向常量的指针和常量指针:
常量指针为double*const ptr2,从右往左读先是常量const,然后是*表示指针,再是double表示指针指向的类型
指向常量的指针为const double* ptr1,从右往左读先是*指针,然后是指针指向double,最后表示指向的double是常量(表明不能通过指针修改指针指向的值)
const double a初始化可容纳一切值,包括const和非const的变量和表达式
但double a初始化用同类型的double,不能包含const
const double *p初始化可容纳一切值
但double *p初始化只能用同类型的double
(注意const int &r = 0 是正确的,但是int &r = 0是不正确的)
2.4.3顶层const与底层const
顶层const的拷贝不受限制,但是底层const的拷贝的对象必须具有相同的底层const资格。一般来说:非常量可以赋值给常量,反之则不行
2.4.4constexpr和常量表达式
常量表达式(字面值类型):编译时就已经能知道结果的表达式,即常量;
包括算术类型、指针和引用,不包括类、IO库、string类
constexpr:一种变量类型,可由编译器来验证变量的值是否为常量表达式,若不是,则报错
注:由于函数体内的变量不是储存在固定地址,所以不能用来初始化constexpr指针,
函数体外的变量储存在固定地址,所以能用来初始化constexpr指针
//常量表达式
const int a=0;//是常量表达式
const int b=a+1;//是常量表达式
int c=2;//不是常量表达式
const int d=getsize();//不是常量表达式,因为函数执行完才能确定返回值
//constexpr
constexpr int a1=0;//正确
constexpr int b1=a1+1;//正确
constexpr int d2=getsize();
//若getsize()返回值是常量,即getsize()为constexpr函数时,才能执行该语句,否则会报错
constexpr与指针
constexpr int *p1=nullptr;//p1为常量指针,值为空
int j=2;
const int *p2=&j;//p2为指向常量的指针
constexpr const int *p3=&j;//p3为指向常量的常量指针
2.5处理类型
2.5.1类型别名
//第一种定义类型别名的方法
typedef double wage;//给double取类型别名为wage
wage base,*p;//创建变量base和*p,变量类型为double
//第二种定义类型别名的方法
using wage = double;//C++11标准
wage base,*p;
//也可以定义类
using SI=Sales_item;
SI item1;
//特别地,对于指针:
typedef char *pstring;
const pstring cstr=0;//cstr为指向char的常量指针
const pstring *ps;//ps是指针,指向另一个指针(指向char的常量指针)
注意:对于typedef用于指针的情形,不能单纯代入之后解读(如const char* cstr,认为cstr是指向const char的指针,这样是错误的),而是把类型别名当作一个整体(pstring是指向char的指针,cstr为指针类型,然后还是const)
2.5.2auto类型说明符
auto类型能够通过编译器由初始化的表达式或值自动推算变量类型,也因此auto必须要有初始值
//auto与变量
auto a=3;//auto此时的类型为int
auto b=3.14//auto此时的类型为double
auto i=5,*p=&i;//i为int类型,p为指向int的指针
auto i2=5,*p=&b;//错误,i2与*p类型不一致
//auto与引用
int i=5;
auto &r=i;//r为int类型的引用
//auto与const
const int ci=2;//ci为顶层const
auto c=ci;//c为int类型,不是const int类型
const auto d=ci;//d为const int 类型,不能修改d的值
//auto与const与引用
const int m1,&r1=m1;
auto r2=r1;//r2为int类型,该语句等价为auto r2=m1,忽略了顶层const
auto &r3=m1;
auto &r4=30;//错误,不能用非const引用绑定字面值
const auto &r5=30;//正确,const引用可以绑定字面值
//auto与const与指针
int c1=3;
const int c2=3;
auto p1=&c1;//p1为指向c1(int类型)的指针
auto p2=&c2;//&c2为底层const,因此p2为指向c2(cosnt int类型)的指针
const auto p3=&c1;//p3为指向int的常量指针,不能修改p3的值
auto const p3=&ci;//与上语句等价,const与auto次序无关
2.5.3decltpe类型指示符
decltype能够识别表达式的最终类型,而不返回表达式的值
int a=3,&b=a;
const int c=2;
decltype (a) d=a;//c为int类型
decltype (b) e=a;//b为int&类型
decltype (c) f=c;//f为const int类型
//括号表达式和赋值表达式的最终类型为引用
decltype (c=a) g=a;//g为int&类型
decltype ((a)) h=a;//h为int&类型
//如果想要取消引用,可以通过用表达式的方法
decltype(b+0) j;//j为int类型
decltype(a+b) k;//k为int类型
//auto和decltype
auto l=a;
decltype(a)m=a;
//l与m均为int
auto n=b;
decltype(b) o=b;
//n为int类型,o为int&类型
auto和decltype的不同点就在于顶层const和引用上:
1.auto无法识别顶层const和引用,只能识别对应的数据类型,如:const int和int&都只能识别int
2.decltype能够识别顶层const和引用
2.6自定义数据结构
1.在main函数里自定义数据结构,如类等(注意:struct定义的数据类型为public型)
//自定义类
#include<iostream>
#include<string>
int main()
{
stuct Sales_data
{
string isbn;
unsigned int sold=0;
double price=0.0;
};//注意分号
//后面为其他操作
Sales_data data1,data2,data3;
cin >> data1.isbn >> data1.sold >> data1.price;
cin >> data2.isbn >> data2.sold >> data2.price;
if (data1.isbn == data2.isbn)
{
data3.sold=data1.sold + data2.sold;
data3.price = data1.price;
data3.isbn = data1.isbn;
}
cout << data3.isbn << data3.sold << data3.price << endl;
}
2.在头文件里自定义
注意:#include<iostream>中符号<>表示该头文件在库里
#include"Sales_data.h"中符号""表示头文件是自己定义的
为了防止该情况:头文件中有变量的定义,且多次引用头文件,导致在一个程序中多次定义变量引发错误,我们使用预处理变量解决:
在该头文件中,ifndef、define、ifdef均为头文件保护符
ifndef作用是判断该预处理变量是否已被定义,若未定义,则为真
ifdef作用是判断该预处理变量是否已被定义,若已定义,则为真
define作用是定义预处理变量
对该头文件进行解释:判断预处理变量SALES_DATA_H有没有被定义过,若没有,则定义变量SALES_DATA_H,直到endif结束;
即该做法防止了多次定义预处理变量,预处理变量只定义一次,即Sales_data只定义一次
//头文件Sales_data.h的内容
#ifndef SALES_DATA_H
#define SALES_DATA_H
#include <string>
struct Sales_data
{
std::string isbn;
double price;
unsigned int sold;
};
#endif // !SALES_DATA_H
//主程序中的内容
#include<iostream>
#include"Sales_data.h"
using namespace std;
int main()
{
Sales_data data1,data2,data3;
cin >> data1.isbn >> data1.sold >> data1.price;
cin >> data2.isbn >> data2.sold >> data2.price;
if (data1.isbn == data2.isbn)
{
data3.sold=data1.sold + data2.sold;
data3.price = data1.price;
data3.isbn = data1.isbn;
}
cout << data3.isbn << data3.sold << data3.price << endl;
}