C++随笔记录

时间:2024-03-26 22:41:33

 C++空指针、智能指针

NULL和nullptr

#include <iostream>

using namespace std; 

void func(char* str) {cout << "char * version" << endl;}
// void func(int i) {cout << "int version" << endl;}

int main()
{   
    func(NULL);
    func(nullptr);
    return 0;
}

输出:

char * version
char * version
#include <iostream>

using namespace std; 

// void func(char* str) {cout << "char * version" << endl;}
void func(int i) {cout << "int version" << endl;}

int main()
{   
    func(NULL);
    // func(nullptr);  // 会报错
    return 0;
}

输出:

int version

智能指针

unique_ptr类似于普通指针,但是会在离开作用域时自动删除释放资源,unique_ptr的特点是内存和资源始终被释放,即使执行返回语句和抛出异常,创建unique_ptr时使用如下语句:

auto anEmployee = make_unique<Employee>();

make_unique是C++14中引入的,如果不兼容C++14,可以使用如下语句创建:

unique_ptr<Employee> anEmployee(new Employee);

 unique_ptr可以用于存储C风格的数组,下面创建一个包含10个Employee实例的数组,存储在unique_ptr中

auto employees = make_unique<Employee[]>(10);
cout << "Salary: " << employees[0].salary << endl;

要创建shared_ptr,应当使用std::make_shared_ptr<>();

auto anEmplyee = std::make_shared<Employee>();
if (anEmployee) 

从C++17开始,也可以将数组存储在shared_ptr中,而旧版的C++不允许这么做,但注意,此时不能使用C++17的make_shared<>()

shared_ptr<Employee[]> employees(new Employee[10]); 

分配二维数组

char** allocateCharacterBoard(size_t xDimension, size_t yDimension) {
    char** myArray = new char*[xDimension];
    for (int i = 0; i < xDimension; i++) {
        myArray[i] = new char[yDimension];
    }
    return myArray;
}

释放多维数组

void releaseCharcterBoard(char** myArray, size_t xDimension) {
    for (size_t i = 0; i < xDimension; i++) {
        delete [] myArray[i];
    }
    delete[] myArray;
}

右值引用是左值

#include <iostream>

using namespace std;

void process_value(int & i) {
    std::cout << "Lvalue processed!" << i << std::endl;
}

void process_value(int && i) {
    std::cout << "Rvalue processed!" << i << std::endl;
}

void forward_value(int && i) {
    process_value(i);
}

int main() {
    int a = 1;
    process_value(a); 
    process_value(1); 
    forward_value(2); 
    /* 输出结果
    Lvalue processed!1
    Rvalue processed!1
    Lvalue processed!2
    */
    return 0;
}

以string类为例,实现拷贝赋值和拷贝构造

//  string 类为示例,实现拷贝构造函数和拷贝赋值操作符
#include <iostream>
#include <vector>
#include <cstring>

class MyString {
private:
    char* m_data;
    size_t m_len;
    void init_data(const char* str) {
        m_len = strlen(str);
        m_data = new char[m_len+1];
        memcpy(m_data, str, m_len);
        m_data[m_len] = '\0';
    }
public:
    MyString() {
        m_len = 0;
        m_data = nullptr;
    }
    MyString(const char* str) {
        init_data(str);
    }
    MyString(const MyString& str) {
        init_data(str.m_data);
        std::cout << "copy construct is called! source: " << str.m_data << std::endl;        
    }
    MyString& operator=(const MyString& str) {
        if (this != &str) {
            init_data(str.m_data);
        }
        std::cout << "Copy Assignment is called! source: " << str.m_data << std::endl;
        return *this;
    }
    virtual ~MyString() {
        if (m_data) {
            std::cout << "call destory fun~" << m_data << std::endl;
            free(m_data);
        }
    }

};

int main() {
    MyString str;
    str = MyString("hello");
    std::vector<MyString> vec;
    vec.push_back(MyString("World"));
}
输出:
Copy Assignment is called! source: hello
call destory fun~hello
copy construct is called! source: World
call destory fun~World
call destory fun~World
call destory fun~hello

添加移动赋值和移动构造函数

//  string 类为示例,实现拷贝构造函数和拷贝赋值操作符
#include <iostream>
#include <vector>
#include <cstring>

class MyString {
private:
    char* m_data;
    size_t m_len;
    void init_data(const char* str) {
        m_len = strlen(str);
        m_data = new char[m_len+1];
        memcpy(m_data, str, m_len);
        m_data[m_len] = '\0';
    }
public:
    MyString() {
        m_len = 0;
        m_data = nullptr;
    }
    MyString(const char* str) {
        init_data(str);
    }
    MyString(const MyString& str) {
        init_data(str.m_data);
        std::cout << "copy construct is called! source: " << str.m_data << std::endl;        
    }

