初始化的基本操作:
值与对象都有类型
类型是编译期概念,可执行文件中没有数据类型的概念
C++是强类型语言
引入类型是为了更好的描述程序,防止误用
sizeof
,标准并没有严格限制)(一种类型对应的是汇编语言中一条简单的易处理的语句,汇编语句与程序的硬件相关)std::numeric_limits
,超过范围可能溢出,上溢出变为最小值,下溢出变成最大值)std::cout<
alignof(int)
查看对齐信息)CPU读取内存中数据过程经过cache缓冲区,一次读取的大小为cache的大小,读取的地址为cache的整数倍,以int
为例,如果在内存中存储的地址为7999 - 8002,那么在读取8000 - 8063后数据没有读取完整,会再次读取8000-64 - 7999段的数据,所以系统存储数据时会按照数据类型的对其信息存储,如8000 - 8003(数据类型的对其信息的整数倍),由于结构体内变量的对齐信息,结构体所占长度不一定等于所有变量长度加和。struct Str{
int x;
char y;
};
例如Str
的长度为8而不为5
char,wchar_t,char16_t,char32_t
short,int,long,long long
unsigned + 带符号整数
(unsigned == unsigned int
)float,double,long double
char
是否有符号,取决于编译器 可以通过定义unsigned char/signed char
定义有符号和无符号
整数在内存中的保存方式:大端、小端(两台机器之间传输数据时要考虑,解析时要一致)
每种类型的大小
int32_t
字面值:在程序中直接表示为一个具体的数值或字符串的值
每个字面值有其类型
整数:20(十进制),024(八进制),0x14(十六进制) int
型
浮点数: 1.3 1e-8 double
型
字符: ‘c’ ‘\n’ char
型
字符串:“hello” char[6]
型(字符串后隐式加上\0
表示字符串结束)
布尔:true false boor
型
指针:nullptr nullptr_t
型
float x = 1.3; //double转为float
unsigned long long y = 2ULL; //int转为unsigned long long
(可以引入自定义后缀改变字面值类型 User-defined literals)
int operator "" _ddd(long double x)
{
return (int)x*2;
}
int x = 3.14_ddd;
对应一段存储空间,可以改变其中内容
变量类型在首次声明(定义)时指出:
int x;
定义extern int x;
不同编译端的声明 (如果在声明时赋值,系统会认为是定义)变量的初始化和赋值
int x(10);
拷贝:int x = 10;
变量赋值时可能设计类型的转换(高精度转为低精度时可能有损)
bool x = 1;
(为0则为false,非0为true),bool转为整数对应0和1隐式类型转换
std::cmp_XXX
(C++ 20) 比较,满足数学定义。指针表示存储变量的首个字节的地址
(64位机 总线长度是64个字节,一次性处理64个字节,内存最大为264)
&
*
int* p = &x; //*表示声明变量p是一个指针
int y = *p; //*表示解引用,访问指针所指向的变量
int main(){
int* p = &x;
int* p(&x);
int* p; //函数内缺省初始化,存放随机值(原内存中的数据被解析成一个地址)
}
int* p; //函数外缺省初始化,初始化为0,0地址特殊,不能被解引用
int* p = nullptr;
C语言中 NULL==0
,函数内尽可能避免地址缺省初始化,可赋值为0
nullptr
函数重载
fun(int){} //1
fun(int*){} //2
//函数调用时会自动选择输入参数类型匹配的函数
int* p;
fun(0); //调用1
fun(p); //调用2
fun(nullptr) //调用2
指针与bool类型的隐式转换,if(p)
语句中如果p指向0(空指针)地址则转换为false,否则为true,等同于if(p!=nullptr)
指针可以增加和减少,指针+1表示增加一个对应类型所占长度
int x = 42;
int* p = &x;
p=p+1;
指针还可判等和比较,当两个指针分别指向不同变量时不建议比较两个指针,因为所分配的地址不固定,与在堆或栈中建立有关。
void*
指针指针为间接调用,可以避免直接调用产生的数据传输。复制成本较低,读写成本较高。
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个字节(硬件总线决定)
}
int& ref = val;
void fun(int& param){
//.......
}
与变量相对,表示不可修改的对象
使用const
声明常量
是编译期概念,编译器利用常量
int x = 3;
if(x = 4){ //如果此处错写,程序执行完全不同,为避免要const x = 3;
//....
}
const int x = 3;
//......
int y = x + 1; //对比int x = 3;编译器默认x值不变,直接赋值y=4,减少一次读x操作
int* const ptr = &x;
常量指针,表示指针不能被修改const int* ptr = &x;
表示不能通过ptr
修改所指向地址处的值区分:如果const
出现在*
右边,则表示指针不能被修改,如果const
出现在*
左边,则表示指针指向的内容不能被修改
解引用:
int* ptr1;
*ptr1; //int类型
int* const ptr2;
*ptr2; //int类型
const int* ptr3;
*ptr3; //const int类型
const int& ref = x;
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);
}
C++11开始
constexpr
来声明 (不是一种类型,是一种类型的限定符)可以为类型引入别名,从而引入特殊的含义或便于使用(如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
,但不会产生退化)(对一般意义表达式左值加引用)int x = 3;
int* ptr = &x;
decltype(x); //变量 不加引用 返回int
decltype(x); //表达式 左值 加引用 返回int&
decltype(*ptr); //表达式 左值 加引用 返回int&
decltype(3.5+5l); //表达式 右值 不加引用 返回double
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): 使用大括号所限定的域,其中定义的是局部对象
还存在其他的域:类域,名字空间域
域可以嵌套,嵌套域中定义的名称可以隐藏外部域中定义的名称
对象的生命周期起始于被初始化的时刻,终止于被销毁的时刻