本 Wiki 开启了 HTTPS。但由于同 IP 的 Blog 也开启了 HTTPS,因此本站必须要支持 SNI 的浏览器才能浏览。为了兼容一部分浏览器,本站保留了 HTTP 作为兼容。如果您的浏览器支持 SNI,请尽量通过 HTTPS 访问本站,谢谢!
第 2 章笔记
10
x = 10
,x
就是对象sizeof()
测量std::numeric_limits
查找,范围为 $2^{bit length}$32
或者 64
位数据。如果存储的数据位置处于两次读取位置的之间,那么需要两次读写才可以完整的读取存取的数据。这种情况被称为内存对齐不良。这种情况可以通过安排数据的位置进行改良。alignof(type)
#include <limits>
int main(int argc, char const *argv[])
{
int x{0};
std::cout << "Size of: " << sizeof(x) << std::endl;
std::cout << "Int min: " << std::numeric_limits<int>::min() <<std::endl;
std::cout << "Int max: " << std::numeric_limits<int>::max() <<std::endl;
std::cout << "Alignof int" << alignof(int) << std::endl;
return 0;
}
char
, whcar_t
, char16_t
, char32_t
):char 以外的 char 是针对其他编码的特殊字型的;比如 Unicode
只有 unsigned
指代 unsigned int
unsigned char c1;
signed char c2;
int32_t
(32 bit, 4 byte) (fixed width integer types)int
): 十进制 / 8 进制 / 16 进制3.14
char
类型,比如转义字符 \n
, \x4d
char[]
类型,以 \0
作为结尾。因此,Hello
的长度是 6
位,而不是 5
位。true
/ false
nullptr
,nullptr_t
类型
// 指定 float 类型的字面值
float x = 1.3f;
// 指定一个 unsigned long long 类型的字面值
unsigned long long y = 2ULL;
C++ 11 允许用户自定义字面值的类型。该定义通过重写运算符 "" 进行定义。该功能可以方便我们定义一些单位(比如时间 / 重量)等等。下面的字面值代表了将接收到的 double 乘以 2 再转化为 int 的结果
int operator "" _customType(long double x)
{
return (int)x * 2;
}
int main(int argc, char const *argv[])
{
//using _customType as an literal type
int x = 3.14_customType;
std::cout << x << std::endl;
}
需要注意的是,User-defined literals 能接收的参数类型是有限的(引入的函数接收的类型是有限的)。
extern
:实现的是定义只有一处,声明可以有多处的概念,用于外部文件引用实现文件中的定义。
为什么要使用 extern 关键字?
假设我们有两个文件需要一起编译。A 中使用调用 B 中已经定义的变量 x。如果没有 extern
,那么会出现两种情况:
因此,如果希望在 A 中使用 B 中定义的 x,那么必须使用 extern
关键字。extern
会让编译器将定义视作外部文件变量在本地的声明,从而达到我们希望的效果:
//source.cpp
int x;
//main.cpp
extern int x;
int main()
{
std::cout << x << std::endl;
}
extern 引入的变量如果进行初始化,那么就会从声明转化为定义。
0
//direct init
int x(10);
//copy init
int x = 10;
//list init
int x{10};
std::cmp_XXX
库(C++ 20)
//p points to the address of the val
int* p = &val;
&
*
:不能访问没有内容的地址(0
地址)一般情况下会给一个 0
地址来保证错误的解引用一定会报错:
// 指针指向内容随机,解引用结果无法预测
int *p;
// null pointer
// 访问报错,但是内容稳定,方便查错
// 缺点是存在整型到指针的隐式转换
int *p = 0;
int *p = NULL;
// 防止隐式转换,C++ 11提供的 nullptr 指针对象
int *p = nullptr;
++ / –
:移动到下一个(上一个)指向的位置,距离取决于指针指向变量的类型==
:判断两个两个指针是否指向同一个内存单元
int x = 42;
int *p = &x;
int **pp = &p;
指针是对对象的间接引用:
false
if (p)
方式来书写条件判断指针可能存在的问题:指向的对象为空或者非法。采用引用是另外一个较好的选择。引用是变量的别名:
int x = 42;
//& is an type modifier here
int &refX = x;
int x = 0;
int y = 1;
int *ptr = &x;
//打印 ptr 结果相同,但实际上是不同的操作
//改变 x 所在内存的值为 y, 1 in x
*ptr = y;
//改变了 ptr 指向的位置,从 x 到 y,1 in y
ptr = &y;
从上面的例子来看,引用只能改变其指向内存地址中的内容,而不能改变其指向的位置。这要求引用在初始化的时候必须绑定一个实际的对象(的地址)。
需要注意非法引用的问题:通过函数返回局部变量的引用可能出现问题。
int* &refPx = ptr;
=
代替 ==
带来的问题指针与常量结合时,需要考虑哪个部分不能被修改:
如果限制的是指针本身,那么:
// 指针本身无法改变
int* const ptr = &x;
如果限制的是指针指向的内容:,那么:
//指针可以改指其他位置,不能通过该指针改变其指向的内容
const int* ptr = &x;
const
出现在 *
左边代表指针本身不能指向其他地方。
// int * to const int *
int x = 4;
// &x 由 int* 转化为了 const int*,该指针之前可以对 x 进行读写,但现在只能读取 x
const int *ptr = &x;
// const int * to int *
const int x = 4;
// error, &x 无法写 x,因此 ptr 也不能对 x 进行写操作
// low-level const 是有传递性的,如果之前只读,那么之后也必须只读(不管通过什么样的途径,比如赋值,参数传递等等)
int* ptr = &x;
// good
const int* ptr = &x;
不同的常量确定的时机不同。比如下面的例子:
int y;
std::cin >> y;
// y1 是在运行期确定的(运行期常量)
const int y1 = y;
// y2 是在编译期确定的(编译期常量)
const int y2 = 3;
由于这样的问题,编译器在处理 y1
和 y2
时,就会有不同的方式。比如以上述的变量做条件判断 if (y1 == 3)
:
y1
会以“期待运行期有输入的”方式进行汇编y2
对编译器可见,那么 y2 == 3
一定为真。因此,编译器不会进行 if
的判断,而会直接执行该 if 下的代码段。(优化)
也就是说,编译器常量是可以被编译器优化的。这也是常量表达式的由来:C++ 11 中通过 constexpr
关键字,将常量直接声明为编译期期量。也就是说,上述的 const int y2 = 3
可以写为:
// y2 被显式的声明为编译期常量
// y2 的类型是 const int
// constexpr 是一种带指导意义的限定符(//specifier//),不属于类型声明的一部分
constexpr int y2 = 3;
指针也可以作为编译期常量。这种情况下,需要满足两个条件:
只有满足这两个条件,指针才能作为编译期常量对编译器可见。也就是说,这种情况下的指针类型是 const type* const
constexpr const int* ptr = nullptr;
//字符串
constexpr const char* str = "abc";
可以使用 std::is_same_v<type1, type2> 来判断类型是否相同。定义于 <type_traits> 头文件
类型可以引入别名,便利使用(比如 size_t
实际上是一个 unsigned int)。别名的引入有两种方式:
//myInt 是 int 类型的别名
//推荐使用 using
//别名实际上是 myCharArr, 但 typedef 的引用看起来非常 confusing
typedef char myCharArr[4];
using myCharArr = char[4];
const
修饰别名,那么 const
修饰的是指针,也就是将指针定义为常量指针。换句话说,下面两者等价:
using intP = int*;
int x = 3;
//别名定义
const intP constIntPAlias = &x;
//等同于 top-const pointer
int* const constIntP = &x;
C++11 允许使用 auto
关键字让编译器自动通过初始化表达式的结果推断变量的类型:
auto x = 3.5 + 15l;
auto
推导出的类型是强类型(定义时已经确定)auto
可能会导致类型退化
int x1 = 3;
int& ref = x1;
//类型退化,ref 作为右值,此处由 int& 退化为 Int, 而不是 int&
int y = ref;
//auto 导致类型退化, ref2 是 int 类型
auto ref2 = ref;
const / constexpr
与 auto
组合会推导出常量 / 常量表达式类型
//const int
const auto x1 = 3;
//const int&
const auto& x2 = 3;
const int x3 = 3;
//const int
const auto y1 = x3;
//int (top-const 的退化)
auto z = x3;
//不会退化,const int&
auto& y2 = x3;
int x[3] = {1,2,3};
//int*
auto x1 = x;
//加引用不会退化:int(&)[3]
auto& x2 = x;
decltype 获取一个表达式,并返回表达式的类型。decltype 与 auto 的区别在于,decltype 不会产生类型退化。
val
代表 entity(变量名称),那么 val
是什么类型,那么返回的就是什么类型
//x is an variable name
int x = 3;
//int
decltype(x);
//注意加括号的形式,(x) 是表达式, x 是变量名
//int&
decltype((x));
decltype(exp(r-value))
:如果表达式是非变量名称的表达式,且为右值,那么返回的是表达式评估后的类型:
int x = 3;
int& y1 = x;
//int
auto y2 = y1;
//int&
decltype(y1) y3 = y1;
decltype(exp(l-vaule))
:如果表达式为非变量名称的左值(通常带运算符),返回的类型会自动加上一个引用
int x = 3;
int* ptr = &x;
//*ptr 是包含了解引用操作符的表达式:此处是 l-value,此处得到的是 int&
decltype(*ptr);
//非常繁琐的写法
decltype(3.5+15l) x = 3.5 + 15l;
//C++ 14 的写法
// 用 auto 来代表繁琐的表达式,并不会导致退化
// 编译器会将 auto 替换为赋值运算符右边的内容
decltype(autol) x = 3.5 + 15l;
int
, long
都可以表示为 std::intergal
#include <concepts>
int main()
{
//int
std::integral auto z = 3;
//error, 3.5 is not an integral
std::integral auto z = 3.5;
}
域代表了程序的一部分,域中的 name 有唯一的含义:
域可以进行嵌套,内部域中的 Name 会掩盖外部域中的 Name:
int x = 3;
int main()
{
int x = 4;
//call local x
std::cout << x << '\n';
}
生命周期指对象从被初始化到被销毁的区间。