    // 1. 参数(右值)的符号必须是右值引用符号,即“&&”。
    // 2. 参数(右值)不可以是常量,因为我们需要修改右值。
    // 3. 参数(右值)的资源链接和标记必须修改。否则,右值的析构函数就会释放资源。转移到新对象的资源也就无效了。

    MyString(MyString&& str) {
        std::cout << "Call move Constructor ! source: " << str.m_data << std::endl;
        m_len = str.m_len;
        m_data = str.m_data;
        str.m_len = 0;
        str.m_data = nullptr;
    }

    MyString& operator=(const MyString& str) {
        if (this != &str) {
            init_data(str.m_data);
        }
        std::cout << "Copy Assignment is called! source: " << str.m_data << std::endl;
        return *this;
    }

    MyString& operator=(MyString&& str) {
        std::cout << "Call move Constructor ! source: " << str.m_data << std::endl;
        if (this != &str) {
            m_len = str.m_len;
            m_data = str.m_data;
            str.m_len = 0;
            str.m_data = nullptr;
        }
        return *this;
    }

    virtual ~MyString() {
        if (m_data) {
            std::cout << "call destory fun~" << m_data << std::endl;
            free(m_data);
        }
    }

};

int main() {
    MyString str;
    str = MyString("hello");
    std::vector<MyString> vec;
    vec.push_back(MyString("World"));
}
输出:
Call move Constructor ! source: hello
Call move Constructor ! source: World
call destory fun~World
call destory fun~hello

constexpr表达式

constexpr修饰变量时,应满足:

如果该变量是某个类的对象,则它应该立即被构造,如果是基本类型,则应该立即被赋值。

为构造函数的参数或者赋值给该变量的值必须是字面常量,constexpr变量或者constexpr函数。

如果是使用构造函数创建该变量,无论是显示构造或者隐式构造,构造函数必须满足constexpr特性。

当constexpr修饰函数时,应满足:

该函数不能是虚函数,return返回值应该是字面类型的常量,该函数每个参数必须是字面类型的常量,函数体只能包含:

 1.null语句,2.[[../static_assert|static_assert]]语句。2.typedef 声明或模板类别声明,(但该模板别名声明不定义类类型与枚举类型)。3.using声明。4.using 指导语句。5.只能存在唯一的return语句,并且return语句只能包含字面常量,constexpr变量或者constexpr函数。

当 constexpr 修饰类构造函数时,应满足:

- 构造函数的每个参数必须是字面类型常量。

- 该类不能有虚基类。

- 构造函数体必须满足以下条件:

    1. null 语句。

    2. [[../static_assert|static_assert]] 语句。

    3. typedef 声明或 模板别名声明(但该模板别名声明不定义类类型与枚举类型)。

    4. using 声明。

    5. using 指导语句。

- 构造函数中不能有 try-语句块。

- 该类的每个基类和非静态成员变量必须是初始化。

- 每一个隐式转换必须是常量表达式。

#include <iostream>
#include <stdexcept>
    
// constexpr functions use recursion rather than iteration
constexpr int factorial(int n)
{
    return n <= 1 ? 1 : (n * factorial(n-1));
}
    
// literal class
class conststr {
    const char * p;
    std::size_t sz;
    public:
    template<std::size_t N>
    constexpr conststr(const char(&a)[N]) : p(a), sz(N-1) {}
    // constexpr functions signal errors by throwing exceptions from operator ?:
    constexpr char operator[](std::size_t n) const {
        return n < sz ? p[n] : throw std::out_of_range("");
    }
    constexpr std::size_t size() const { return sz; }
};
    
constexpr std::size_t countlower(conststr s, std::size_t n = 0,
                                                std::size_t c = 0) {
    return n == s.size() ? c :
            s[n] >= 'a' && s[n] <= 'z' ? countlower(s, n+1, c+1) :
            countlower(s, n+1, c);
}
    
// output function that requires a compile-time constant, for testing
template<int n> struct constN {
    constN() { std::cout << n << '\n'; }
};
    
int main()
{
    std::cout << "4! = " ;
    constN<factorial(4)> out1; // computed at compile time
    
    volatile int k = 8; // disallow optimization using volatile

    std::cout << k << "! = " << factorial(k) << '\n'; // computed at run time
    
    std::cout << "Number of lowercase letters in \"Hello, world!\" is ";
    constN<countlower("Hello, world!")> out2; // implicitly converted to conststr
}
输出:
4! = 24
8! = 40320
Number of lowercase letters in "Hello, world!" is 9

