指针
void*
不允许加减,因为不知道指向对象的大小。
指针的数值运算(+
、-
、++
、--
),依赖于所指定的对象。
指针之间的减法只有指向同一数组的元素才有效,当计算两个指针 p
和 q
的差值(q - p
)时,所得结果是序列 [p:q)
中的元素数量(一个整数):
#include <iostream>
#include <cstddef> // For size_t
// 模板函数,计算两个指针之间的字节差
template<typename T>
size_t byte_diff(T* p, T* q) {
return reinterpret_cast<char*>(q) - reinterpret_cast<char*>(p);
}
void diff_test() {
int vi[10];
short vs[10];
std::cout << &vi[0] << " " << &vi[1] << " " << &vi[1] - &vi[0] << " " << byte_diff(&vi[0], &vi[1]) << "\n";
std::cout << &vs[0] << " " << &vs[1] << " " << &vs[1] - &vs[0] << " " << byte_diff(&vs[0], &vs[1]) << "\n";
}
int main() {
diff_test();
return 0;
}
运行结果:
000000FBBC0FF948 000000FBBC0FF94C 1 4
000000FBBC0FF988 000000FBBC0FF98A 1 2
指针之间不允许加法,也没意义。
数组
不允许拷贝数组
int a1[10]{};
int a2[10] = a1; // 不OKa
不要尝试按值传递数组给函数。这其实并不是按值的语义,实际上也会影响原数组。此外,还会错误估计数组的大小。用传入指针(数组名会默认转换成指针)和大小代替。
#include <iostream>
void func(int arg[5]) {
for (int i = 0; i < 5; ++i) {
arg[i] += 10;
}
}
int main() {
int a1[10]{};
// 并不是期待的按值传递,原数组被修改
func(a1);
for (int i = 0; i < 10; ++i) {
std::cout << "a1[" << i << "] = " << a1[i] << std::endl;
}
}
多维数组
#include <iostream>
int main() {
int ma[3][5] = { {1, 2, 3, 4, 5}, {6, 7, 8, 9, 10 }, {11, 12, 13, 14, 15} }; // 实际上,就是一个连续的空间
for (int i = 0; i < 15; i++) {
std::cout << *(&ma[0][0]+i) << " ";
}
std::cout << std::endl;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 5; j++) {
std::cout << ma[i][j] << " ";
}
std::cout << std::endl;
}
for (int i = 0; i < 3; i++) {
std::cout << ma[i] << " "; // 注意,ma[i] 会得到指针的结果,两两地址差值是 5*sizeof(int)
}
std::cout << std::endl;
std::cout << ma[1] - ma[0] << std::endl; // 5
return 0;
}
多维数组可以省略第一维:
#include <iostream>
int main() {
int ma1[3][3] = { {1, 2, 3}, {4, 5, 6}, {7, 8, 9} }; // OK
int ma2[][3] = { {1, 2, 3}, {4, 5, 6}, {7, 8, 9} }; // OK,省略第一维的大小
int ma3[][3][3] = { { {1, 2, 3}, {4, 5, 6}, {7, 8, 9} }, { {10, 11, 12}, {13, 14, 15}, {16, 17, 18} } }; // OK,省略第一维的大小
//int ma4[][][3] = { { {1, 2, 3}, {4, 5, 6}, {7, 8, 9} }, { {10, 11, 12}, {13, 14, 15}, {16, 17, 18} } }; // 不OK,第二维的大小不能省略
}
字符串
原始字符串:
#include <iostream>
#include <string>
int main() {
// 使用原始字符串字面量包含双引号
std::string str = R"(He said, "Hello, World!")";
std::cout << "1. String with quotes: " << str << std::endl;
// 使用原始字符串字面量包含反斜杠
std::string path = R"(C:\Users\Name\Documents\file.txt)";
std::cout << "2. Path with backslashes: " << path << std::endl;
// 使用原始字符串字面量包含多行文本
std::string multiLineStr = R"(
#include <iostream>
int main() {
std::cout << "Hello, World!" << std::endl;
return 0;
}
)";
std::cout << "3. Multi-line string: " << std::endl << multiLineStr << std::endl;
// 使用原始字符串字面量包含JSON字符串
std::string jsonStr = R"({
"name": "John",
"age": 30,
"city": "New York"
})";
std::cout << "4. JSON string: " << std::endl << jsonStr << std::endl;
// 使用不同的定界符
std::string customDelimiter = R"##(Custom delimiter ##)##";
std::cout << "5. String with custom delimiter: " << customDelimiter << std::endl;
return 0;
}
注意,原始字符串中的空格、制表符、回车等都被当作字符串的一部分了。
指针与 const
C++ 提供了两种与“常量”有关的概念:
-
constexpr
:编译时求值 -
const
:在当前作用域内,值不发生改变
一个指针牵扯到两个对象:指针本身以及指针所指的对象。在指针的声明语句中,“前置” const
关键字将令所指的对象而非指针本身成为常量。要想令指针本身成为常量,应该用 *const
代替普通的 *
。
const int* ptr; // 指针所指的对象是常量
int* const ptr; // 指针本身是常量
引用
和指针类似,引用作为对象的别名存放的也是对象的机器地址。与指针相比,引用不会带来额外的开销(有时候编译器能对引用进行优化,使得在运行时无须任何对象表示引用)。引用与指针的区别主要包括:
- 访问引用与访问对象本身从语法形式上看是一样的。
- 引用所引的永远是一开始初始化的那个对象。
- 不存在“空引用”,我们可以认为引用一定对应着某个对象。
左值引用和右值引用
为了体现左值/右值以及 const/非 const 的区别,存在三种形式的引用:
-
左值引用(
lvalue reference
):引用那些我们希望改变值的对象-
T&
:初始值必须是 T 类型的左值
-
-
const
引用(const reference
):引用那些我们不希望改变值的对象(比如常量)-
const T&
:初始值不一定非得是左值,甚至可以不是T
类型(其实是发生了隐式转换之后得到的右值,此时这个临时变量的声明周期会随着引用的结束而结束)
-
-
“右值引用”(
rvalue reference
):所引对象的值在我们使用之后就无须保留了(比如临时变量)-
T&&
:必须是右值 - “破坏性读取”——使用移动(
std::move
)来优化性能
-
#include <iostream>
int f() {
return 0;
}
int main() {
int i = 1;
int& i_lvalue_ref = i;
const int& i_const_ref = i;
//int&& i_rvalue_ref = i; //不不OK,不是右值
//int& j_lvalue_ref{ 1 }; //不OK,不是左值
const int& j_const_ref{ 1 }; //OK
int&& j_rvalue_ref{ 1 }; //OK
//int& k_lvalue_ref{ f()}; //不OK,不是左值
const int& k_const_ref{ f() }; //OK
int&& k_rvalue_ref{ f() }; //OK
}
引用的引用
引用的引用,其实还是引用,只是可能是左值引用还是右值引用的区别:
using rr_i = int&&;
using lr_i = int&;
using rr_rr_i = rr_i&&; // "int&& &&" 的类型是 int&&
using lr_rr_i = rr_i&; // "int&& &" 的类型是 int&
using rr_lr_i = lr_i&&; // "int& &&" 的类型是 int&
using lr_lr_i = lr_i&; // "int& &" 的类型别名是 int&
总之,永远是左值引用优先(有一方是左值,就是左值)。
C++ 不允许下面的语法形式:
int&& & r = i;
引用的引用只能作为别名的结果或者模板类型的参数。