现代C++核心语言特性
先速通了一波环境,注意这里每次xlings install之后一定要记得 xlings update一下,要不然会报错,包括d2x的version 也要在 0.1.5以上啊
auto 和 decltype()的使用
习题一
修改过后的代码部分,不得不说 auto和decltype()确实好用,就我感觉而言,auto的话就不需要管后面那些是什么(不管是变量还是表达式,编译器自己会作出决断),但是你的 decltype()括号里需要看后面表达式或者是变量是什么,但也是编译器去决断不需要人眼观察
#include <d2x/cpp/common.hpp>
int main() {
// 0. Declaration and definition
int a = 1;
auto a1 = a; // a1's type is int
int b = 2;
auto b1 = b;
decltype(b) b2 = b; // b2's type is int
auto a2 = a;
char c = 'c';
auto c1 = c;
auto c2 = c;
d2x_assert_eq(a, a1);
d2x_assert_eq(a1, a2);
d2x_assert_eq(b, b1);
d2x_assert_eq(b1, b2);
d2x_assert_eq(c, c1);
d2x_assert_eq(c1, c2);
//D2X_WAIT
return 0;
}
习题二
#include <d2x/cpp/common.hpp>
int main() {
// 1. Expressions
int a = 1;
auto a1 = a + 2;
auto a2 = a + 2 + 1.1;
int b = 2;
decltype(a+0.1) b1 = a + 0.1;
decltype(a + b + 1.1) b2 = a + b + 1.1;
char c = 'c';
auto c1 = 1 + c;
auto c2 = 2 + 'a';
d2x_assert_eq(a2, a + 2 + 1.1);
d2x_assert_eq(b1, a + 0.1);
d2x_assert_eq(c1, 1 + c);
d2x_assert_eq(c2, 2 + 'a');
//D2X_WAIT
return 0;
}
习题三
#include <d2x/cpp/common.hpp>
#include <iostream>
#include <vector>
#include <functional>
int add_func(int a, int b) {
return a + b;
}
int main() {
// 2. Complex types
std::vector<int> v = {1, 2, 3};
std::vector<int>::iterator v1 = v.begin();
for (; v1 != v.end(); ++v1) {
std::cout << *v1 << " ";
}
std::cout << std::endl;
auto v2 = v.begin();
for (; v2 != v.end(); ++v2) {
std::cout << *v2 << " ";
}
std::cout << std::endl;
auto minus_func = [](int a, int b) { return a - b; };
std::vector<std::function<decltype(add_func)>> funcVec = {
add_func,
minus_func
};
d2x_assert_eq(funcVec[0](1, 2), 3);
d2x_assert_eq(funcVec[1](1, 2), -1);
//D2X_WAIT
return 0;
}
习题四
#include <d2x/cpp/common.hpp>
#include <iostream>
#include <vector>
// 3. Function return types
auto add_func(int a, double b) -> decltype(a + b) {
return a + b;
}
template<typename T1, typename T2>
auto minus_func(T1 a, T2 b) -> decltype(a - b) {
return a - b;
}
//按照前面 add_func的操作将 minus_func()照做就OK啦!
int main() {
d2x_assert_eq(minus_func(1, 2), -1);
d2x_assert_eq(minus_func(2, 1), 1);
d2x_assert_eq(minus_func(1, 2.1), -1.1);
//D2X_WAIT
return 0;
}
习题五
**提示时间!**
- `(obj.a)` 是带括号的表达式 → `const int &`
- `obj.b` 直接取成员 → `double`
- `(obj.b)` 带括号表达式 → `double &`
我的理解:obj.b:这是成员访问表达式,直接取结构体/类的成员。(obj.b):这是括号表达式,括号内是成员访问表达式,整体作为一个左值表达式。
补了一发左值(指向特定内存具有名称的值)和右值(指临时短暂的表达式或值)的概念
下面代码为什么obj.a 直接用他成员类型即可,而obj.b 直接用double 不行?
因为**const Object obj** 这个对象 obj是 const 修饰了,那么整个成员变量都需要加上 const才起作用
#include <d2x/cpp/common.hpp>
#include <type_traits>
// 4. Class/Struct member type deduction
struct Object {
const int a;
double b;
Object() : a(1), b(2.0) { }
};
int main()
{
const Object obj;
bool type_check = false;
// Type deduction for obj and (obj)
type_check = std::is_same<decltype(obj), const Object>::value;
d2x_assert(type_check); type_check = false; // dont change this line
type_check = std::is_same<decltype((obj)), const Object &>::value;
d2x_assert(type_check); type_check = false; // dont change this line
// Type deduction for obj.a and (obj.a)
type_check = std::is_same<decltype(obj.a), const int>::value;
d2x_assert(type_check); type_check = false; // dont change this line
type_check = std::is_same<decltype((obj.a)), const int &>::value;
d2x_assert(type_check); type_check = false; // dont change this line
// Type deduction for obj.b and (obj.b)
type_check = std::is_same<decltype(obj.b), double >::value;
d2x_assert(type_check); type_check = false; // dont change this line
type_check = std::is_same<decltype((obj.b)), const double & >::value;
d2x_assert(type_check); type_check = false; // dont change this line
//D2X_WAIT
return 0;
}
习题六
需要注意:auto 会剥离顶层 const 和引用
也就是说我们使用 auto的时候 ,后面不管是左值或者右值,后面填写的都只是类型名
#include <d2x/cpp/common.hpp>
#include <type_traits>
// 5. const & reference stripping and preservation
int main() {
const int ci = 1;
int n = 2;
int& ri = n;
bool type_check = false;
// auto strips top-level const: what does a deduce to?
auto a = ci;
type_check = std::is_same<decltype(a), const int >::value;
d2x_assert(type_check); type_check = false; // dont change this line
// auto strips the reference: what does b deduce to? (b is an independent copy of n)
auto b = ri;
type_check = std::is_same<decltype(b), const int>::value;
d2x_assert(type_check); type_check = false; // dont change this line
// To keep a const reference const int&: how should cr be declared?
auto cr = ci;
type_check = std::is_same<decltype(cr), const int >::value;
d2x_assert(type_check); type_check = false; // dont change this line
// To preserve the declared type const int exactly: how should d be declared?
decltype(ci) d = ci;
type_check = std::is_same<decltype(d), const int &>::value;
d2x_assert(type_check); type_check = false; // dont change this line
//D2X_WAIT
return 0;
}
default_and_delete
补了一发 default 和 delete用法,还是听容易理解的,两者都作用于构造函数那一类,default 就相当于我就是默认用系统提供给我的,而delete 相当于我让其他程序员知道 我不用系提供给我的那一套
习题一
#include <d2x/cpp/common.hpp>
#include <iostream>
// default and delete explicitly control -> compiler default constructor generation behavior
struct A {
};
struct B {
B() = default;
B(int x) { std::cout << "B(int x)" << std::endl; }
};
struct C {
C()=delete;
C(int x = 1) { std::cout << "C(int x = 1)" << std::endl; }
};
int main() { // Do not directly modify the code in the main function
A a;
B b;
C c(1);
//D2X_WAIT
return 0;
}
习题二
拷贝构造函数就是一个对去初始化另外一个对象
class Num
{};
Num(const Num &another)
{}
#include <d2x/cpp/common.hpp>
#include <iostream>
// Implement std::unique_ptr property: not copyable but movable
struct UniquePtr {
void *dataPtr;
UniquePtr() = default;
UniquePtr(const UniquePtr&) = delete;
UniquePtr & operator = (const UniquePtr &) = delete;
UniquePtr(UniquePtr &&) = default;
UniquePtr & operator = (UniquePtr &&) = default;
};
int main() { // Do not directly modify the code in the main function
// std::unique_ptr<int> a(new int(1));
UniquePtr a;
// Object cannot be copied/duplicated
// std::unique_ptr<int> b = a; // error
d2x_assert(std::is_copy_constructible<UniquePtr>::value == false);
// a = b; // error
d2x_assert(std::is_copy_assignable<UniquePtr>::value == false);
// Object can be moved
// std::unique_ptr<int> c = std::move(a); // ok
d2x_assert(std::is_move_constructible<UniquePtr>::value == true);
// a = std::move(c); // ok
d2x_assert(std::is_move_assignable<UniquePtr>::value == true);
//D2X_WAIT
return 0;
}
习题三
#include <d2x/cpp/common.hpp>
#include <iostream>
void func(int x) {
std::cout << "x = " << x << std::endl;
}
// Explicitly delete float parameter overload
//void func(float) = delete;
int main()
{
func(1); // int
//func(int(1.1); // float
//上述是丢失精度的做法,如果不丢失精度,就得把上面 void func(float) = delete删除
float(1.1);
//D2X_WAIT
return 0;
}
final_and_override
override针对于子类继承父类后,父类中用 ```virture```修饰过的,子类可以重写,但是必须是父类中的方法
主要还是为了 防止我们写错相应方法做的补充
final 和 override 都是实现多态的方式,final 的意思是 不允许父类的虚函数再被**其他有虚函数的子类重写**
#include <d2x/cpp/common.hpp>
#include <iostream>
#include <string>
struct A {
virtual void func1() {
std::cout << "A::func1()" << std::endl;
}
void func2() {
std::cout << "A::func2()" << std::endl;
}
};
struct B : A {
void func1() {
std::cout << "B::func1()" << std::endl;
}
void func2() {
std::cout << "B::func2()" << std::endl;
}
};
int main() {
B override; // Do not directly modify the code in the main function
override.func1(); // B::func1()
override.func2(); // B::func2()
A *a = &override;
a->func1(); // B::func1()
a->func2(); // A::func2()
//D2X_WAIT
return 0;
}
习题二
这题为什么```A *a = &final ```的时候 ```a->fun1()```是 3 呢,而后面 ```a->func2() ```却是2?
我猜测 因为func1()是虚函数,下面又有子类进行重写(override) 那么就是默认调用为子类的方法
而 fun2()并没有在父类中虚函数定义,自然而然调用的还是 A的方法 (即使指向了B这个对象)
#include <d2x/cpp/common.hpp>
#include <iostream>
#include <string>
struct A {
virtual int func1() {
return 1;
}
int func2()
{
return 2;
}
};
struct B : A
{
int func1()
{
return 3;
}
int func2()
{
return 4;
}
};
int main()
{
B final; // Do not directly modify the code in the main function
d2x_assert_eq(final.func1(), 3); // B::func1()
d2x_assert_eq(final.func2(), 4); // B::func2()
A *a = &final;
d2x_assert_eq(a->func1(), 3); // B::func1()
d2x_assert_eq(a->func2(), 2); // A::func2()
//D2X_WAIT
return 0;
}
习题三
父类是 AudioPlayer 子类是WAVPlayer、MP3Player、OGGPlayer 但是请注意 父类i的play()是final关键字修饰,说明子类不可重写play()方法 但是 init_audio_params() 和 play_audio() 方法是可重写的,于是我们直接重写这两个方法即可
#include <d2x/cpp/common.hpp>
#include <iostream>
#include <string>
struct AudioPlayer { // Do not directly modify the AudioPlayer class
virtual void play() final{
init_audio_params();
play_audio();
}
private:
virtual void init_audio_params() = 0;
virtual void play_audio() = 0;
};
struct WAVPlayer : AudioPlayer {
void init_audio_params() override {
std::cout << "WAVPlayer: Initializing audio parameters..." << std::endl;
}
void play_audio() override {
std::cout << "WAVPlayer: Playing WAV audio..." << std::endl;
}
};
struct MP3Player : AudioPlayer {
void init_audio_params() override {
std::cout << "MP3Player: Initializing audio parameters..." << std::endl;
}
void play_audio() override {
std::cout << "MP3Player: Playing MP3 audio..." << std::endl;
}
};
struct OGGPlayer : AudioPlayer {
// Correctly implement OGGPlayer
// init_audio_params();
void init_audio_params()override
{
std::cout << "OGGPlayer: Initializing audio parameters..." << std::endl;
}
void play_audio() override {
// play_audio();
std::cout << "OGGPlayer: Playing OGG audio..." << std::endl;
}
};
int main() { // Do not directly modify the code in the main function
AudioPlayer *player1 = new WAVPlayer();
AudioPlayer *player2 = new MP3Player();
AudioPlayer *player3 = new OGGPlayer();
player1->play();
player2->play();
player3->play();
delete player1;
delete player2;
delete player3;
//D2X_WAIT
return 0;
}
Trailing_and_return type
我理解的 return_type 这部分内容指的是 编译器自动帮你解析出数据类型
比如 double + double 的数据类型,如果想要最后是 int 的结果,那么就在后面加一个 ->int{}即可
习题一
#include <d2x/cpp/common.hpp>
#include <iostream>
int add0(double a, int b) {
return a + b;
}
auto add1(double a, int b) -> int {
return a + b;
}
template<typename T1, typename T2>
auto add2(const T1 &a, const T2 &b) -> decltype(a+b)
{
return a + b;
}
auto add3 = [](double a, double b) -> int {
return a + b;
};
int main() {
d2x_assert_eq(add0(1.1, 2), 3);
d2x_assert_eq(add1(1.1, 2), 3);
d2x_assert_eq(add2(1.1, 2), 3.1);
d2x_assert_eq(add2(1, 2.1), 3.1);
d2x_assert_eq(add3(1.1, 2.1), 3);
//D2X_WAIT
return 0;
}
rvalue references
这里讨论的是如何用右值引用延长对象的生命周期,比对了绑定到左值引用上不能修改这个临时对象, 而使用Object&& 用右值引用后面依然可以修改这个对象中的内容
在下述代码中,因为 `const Object &` 就像给临时对象贴了"请勿触摸"的标签,所以
这是左值引用延长临时对象生命周期的办法:在最左边加一个 const
const Object &objRef = Object();
而右值引用 删除const 再在后面加一个 & 即可:
Object &&objRef = Object();
习题一
#include <d2x/cpp/common.hpp>
#include <iostream>
#include <string>
struct Object;
static Object * object_address = nullptr;
struct Object {
int data = 0;
Object() {
std::cout << "Object():" << this << std::endl;
object_address = this;
}
Object(const Object&) { std::cout << "Object(const Object&):" << this << std::endl; }
Object(Object&&) { std::cout << "Object(Object&&):" << this << std::endl; }
~Object() { std::cout << "~Object():" << this << std::endl; }
};
int main() { // Disable compiler optimization
{
std::cout << "----> Temporary object - rvalue 1" << std::endl;
Object();
std::cout << "----> Temporary object - rvalue 2" << std::endl;
Object obj = Object();
std::cout << "--------Code modifiable area - Start--------" << std::endl;
Object &&objRef = Object(); // Extend temporary object lifetime
std::cout << "--------Code modifiable area - End--------" << std::endl;
objRef.data = 1; // Modify the value of the extended lifetime temporary object (do not directly modify this line)
std::cout << "objRef.data = " << objRef.data << " - " << &objRef << std::endl;
d2x_assert((&objRef == object_address));
}
//D2X_WAIT
return 0;
}
move-semantics
移动语义在上述右值引用的基础下, 通过一个构造函数创建另一个对象,让原来对象的指针失效,实现了一个转移的效果
不需要把数据一个个复制i过去
步骤就分三步:
1.释放旧资源
2.转移该对象资源
3.让该对象指针指空
习题一
编译器告诉我们 **buff2DataPtr == buff3DataPtr** 不对等,为什么呢?
是因为 **other.data = nullptr;** 我们已经旧的对象的指针指空了,相当于他里面无数据了,所以不能直接采用赋值的方式拷贝数据,而是应该用 **std::move** 实现数据移动的操作
#include <d2x/cpp/common.hpp>
#include <iostream>
struct Buffer {
int *data;
Buffer() : data { new int[2] {0, 1} } {
std::cout << "Buffer():" << data << std::endl;
}
Buffer(const Buffer &other) {
std::cout << "Buffer(const Buffer&):" << data << std::endl;
data = new int[2];
data[0] = other.data[0];
data[1] = other.data[1];
}
Buffer(Buffer&& other) : data { other.data } {
std::cout << "Buffer(Buffer&&):" << data << std::endl;
other.data = nullptr; // Invalidate the pointer of the original object
}
~Buffer() {
if (data) {
std::cout << "~Buffer():" << data << std::endl;
delete[] data;
}
}
const int * data_ptr() const { return data; }
};
Buffer process(Buffer buff) {
std::cout << "process(): " << buff.data << std::endl;
return buff;
}
int main() {
{
Buffer buff1 = process(Buffer());
auto buff1DataPtr = buff1.data_ptr();
std::cout << " --- " << std::endl;
Buffer buff2(std::move(buff1));
auto buff2DataPtr = buff2.data_ptr();
d2x_assert(buff1DataPtr == buff2DataPtr);
Buffer buff3 = std::move(buff2);
auto buff3DataPtr = buff3.data_ptr();
d2x_assert(buff2DataPtr == buff3DataPtr);
Buffer buff4 = std::move(buff3);
auto buff4DataPtr = buff4.data_ptr();
d2x_assert(buff3DataPtr == buff4DataPtr);
}
//D2X_WAIT
return 0;
}
习题二
#include <d2x/cpp/common.hpp>
#include <iostream>
static int move_assignment_counter = 0;
struct Buffer {
int *data;
Buffer() : data { new int[2] {0, 1} } {
std::cout << "Buffer():" << data << std::endl;
}
Buffer(const Buffer &other) {
std::cout << "Buffer(const Buffer&):" << data << std::endl;
data = new int[2];
data[0] = other.data[0];
data[1] = other.data[1];
}
Buffer(Buffer&& other) : data { other.data } {
std::cout << "Buffer(Buffer&&):" << data << std::endl;
other.data = nullptr; // Invalidate the pointer of the original object
}
Buffer & operator=(const Buffer &other) {
std::cout << "Buffer& operator=(const Buffer&):" << data << std::endl;
if (this != &other) {
delete[] data; // Release old resources
data = new int[2];
data[0] = other.data[0];
data[1] = other.data[1];
}
return *this;
}
Buffer & operator=(Buffer&& other) {
move_assignment_counter++;
std::cout << "Buffer& operator=(Buffer&&):" << data << std::endl;
if (this != &other) {
delete[] data; // Release old resources
data = other.data; // Transfer resources
other.data = nullptr; // Invalidate the pointer of the original object
}
return *this;
}
~Buffer() {
if (data) {
std::cout << "~Buffer():" << data << std::endl;
delete[] data;
}
}
const int * data_ptr() const {
std::cout << "data[0] = " << data[0] << ", data[1] = " << data[1] << std::endl;
return data;
}
};
Buffer process(Buffer buff) {
std::cout << "process(): " << buff.data << std::endl;
return buff;
}
int main() { // No compiler optimization
{
Buffer buff1;
buff1 = Buffer(); // Case 1: Temporary object assignment
d2x_assert_eq(move_assignment_counter, 1);
Buffer buff2;
buff2 = process(buff1); // Case 2: Intermediate object assignment
d2x_assert_eq(move_assignment_counter, 2);
buff2 = std::move(buff1); // Case 3: Explicit move assignment
d2x_assert_eq(move_assignment_counter, 3);
}
//D2X_WAIT
return 0;
}
948

被折叠的 条评论
为什么被折叠?



