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