第3章 对象与基本类型
第1节 初始化/赋值语句第2节 类型详述
类型描述类型种类划分与类型相关的标准未定义部分变量 第3节 复合类型:从指针到引用
指针
指针特点相关操作符指针的定义`void*`指针指针的指针指针v.s.对象指针的问题: 引用
指针的引用 第4节 常量类型与常量表达式
常量常量指针与顶层常量(top-level const)常量引用常量表达式 第5节 类型别名与类型的自动推导
类型别名类型的自动推导 第6节域与对象的声明周期
第3章 对象与基本类型 第1节 初始化/赋值语句初始化的基本操作:
在内存中开辟空间(堆或栈),保存相应的数值在编译器中构造符号表,将标识符与相关内存空间关联
值与对象都有类型
第2节 类型详述类型是编译期概念,可执行文件中没有数据类型的概念
C++是强类型语言
引入类型是为了更好的描述程序,防止误用
类型描述存储需要的尺寸(sizeof ,标准并没有严格限制)(一种类型对应的是汇编语言中一条简单的易处理的语句,汇编语句与程序的硬件相关)取值空间(std::numeric_limits ,超过范围可能溢出,上溢出变为最小值,下溢出变成最大值)
std::cout< 对齐信息 (alignof(int)查看对齐信息)CPU读取内存中数据过程经过cache缓冲区,一次读取的大小为cache的大小,读取的地址为cache的整数倍,以int为例,如果在内存中存储的地址为7999 - 8002,那么在读取8000 - 8063后数据没有读取完整,会再次读取8000-64 - 7999段的数据,所以系统存储数据时会按照数据类型的对其信息存储,如8000 - 8003(数据类型的对其信息的整数倍),由于结构体内变量的对齐信息,结构体所占长度不一定等于所有变量长度加和。
例如Str的长度为8而不为5 可执行的操作
类型种类划分
基本(内建)类型:C++语言中支持的类型
数值类型
字符类型:char,wchar_t,char16_t,char32_t整数类型
带符号整数 short,int,long,long long无符号整数 unsigned + 带符号整数 (unsigned == unsigned int) 浮点类型 float,double,long double void 复杂类型:由基本类型组合、变种所产生的类型,可能是标准库引入或类型定义。
与类型相关的标准未定义部分
char是否有符号,取决于编译器
可以通过定义unsigned char/signed char定义有符号和无符号 整数在内存中的保存方式:大端、小端(两台机器之间传输数据时要考虑,解析时要一致) 每种类型的大小 C++11中引入固定尺寸整数类型,如int32_t
字面值:在程序中直接表示为一个具体的数值或字符串的值 每个字面值有其类型 整数:20(十进制),024(八进制),0x14(十六进制) int型 浮点数: 1.3 1e-8 double型 字符: ‘c’ ‘n’ char型 字符串:“hello” char[6]型(字符串后隐式加上 表示字符串结束) 布尔:true false boor型 指针:nullptr nullptr_t型 (可以引入自定义后缀改变字面值类型 User-defined literals) 对应一段存储空间,可以改变其中内容 变量类型在首次声明(定义)时指出: int x;定义extern int x;不同编译端的声明 (如果在声明时赋值,系统会认为是定义)
变量的初始化和赋值 初始化:构造变量之初赋予值
缺省初始化(不赋值)如果变量缺省初始化在函数内,则赋予一个随机值,如果时全局变量,缺省初始化赋值为0。因为函数内定义变量时,多数要对变量进行操作,变量的初始化赋值是不必要的,为提升效率默认在初始化时不赋值,内存中为上一次程序在此处内存中存储的值。全局变量的初始化是在整个程序开始时只执行一次,代价较小,所以为保证程序每次执行的一致性,默认为初始变量缺省赋值为0;直接/拷贝初始化 直接:int x(10); 拷贝:int x = 10;
变量赋值时可能设计类型的转换(高精度转为低精度时可能有损) bool与整数之间转换 bool x = 1;(为0则为false,非0为true),bool转为整数对应0和1浮点与整数之间
隐式类型转换 if 判断数值比较
无符号数与带符号数进行比较时,会把带符号数转化为无符号数可以使用std::cmp_XXX(C++ 20) 比较,满足数学定义。
第3节 复合类型:从指针到引用
指针
一种间接类型 指针表示存储变量的首个字节的地址 指向不同对象具有相同的尺寸(各种类型的指针,在64位机上通常大小都为8个字节)
(64位机 总线长度是64个字节,一次性处理64个字节,内存最大为264) 取地址&解引用*
C语言中 NULL==0,函数内尽可能避免地址缺省初始化,可赋值为0 nullptr 一个特殊类型的对象,类型为nullptr_t,表示空指针类似于C中的NULL,但更安全
函数重载 指针与bool类型的隐式转换,if(p)语句中如果p指向0(空指针)地址则转换为false,否则为true,等同于if(p!=nullptr) 指针可以增加和减少,指针+1表示增加一个对应类型所占长度 指针还可判等和比较,当两个指针分别指向不同变量时不建议比较两个指针,因为所分配的地址不固定,与在堆或栈中建立有关。 没有记录对象的尺寸信息,可以保存任意地址(因为指针的尺寸是相同的,等同于硬件总线长度,所以可以声明一个占位的指针,并与其他类型的指针转化)(由于没有类型信息,故不可以加减)支持判等操作
指针的指针
指针为间接调用,可以避免直接调用产生的数据传输。复制成本较低,读写成本较高。 可以为空地址信息可能非法解决方案:引用
引用
int& ref = val;是对象的别名,不能绑定字面值在构造时绑定对象,在其生命周期内不能绑定其他对象(赋值操作会改变对象内容)不存在空引用,但可能存在非法引用(初始化为一个临时变量如函数内的变量)——总的来说比指针安全(作为函数的入口参数)
属于编译期概念,在底层还是通过指针实现
指针的引用
指针也是对象,可以定义引用
第4节 常量类型与常量表达式
常量
与变量相对,表示不可修改的对象 使用const声明常量 是编译期概念,编译器利用常量 防止非法操作优化程序逻辑 int* const ptr = &x; 常量指针,表示指针不能被修改const int* ptr = &x; 表示不能通过ptr修改所指向地址处的值
区分:如果const出现在*右边,则表示指针不能被修改,如果const出现在*左边,则表示指针指向的内容不能被修改 解引用: const int& ref = x;
主要用于函数的形参
常量引用可以绑定字面值(为保证常量引用做函数形参时,使用字面值调用函数)
常量表达式
C++11开始 使用constexpr来声明 (不是一种类型,是一种类型的限定符)声明是编译期概念编译器可以利用其进行优化常量表达式指针
第5节 类型别名与类型的自动推导
类型别名
可以为类型引入别名,从而引入特殊的含义或便于使用(如size_t)同一种类型在不同的硬件中的长度可能不同,为了避免同一程序在不同硬件上执行的一致性 两种引入类型别名的方式 typedef int MyInt;using MyInt = int;(C++11开始)
使用using方式更好 typedef char MyCharArr[4];using MyCharArr = char[4];
类型别名与指针、引用的关系 应将指针的类型别名视为整体,在此基础上引入常量指针表示指针为常量的类型不能构造引用的引用
类型的自动推导
auto(C++11开始)从初始化表达式自动推导变量的类型 并不意味着弱化对象类型(python弱对象类型语言) 几种常见形式 auto x = 6.5; 会产生类型退化 类型退化:当变量放在等号右侧时,变量的类型会发生改变。典型的类型退化有引用、常量等(int& ref = x; int y = ref;此处的ref的类型会退化成int类型。const int x = 3; int y = x;) auto&可以避免类型的退化decltype(exp); 返回表达式的类型(类似于auto,但不会产生退化)(对一般意义表达式左值加引用) decltype(val); 返回val的类型 decltype(auto) x = 3.5 + 15l;等同于下面的 decltype(3.5 + 15l) x = 3.5 + 15l;C++14开始 concept auto 域(scope)表示程序中的一部分,其中名称有唯一的含义 全局域(global scope):程序最外围的域,其中定义的是全局变量 块域(block scope): 使用大括号所限定的域,其中定义的是局部对象 还存在其他的域:类域,名字空间域 域可以嵌套,嵌套域中定义的名称可以隐藏外部域中定义的名称 对象的生命周期起始于被初始化的时刻,终止于被销毁的时刻 全局对象的生命周期是整个程序的运行期间局部对象的生命周期起源于被初始化位置,终止于所在域被执行完成struct Str{
int x;
char y;
};
float x = 1.3; //double转为float
unsigned long long y = 2ULL; //int转为unsigned long long
int operator "" _ddd(long double x)
{
return (int)x*2;
}
int x = 3.14_ddd;
变量
int* p = &x; //*表示声明变量p是一个指针
int y = *p; //*表示解引用,访问指针所指向的变量
指针的定义
int main(){
int* p = &x;
int* p(&x);
int* p; //函数内缺省初始化,存放随机值(原内存中的数据被解析成一个地址)
}
int* p; //函数外缺省初始化,初始化为0,0地址特殊,不能被解引用
int* p = nullptr;
fun(int){} //1
fun(int*){} //2
//函数调用时会自动选择输入参数类型匹配的函数
int* p;
fun(0); //调用1
fun(p); //调用2
fun(nullptr) //调用2
int x = 42;
int* p = &x;
p=p+1;
struct Str
{
//.....这是一个较大的结构体
};
void fun(Str param)
{
}
int main(){
Str x;
fun(x); //这种情况会产生较大的数据传输
}
struct Str
{
//.....这是一个较大的结构体
};
void fun(Str* param)
{
}
int main(){
Str x;
Str* p = &x;
fun(p); //这种情况可以避免大数据传输,因为指针的大小为固定的8个字节(硬件总线决定)
}
指针的问题:
void fun(int& param){
//.......
}
int x = 3;
if(x = 4){ //如果此处错写,程序执行完全不同,为避免要const x = 3;
//....
}
const int x = 3;
//......
int y = x + 1; //对比int x = 3;编译器默认x值不变,直接赋值y=4,减少一次读x操作
常量指针与顶层常量(top-level const)
int* ptr1;
*ptr1; //int类型
int* const ptr2;
*ptr2; //int类型
const int* ptr3;
*ptr3; //const int类型
常量引用
int x = 3;
const int& ref = x; //加强限制,只能读不能写
const int x = 3;
int& ref = x; //放松限制,报错
struct Str{
//.....
};
void fun(const Str& param){
//可以避免大量拷贝(引用),同时防止数据被写改变(常量引用)
}
int main(){
Str x;
fun(x);
}
int x = 3;
int* ptr = &x;
decltype(x); //变量 不加引用 返回int
decltype(x); //表达式 左值 加引用 返回int&
decltype(*ptr); //表达式 左值 加引用 返回int&
decltype(3.5+5l); //表达式 右值 不加引用 返回double