宏定义中的# ## \符号

字符串化操作符(#)
#include <iostream>
#include <string>

#define exp(s) printf("test s is: %s\n", s)
#define exp1(s) printf("test s is: %s\n", #s)
#define exp2(s) #s
using namespace std;

int main() {
    exp("Hello");
    exp1(hello);

    std::string str = exp2(   bbbb   );
    /**
     * 忽略传入参数名前面和后面的空格。
     */
    std::cout << str << " "  << str.size() << std::endl;
    std::string str2 = exp2(  adfa  asdfasf   );
    /**
     * 当传入参数名间存在空格时,编译器将会自动连接各个子字符串,
     * 用每个子字符串之间以一个空格连接,忽略剩余空格。
     */
    std::cout << str2 << " " << str2.size() << std::endl;
    return 0;
}
输出:
test s is: Hello
test s is: hello
bbbb 4
adfa asdfasf 12
符号连接操作符(##)

“##”是一种分隔连接方式,它的作用是先分隔,然后进行强制连接。将宏定义的多个形参转换成一个实际参数名。

注意事项:

(1)当用##连接形参时,##前后的空格可有可无。

(2)连接后的实际参数名,必须为实际存在的参数名或是编译器已知的宏定义。

(3)如果##后的参数本身也是一个宏的话,##会阻止这个宏的展开。

#include <iostream>
#include <string>


#define expA(s) printf("前缀加上后的字符串为:%s\n",gc_##s)  //gc_s必须存在
// 注意事项2
#define expB(s) printf("前缀加上后的字符串为:%s\n",gc_  ##  s)  //gc_s必须存在
// 注意事项1
#define gc_hello1 "I am gc_hello1"
int main() {
    // 注意事项1
    const char * gc_hello = "I am gc_hello";
    expA(hello);
    expB(hello1);
}
输出:
前缀加上后的字符串为:I am gc_hello
前缀加上后的字符串为:I am gc_hello1

计算程序运行的时间

#include <iostream>
#include <chrono>

using namespace std;
using namespace std::chrono;

using ull = unsigned long long;

ull OddSum = 0;
ull EvenSum = 0;

void findEven(ull start, ull end) {
    for (ull i = start; i <= end; i++) {
        if ((i & 1) == 0) {
            EvenSum += i;
        }
    }
}

void findOdd(ull start, ull end) {
    for (ull i = start; i <= end; i++) {
        if ((i & 1) == 1) {
            OddSum += i;
        }
    }
}

int main() {
    ull start = 0, end = 1900000000;
    auto startTime = high_resolution_clock::now();
    findOdd(start, end);
    findEven(start, end);
    auto endTime = high_resolution_clock::now();
    auto duration = duration_cast<microseconds>(endTime - startTime);

    cout << "OddSum: " << OddSum << endl;
    cout << "EvenSum: " << EvenSum << endl;
    cout << "Sec: " << duration.count() / 1000000 << endl;
    return 0;
}
输出:
OddSum: 902500000000000000
EvenSum: 902500000950000000
Sec: 6

detach

#include <iostream>
#include <thread>
#include <chrono>
#include <mutex>
using namespace std;
/**
 * 这用于从父线程分离新创建的线程
 * 在分离线程之前,请务必检查它是否可以joinable,
 * 否则可能会导致两次分离,并且双重detach()将导致程序终止
 * 如果我们有分离的线程并且main函数正在返回,那么分离的线程执行将被挂起
*/
void run(int count) {
    while (count > 0) {
        cout << count << endl;
    }
    std::this_thread::sleep_for(chrono::seconds(3));
}

int main() {
    thread t1(run, 10);
    cout << "main()" << endl;
    t1.detach();
    if(t1.joinable()) {
        t1.detach();
    }
    cout << "main() after" << endl;
    return 0;
}

thread id

#include <iostream>
#include <thread>

using namespace std;

// 线程的通用标识符
std::thread::id master_thread;

void do__master_thread_work() {
    cout << "master" << endl;
}

void do_common_work() {
    cout << "common" << endl;
}

void some_core_part_of_algorithm() {
    if (std::this_thread::get_id() == master_thread) {
        do__master_thread_work();
    }
    do_common_work();
}

int main() {
    master_thread = std::this_thread::get_id();
    std::cout << "master_thread: " << master_thread << endl;
    std::cout << "master_thread 中运行: " << std::endl;
    some_core_part_of_algorithm();
    std::cout << "thread 中运行:" << std::endl;
    thread t(some_core_part_of_algorithm);
    t.join();
    return 0;
}
输出:
master_thread: 1
master_thread 中运行:
master
common
thread 中运行:
common