文章目录
- 1. 基础语法
- 1.1. static关键字
- 1.2. const关键字
- 1.3. friend关键字
- 1.4. mutable关键字
- 1.5. assert关键字
- 1.6. using namespace std
- 1.7. noncopyable禁止拷贝
- 2. C &面向对象
- 2.1. RAII机制
- 2.2. 什么是虚函数,实现原理是什么?
- 2.3. 面向对象三大特性
- 2.4. 编译时多态是怎样的
- 2.5. 类成员的权限控制
- 2.6. struct和class的区别
- 2.7. Cpp中如何禁止一个类创建对象
- 2.8. 如何限制类只能在堆或栈上创建对象
- 2.9. 带默认参数的构造函数
- 2.10. Cpp构造函数私有化
- 2.11. 拷贝构造函数的调用时机
- 2.12. 在一个有指针对象的类中至少要实现哪三个函数
- 2.13. 如果没有实现拷贝赋值运算符可能会遇到什么问题(深拷贝、浅拷贝)
- 2.14. 指针和引用的区别
- 2.15. volatile
- 2.16. 结构体内存对齐
- 2.17. new和malloc的区别
- 2.18. ptrdiff_t
- 3. STL
- 3.1. 讲一下类型萃取机制
- 3.2. STL中分别有哪些容器,底层实现是什么
- 3.3. sort函数怎么实现的
- 3.4. 什么是仿函数
- 3.5. 哪些情况迭代器会失效
- 3.6. vector使用时注意问题
- 3.7. []与at()区别
- 3.8. vector扩容原理
- 3.9. deque扩容原理
- 4. C 11
- 4.1. std::move()语义原理
- 4.2. std::forward()
- 4.3. 四种智能指针
- 4.3.1. shared_ptr
- 4.3.2. weak_ptr
- 4.3.3. unique_ptr
- 4.3.4. auto_ptr(已遗弃)
- 4.4. 实现一个shared_ptr智能指针
- 4.5. shared_ptr的线程安全性
- 4.6. C 11的四种强制类型转换
- 4.7. 列表初始化
- 4.8. decltype作用以及与auto区别。
Cpp基础知识与常见问题。
基础语法
static关键字
- 修饰全局变量,在堆区分配内存;默认初始化为零;限定作用域为当前文件。
- 修饰局部变量时,在堆区分配内存;只有首次定义时被初始化,直到程序运行结束才释放;作用域为局部作用域。
- 修饰普通函数,不能修改任何非static对象;该函数的作用域为当前文件 。
- 修饰类内成员,堆区分配内存;程序运行时就被初始化,直到程序结束;成员归属于类,被所有对象共享;可以通过”类名::静态成员”或”对象.静态成员”访问
- 修饰类内函数,只能访问类内静态成员或调用类内静态函数,但是普通函数可以访问静态成员和静态函数;可以通过类名调用或对象调用。
- C 中的static关键字的总结
const关键字
- 特性:(1)被修饰的对象不是常量,是一个只读变量(不能放在case关键字后面也说明const不是一个常量);(2)定义时赋值,之后不允许修改。
- 修饰普通变量
const int a;
a还是一个int型变量,const int 顺序可以互换:
int const a;
- 修饰数组
const int a[5];
int const a[5];
只读数组
- 修饰指针
const char *p;
char const *p;//这两种,const都是修饰*p,则p指向的变量只读。(与下一种对照记忆)
char * const p;//const很明显修饰指针p,则指针p不能被修改。
const char * const p;//指针p不能被修改,指向的对象也不能被修改。
- 修饰函数形参
void apple(const char *a){//防止修改a指向的字符串
...
}
void apple(char * const a){//防止修改a本身
...
}
void apple(const vector<int> &num){//防止修改容器内容
...
}
- 修饰函数
int getSum() const {//修饰函数,表示该函数内不能修改变量
return get_Name_sum;
}
friend关键字
friend提供了在类外访问类的私有成员的能力,friend可以修饰函数或类。当在类内声明一个友元函数时,该函数可以访问类的私有成员。当在类内声明友元类时,则友元类可以访问当前类的私有成员。
mutable关键字
如果需要在const成员方法中修改一个成员变量的值,那么需要将这个成员变量修饰为mutable。即用mutable修饰的成员变量不受const成员方法的限制。
assert关键字
assert是一个宏,用于在DEBUG版本下判断表达式的真假,如果表达式为假,它会先向stderr打印错误信息,然后调用abort终止程序运行。
代码语言:javascript复制#include <bits/stdc .h>
using namespace std;
int main(){
cout<<"test assert:"<<endl;
assert(2*2==4);
assert(2*2==5);
return 0;
}
/*
test assert:
20200930_test: 20200930_test.cpp:10: int main(): Assertion 2*2==5 failed.
已放弃 (核心已转储)
using namespace std
1.在头文件中一定不要使用,否则在别人引用你的头文件后,如果std中的函数名和其他库中的冲突了,可能会带来麻烦。 2.在cpp文件中:
代码语言:javascript复制* 在"一般情况下"可以使用,但是注意一定要在所有include语句之后使用。
* 可以在函数中使用,使其只在有限作用域内有效。
* 不直接使用命名空间using namespace xxx,可以使用using std::cin;using std::cout;这种方式。
* 也可以在需要的地方全部加上std:: 。
noncopyable禁止拷贝
1.通用的做法是写一个类noncopyable,凡是继承该类的任何类都无法复制和赋值。
将拷贝构造函数和拷贝赋值运算符设置为私有,这样继承nocopyable的类给对象赋值或拷贝构造时,会先调用父类nocopyable的函数,但是这两个函数是私有的,所以会引发编译错误。
将noncopyable的构造函数和析构函数设置protected,这样该类无法创建对象,但是子类中可以调用。
代码语言:javascript复制#include <bits/stdc .h>
using namespace std;
class noncopyable {
protected:
noncopyable() = default;
~noncopyable() = default;
private:
noncopyable(const noncopyable&) = delete;
const noncopyable& operator=( const noncopyable& ) = delete;
};
class StockFactory:noncopyable{
public:
StockFactory(double _price):price(_price){}
private:
double price;
};
int main(){
StockFactory s1(10);
//StockFactory s2=s1;
return 0;
}
2.使用c 11标准的简单实现:
代码语言:javascript复制class noncopyalbe{
protected:
noncopyable()=default;
~noncopyable()=default;
noncopyable(const noncopyable &)=delete;
noncopyable &operator=(const noncopyable &)=delete;
}
3.google开源项目风格指南建议的做法是使用 DISALLOW_COPY_AND_ASSIGN 宏:
代码语言:javascript复制// 禁止使用拷贝构造函数和 operator= 赋值操作的宏
// 应该类的 private: 中使用
#define DISALLOW_COPY_AND_ASSIGN(TypeName)
TypeName(const TypeName&);
void operator=(const TypeName&)
在 class foo 中使用方式如下:
class Foo {
public:
Foo(int f);
~Foo();
private:
DISALLOW_COPY_AND_ASSIGN(Foo);
};
绝大多数情况下都应使用 DISALLOW_COPY_AND_ASSIGN 宏。如果类确实需要可拷贝,应在该类的头文件中说明原由,并合理的定义拷贝构造函数和赋值操作。注意在 operator= 中检测自我赋值的情况。
C &面向对象
RAII机制
RAII(Resource Acquisition Is Initialization),翻译过来是资源获取即初始化。也就是说当创建一个对象的时候,就对其进行初始化,同样当不需要该对象时,也要对其资源进行释放。比如当调用new来申请空间时、调用open()打开文件时,都需要对应的delete、close来释放资源,但是往往就忘掉释放资源。所以可以利用类的构造函数和析构函数,将需要分配资源的对象进行一层封装,将其获取资源和释放资源分别绑定到构造函数和析构函数里,这样当该对象生命周期结束,就会自己释放资源。
示例一:编程中可能出现以下情况,但是中间如果发生异常或者return了,就执行不了unlock()了。就会导致该资源一直被占用了。
代码语言:javascript复制std::mutex mutex_;
void function()
{
mutex_.lock();
......
......
mutex_.unlock();
}
所以正确的方式是使用std::unique_lock或者std::lock_guard对互斥量进行状态管理:
代码语言:javascript复制std::mutex mutex_;
void function()
{
std::lock_guard<std::mutex> lock(mutex_);
......
......
}
这样只管创建一个lock对象就可以,lock生命周期结束时会自动对mutex_解锁。
- 参考:https://www.cnblogs.com/jiangbin/p/6986511.html
什么是虚函数,实现原理是什么?
虚函数是实现运行时多态的一种机制,比如两个父类指针分别指向子类A和子类B的实例,父类指针调用虚函数时,会根据不同的子类来调用不同的函数。 当类中声明虚函数之后,编译器会在类的开始位置设置一个指针,来指向一个虚函数列表,当子类继承父类时,会一块继承这个指针,如果子类对父类中的虚函数进行了重写,就会用新函数的地址覆盖虚函数表中的旧函数。 http://taowusheng.cn/2019/05/18/20190518 C 虚函数相关知识点/
面向对象三大特性
- 封装。通过设置资源的权限,来实现信息隐藏,提高安全性。一般讲数据设置私有,只提供公开接口来访问资源。
- 继承。对事物进行抽象,将通用的特征放到基类,根据不同事物的分化,实现不同的子类。
- 多态。分为编译时多态和运行时多态。编译时多态通过模板和函数重载实现,运行时多态通过虚函数实现。
编译时多态是怎样的
第一种通过模板实现。
代码语言:javascript复制class Dog{
public:
void sound(){
cout<<"汪汪"<<endl;
}
};
class Cat{
public:
void sound(){
cout<<"喵喵"<<endl;
}
};
template<typename T>
void animalSound(T t){
t.sound();
}
//===================
Dog d;
Cat c;
animalSound(d);
animalSound(c);
第二种重载。函数名相同,参数不同。
类成员的权限控制
访问权限 | 类内 | 子类 | 类外 |
---|---|---|---|
public | ✔ | ✔ | ✔ |
protect | ✔ | ✔ | |
private | ✔ |
struct和class的区别
- struct默认的访问权限和继承权限是public,class默认的访问权限和继承权限是private。
Cpp中如何禁止一个类创建对象
1.将构造函数设置为protected或private。 2.在类内声明纯虚函数。
如何限制类只能在堆或栈上创建对象
1.编译器在为类对象分配栈空间时,会先检查类的析构函数的访问性,其实不光是析构函数,只要是非静态的函数,编译器都会进行检查。如果类的析构函数是私有的,则编译器不会在栈空间上为类对象分配内存。因此,将析构函数设为私有,类对象就无法建立在栈上了。 缺点:(1).无法解决继承问题。如果A作为其它类的基类,则析构函数通常要设为virtual,然后在子类重写,以实现多态。因此析构函数不能设为private。还好C 提供了第三种访问控制,protected。将析构函数设为protected可以有效解决这个问题,类外无法访问protected成员,子类则可以访问。(2).类的使用很不方便,使用new建立对象,却使用destory函数释放对象,而不是使用delete。(使用delete会报错,因为delete对象的指针,会调用对象的析构函数,而析构函数类外不可访问)这种使用方式比较怪异。(3)为了统一,可以将构造函数设为protected,然后提供一个public的static函数来完成构造,这样不使用new,而是使用一个自定义函数来构造,使用一个自定义函数来析构。
代码语言:javascript复制class A{
protected:
A(){}
~A(){}
public:
static A* create(){
return new A();
}
void destory(){
delete this;
}
};
2.只有使用new运算符,对象才会建立在堆上,因此,只要禁用new运算符就可以实现类对象只能建立在栈上。虽然你不能影响new operator的能力(因为那是C 语言内建的),但是你可以利用一个事实:new operator 总是先调用 operator new,而后者我们是可以自行声明重写的。因此,将operator new()设为私有即可禁止对象被new在堆上。
代码语言:javascript复制class A{
private:
void* operator new(size_t t){} // 注意函数的第一个参数和返回值都是固定的
void operator delete(void* ptr){} // 重载了new就需要重载delete
public:
A(){}
~A(){}
};
带默认参数的构造函数
- 如果不写构造函数,会有一个默认的无参构造。如果写了带参构造,编译器就不会创建无参构造了。
- 创建一个如下带默认参数的构造函数,相当于手动创建四个构造函数的效果。
#include <iostream>
using namespace std;
class A{
public:
A(int a=9,int b=8,int c=7){
x=a;
y=b;
z=c;
}
void show(){
cout<<x<<" "<<y<<" "<<z<<endl;
}
private:
int x,y,z;
};
int main(){
A a1;
A a2(1);
A a3(1,1);
A a4(1,1,1);
a1.show();//9 8 7
a2.show();//1 8 7
a3.show();//1 1 7
a4.show();//1 1 1
return 0;
}
Cpp构造函数私有化
一般构造函数都是公有地,创建一个对象时就会自动调用构造函数。(对象是算作类外的,它不是类本身) 构造函数设置为私有,那岂不是没法创建对象了。但是对于强大的Cpp来说,有方法可以绕过去。 构造函数还是要调用的,我们可以在有权限的地方调用,比如static函数、友元函数或友元类。
代码语言:javascript复制#include <iostream>
using namespace std;
class Student{
public:
static Student* makeObj(){
cout<<"hello"<<endl;
return (new Student);
}
private:
Student(){}
};
int main(){
Student* s=Student::makeObj();
return 0;
}
这种方式可以用在单例模式的实现上。
拷贝构造函数的调用时机
- 用一个类的对象去初始化另一个对象时。
- 往函数中传递对象参数时。
- 从函数中返回一个对象时。
在一个有指针对象的类中至少要实现哪三个函数
- 拷贝构造函数、拷贝赋值运算符、析构函数
如果没有实现拷贝赋值运算符可能会遇到什么问题(深拷贝、浅拷贝)
- 浅拷贝,只拷贝指针的值,深拷贝会再开辟一块新空间,连同指针在堆中指向的内容一块拷贝过去。
- 当一个类中含有对象指针时,如果把该类的一个对象复制给另一个对象,这时会导致两个对象中的指针指向同一块内存,此时一个对象销毁,可能会导致另一个对象中的指针指向的内容被销毁。
指针和引用的区别
指针也是一个变量,里面存储的内容是一个地址。而引用本质上是一个常量指针,引用只允许初始化,不能再修改。 编译指针和引用的代码,在汇编上是一样的:c 中,引用和指针的区别是什么? - RainMan的回答 - 知乎 https://www.zhihu.com/question/37608201/answer/545635054
volatile
- 1.保证可见性。volatile的语义是让编译器不要对变量的值做任何假设和推理,禁止用寄存器缓存变量,每次都重新读写内存。
- https://zhuanlan.zhihu.com/p/33074506
结构体内存对齐
对齐规则
- 为了提高内存的读取效率,编译器使用内存对齐的技术。
- 1.结构体内成员对齐规则:第一个成员偏移为0,其他每个成员的开始地址需要是min(当前成员大小,默认对齐字节)的整数倍。
- 2.结构体的对齐规则:偏移地址需要是min(“默认对齐字节”,结构体内最宽成员)的整数倍。
- 3.结构体总大小:内部最宽基本类型的整数倍。
- 使用预编译命令可以控制默认对齐字节,#pragma pack(4)
内存对齐作用
- 平台移植:有的硬件平台不能访问任意地址。
- 性能原因:cpu是按块读取内存的,能够提高访问速度。
new和malloc的区别
malloc
- malloc是一个库函数,作用是分配指定大小的空间。
- 参数是要分配的字节数,返回void*类型的指针,返回值一般需要强制类型转换才能使用。
- 如果申请内存失败会返回NULL。
- 可以用realloc扩容,使用free释放内存。
- 申请数组时:int ptr=(int)malloc(sizeof(int)*n);释放数组free(ptr);
operator new与new operator
- operator new是一个类似加减乘除的表达式,内部会调用malloc分配内存。
- 当A a= new A();创建新对象时,是使用的new operator。会做两件事,一是调用operator分配内存,二是调用对象的构造函数。
- 后者编译时行为,前者运行时行为
operator new
- 接受数据尺寸类型,返回该类型的指针。
- 申请内存失败会抛出异常,或者执行给设定的处理函数。
- 使用delete释放内存。
placment new
- placement new,可以给new在指定内存区创建对象,char p[1024];int *ptr=new(p) int;
- 注意使用”放置new”创建的对象不要使用delete,需要自己手动调用析构函数。
nothrow new
- new函数在分配内存失败时会抛出异常,可以通过nothrow new在申请内存失败时,将返回值设置为NULL。
- 使用方式:char myarray = new (std::nothrow)char[2047 1024 * 1024]; if(myarray==nullptr){…}
set_new_handler
该函数接收一个函数指针,当new或new[]失败时,就会执行传进来的函数。
代码语言:javascript复制// new_handler example
#include <iostream> // std::cout
#include <cstdlib> // std::exit
#include <new> // std::set_new_handler
void no_memory () {
std::cout << "Failed to allocate memory!n";
std::exit (1);
}
int main () {
std::set_new_handler(no_memory);
std::cout << "Attempting to allocate 1 GiB...";
char* p = new char [1024*1024*1024];
std::cout << "Okn";
delete[] p;
return 0;
ptrdiff_t
ptrdiff_t是C/C 标准库中定义的一个与机器相关的数据类型。ptrdiff_t类型变量通常用来保存两个指针减法操作的结果。ptrdiff_t定义在stddef.h(cstddef)这个文件内。ptrdiff_t通常被定义为long int类型。
STL
讲一下类型萃取机制
为什么?当我们利用模板的参数推导机制,实现一个对不同迭代器通用的函数时,函数的参数类型(智能指针)能够推导出来,但是如果函数内部需要用到指针指向的类型,就很不方便了。再就是函数的返回值也要用到指针指向的类型时,仅利用模板的参数推导是做不到的。
如何实现?首先需要每个迭代器来配合,迭代器内部应当储存所指向数据的类型value_type,然后我们利用typedef来将不同迭代器中的value_type都加个新名字。然后在需要用到萃取类型的地方,用我们的typedef所创造的新名字就行了。关键实现如下:
代码语言:javascript复制template<typename I>
struct my_traits{
typedef typename I::value_type value_type;
}
//使用的时候如下:
template<typename Iter>
typename my_traits<Iter>::value_type
addone(Iter iter){
typename my_traits<Iter>::value_type tmp=*iter;
tmp ;
return tmp;
}
每个类型I的迭代器都会储存指向元素的类型value_type,像原生指针、const指针不是一个类,没有value_type,就得自己实现特化版本。
STL中分别有哪些容器,底层实现是什么
- 序列式容器:vector(连续空间)、list(双向链表)、deque(分段连续空间)。
- 关联式容器:map、set、multimap、multiset。(都是红黑树)
- 配置器:queue(对deque封装,可改为list)、stack(对deque封装,可改为list)、priority_queue(堆)。
- 无序关联式容器:unordered_map、unordered_set、unordered_mulitimap、unordered_mulitiset。(都是hash表)
sort函数怎么实现的
- sort的主体是先借助”内省式排序(__introsort_loop())”将序列排成大体有序的,然后利用插入排序再排成完全有序的。
- 第一步的__introsort_loop()是不完全的快排和堆排序的结合体。不完全快排是指在快排递归过程中,判断当前序列的元素个数小于等于16,就退出,不再排序了,这样的结果会让不同的序列块之间是有序的。堆排序是指在当递归深度达到logn时(即快排有递归恶化的倾向出现),调用堆排序对序列进行排序。
- 第二步的插入排序也不是标准的插入排序,也是将序列分段进行插入排序,节省了一次排序过程中的比较操作。
- sort的实现中有很多技巧对排序进行了优化,全是为了提高效率,其最坏情况的时间复杂度也是nlogn。包括使用while循环减少一半快排的函数递归调用、插入排序分段、使用堆排序优化递归层数等。
- 推荐阅读《STL源码剖析》 & 知无涯之std::sort源码剖析
- 另sort为什么不直接用稳定的堆排序实现?堆排序在排序过程中是跳跃式地访问元素,缓存命中率较低。而快排每次对局部数据操作,具有较好的缓存命中率。
什么是仿函数
- 仿函数是对一个类的括号运算符进行重载,然后可以通过函数调用的方式来调用该类所重载的运算符。
哪些情况迭代器会失效
- 一般发生在对容器进行insert()、erase()后。
- 当对vector插入或删除中间一个元素后,原位置之后的迭代器会失效。
- 对list、map、set的结点进行修改后,一般只会导致当前迭代器失效。
vector使用时注意问题
*当插入或删除中间一个元素后,原位置之后的迭代器会失效。
[]与at()区别
- []没有下标越界检查,效率更高,访问越界可能会segment fault。
- at函数有越界检查,如果越界会抛出out_of_range异常。
vector扩容原理
在push_back()的时候会检查是否还有剩余空间,如果没有了,就申请一块原来尺寸2倍的空间,将原来的数据直接复制过去,然后把最后一个元素添加到最后面。并释放原来的空间。
deque扩容原理
deque结构:有一个map指针数组,每一个元素都指向一个缓冲区,扩容时申请空间为原map数组长度二倍,然后把原数组内容复制到新空间的中间。
C 11
std::move()语义原理
简介:
理解move要先知道左值和右值,以string str=”hello”为例,str这个变量是一个左值,可以被改变。”hello”是一个右值,不能被改变了。 然后对左值使用&进行左值引用,对右值使用&&进行右值引用。
对于左值,我们可以使用&进行引用,对于右值,我们可以用&&给它续命。
代码语言:javascript复制int a=10;
int &b=a;
int &&c=10;
如果我们就想用左值引用绑定到左值上,那就需要用到move()了。
代码语言:javascript复制int a=10;
int &&b=std::move(a);
//std::move()做的是转移控制权,将a储存的右值的所有权交给b。
//因为转移了所有权,所以除了对a赋值或销毁外,不要再使用a。
//注意使用该函数时加std::,避免潜在的名字冲突。
从实现上讲,std::move基本等同于一个类型转换:static_cast(lvalue);
参考:
- Cpp primer p470
- 怎样理解 C 11中的move语义
std::forward()
forward会保留参数的类型、const、引用等属性。
代码语言:javascript复制template<typename F,typename T1,typename T2>
void flip1(F f,T1 t1,T2 t2){
f(t1,t2);
}
当传递一般的函数f或参数时,flip1一般能正常工作。但是传递的f函数是下面这样时,就得不到想要的结果。
void f(int v1,int &v2){
cout<<v1<<" "<< v2<<endl;
}
我们期望的结果是:
f(42,i); //f能够改变实参i的值。
但实际:
flip1(f,42,j); //j实参不会被改变。
使用std::forward()和右值引用可以解决这个问题:
template<typename F,typename T1,typename T2>
void flip1(F f,T1 &&t1,T2 &&t2){
f(std::forward<T1>(t1),std::forward<T2>(t2));
}
四种智能指针
shared_ptr
简介
从名字可以看出是一个共享指针,允许多个shared_ptr指针指向一个资源,shared_ptr内部会有一个计数,记录指向该资源的指针个数。当计数为0,就会自动释放资源。
使用场景
当需要频繁申请内存时,使用shared_ptr来管理内存,可以在创建对象时自动初始化资源,也能在生命周期结束时自动释放内存。
创建方式
代码语言:javascript复制shared_ptr<int> p=std::make_shared<int>(10);//推荐使用make_shared()方式。
shared_ptr<T> p=std::make_shared<T>();
shared_ptr<int> p(new int(10));
使用方式
代码语言:javascript复制shared_ptr<int> p1=std::make_shared<int>(10);
shared_ptr<int> p2=std::make_shared<int>(20);
p1=p2;//p2的资源计数会加1,p1资源计数会减1(若p1指向资源计数为0,则释放资源)
注意事项 1.不混合使用普通指针和智能指针
代码语言:javascript复制//有如下函数:
void process(shared_ptr<int> ptr){
使用ptr
}//离开作用域,ptr被销毁。
//给process()传递参数时,不能传递普通指针和临时shared_ptr。
int *x(new int(1024));
process(x);//错误,不能将int*转换为shared_ptr<int>
process(shared_ptr<int>(x));//能编译通过,但是内存会被释放
int j=*x;//未定义行为,x已经是一个空悬指针!
可以这样使用:
shared_ptr<int> p1=std::make_shared<int>(1024);
process(p1);
2.智能指针内部有一个get()函数,可以获取到原生指针。注意get()到的指针不要再初始化另一个智能指针。
代码语言:javascript复制shared_ptr<int> p(new int(1024));
int *q=p.get();
{
shared_ptr<int>(q);
}//程序块结束会释放掉内存。
int foo=*p;//访问了释放掉的内存。
3.get()返回的指针不要去delete、reset其他智能指针、初始化其他智能指针。 4.如果资源不是new申请到的,要注意给智能指针传递一个删除器。
5.不要两个指针相互引用,会造成内存泄漏,可以用weak_ptr解决。
weak_ptr
简介
这是一个弱指针,它必须跟shared_ptr结合来用,它指向shared_ptr所管理的对象,但是它不会导致资源的引用计数变化.
使用场景
使用shared_ptr会有循环引用的问题,可以用weak_ptr来解决这个问题。
使用方式
代码语言:javascript复制//方式一
weak_ptr<T> w(sp);
//方式二
weak_ptr<T> w;
w=p;//p可以是shared_ptr或weak_ptr。
注意事项 1.weak_ptr在使用时,它指向的shared_ptr可能已经释放了,可以使用前先调用lock()。(检查weak_ptr是否为空指针)
代码语言:javascript复制if(shared_ptr<int> np=wp.lock()){
...
}
unique_ptr
简介
与shared_ptr不同,在某个时刻只能有一个unique_ptr指向一个给定对象,当unique_ptr被销毁时,它所指向的对象也被销毁。
使用场景
如果不需要对资源进行共享,优先使用unique_ptr.
代码语言:javascript复制//要采用直接初始化形式。
unique_ptr<double> p(new int(42));
注意事项 1.不能拷贝初始化、不能拷贝
代码语言:javascript复制unique_ptr<string> p2(p1);//错误
unique_ptr<string> p3;
p3=p2;//错误
2.不能拷贝的规则有一个例外,返回一个将要被销毁的unique_ptr可以发生拷贝。
代码语言:javascript复制unique_ptr<int> clone(int p){
return unique_ptr<int>(new int(p));
}
或
unique_ptr<int> clone(int p){
unique_ptr<int> ret(new int(p));
return ret;
}
auto_ptr(已遗弃)
简介
跟unique_ptr有一些相似的特性,同一时刻只能指向一个对象。但是这个允许拷贝,unique_ptr不允许拷贝。在cpp11已经被遗弃。
实现一个shared_ptr智能指针
000000
参考:技术: C 智能指针实现
shared_ptr的线程安全性
C 11的四种强制类型转换
1.static_case(静态转换)
主要执行非多态的转换操作,用于代替C中通常的转换操作。
隐式转换都建议使用 static_cast 进行标明和替换。
在有类型指针与void *之间转换,不能使用static_cast在有类型指针间转换。
代码语言:javascript复制// 1. 使用static_cast在基本数据类型之间转换
float fval = 10.12;
int ival = static_cast<int>(fval); // float --> int
// 2. 使用static_cast在有类型指针与void *之间转换
int *intp = &ival;
void *voidp = static_cast<void *>(intp); // int* --> void*
long *longp = static_cast<long *>(voidp);
// 3. 用于类层次结构中基类和派生类之间指针或引用的转换
// 上行转换(派生类---->基类)是安全的
CDerived *tCDerived1 = nullptr;
CBase *tCBase1 = static_cast<CBase*>(tCDerived1);
// 下行转换(基类---- > 派生类)由于没有动态类型检查,所以是不安全的
CBase *tCBase2 = nullptr;
CDerived *tCDerived2 = static_cast<CDerived*>(tCBase2); //不会报错,但是不安全
// 不能使用static_cast在有类型指针内转换
float *floatp = &fval; //10.12的addr
//int *intp1 = static_cast<int *>(floatp); // error,不能使用static_cast在有类型指针内转换
2.dynamic_cast(动态转换)
用于将一个父类的指针/引用转化为子类的指针/引用(下行转换)。
基类必须要有虚函数,因为 dynamic_cast 是运行时类型检查,需要运行时类型信息,而这个信息是存储在类的虚函数表中。
代码语言:javascript复制CBase *p_CBase = new CBase; // 基类对象指针
CDerived *p_CDerived = dynamic_cast<CDerived *>(p_CBase); // 将基类对象指针类型转换为派生类对象指针
CBase i_CBase; // 创建基类对象
CBase &r_CBase = i_CBase; // 基类对象的引用
CDerived &r_CDerived = dynamic_cast<CDerived &>(r_CBase); // 将基类对象的引用转换派生类对象的引用
3.const_cast(常量转换)
常量指针(或引用)与非常量指针(或引用)之间的转换。
cosnt_cast 是四种类型转换符中唯一可以对常量进行操作的转换符。
去除常量性是一个危险的动作,尽量避免使用。
代码语言:javascript复制int value = 100;
const int *cpi = &value; // 定义一个常量指针
//*cpi = 200; // 不能通过常量指针修改值
// 1. 将常量指针转换为非常量指针,然后可以修改常量指针指向变量的值
int *pi = const_cast<int *>(cpi);
*pi = 200;
// 2. 将非常量指针转换为常量指针
const int *cpi2 = const_cast<const int *>(pi); // *cpi2 = 300; //已经是常量指针
const int value1 = 500;
const int &c_value1 = value1; // 定义一个常量引用
// 3. 将常量引用转换为非常量引用
int &r_value1 = const_cast<int &>(c_value1);
// 4. 将非常量引用转换为常量引用
const int &c_value2 = const_cast<const int &>(r_value1);
4.reinterpret_cast(不相关类型的转换)
用在任意指针(或引用)类型之间的转换。
能够将整型转换为指针,也可以把指针转换为整型或数组。
reinterpret_cast 是从底层对数据进行重新解释,依赖具体的平台,可移植性差。
尽量不使用这个转换符,高危操作。
代码语言:javascript复制int value = 100;
// 1. 用在任意指针(或引用)类型之间的转换
double *pd = reinterpret_cast<double *>(&value);
cout << "*pd = " << *pd << endl;
// 2. reinterpret_cast能够将指针值转化为整形值
int *pv = &value;
int pvaddr = reinterpret_cast<int>(pv);
cout << "pvaddr = " << hex << pvaddr << endl;
cout << "pv = " << pv << endl;
/*
输出结果:
*pd = -9.25596e 61
pvaddr = 8ffe60
pv = 008FFE60
*/
https://www.cnblogs.com/linuxAndMcu/p/10387829.html
列表初始化
效率,列表初始化是在变量创建的时候就进行初始化,相比构造函数体内的赋值要节省一遍拷贝操作,能提高运行时的效率。
类内有const变量要用列表初始化,而不能赋值。
代码语言:javascript复制#include <iostream>
using namespace std;
class Student{
public:
Student(int a):birthday(a){//如果不用列表初始化,下面的birthday无法编译通过。
cout<<birthday<<endl;
}
private:
const int birthday;
};
int main(){
Student s1(1);
return 0;
}
类内有类对象(该对象没有默认构造函数)必须用。
代码语言:javascript复制class CAnimal{
public:
CAnimal(int weight) :m_weight(weight) {
}
int m_weight;
};
class CDog {
public:
CAnimal m_a;//CAnimal类没有默认构造函数,但m_a必须得被初始化呀,Cdog构造函数就可以对m_a进行列表初始化。
const int m_b;
CDog(int a, int b) : m_a(a),m_b(b) {
}
};
- 初始化顺序是根据定义顺序来的,跟初始化列表中的顺序无关。
decltype作用以及与auto区别。
1.作用:用来获取数据类型。
代码语言:javascript复制int tempA = 2;
decltype(tempA) dclTempA;
2.decltype和auto都可以用来推断类型,但是二者有几处明显的差异。
代码语言:javascript复制auto忽略顶层const,decltype保留顶层const;
对引用操作,auto推断出原有类型,decltype推断出引用;
对解引用操作,auto推断出原有类型,decltype推断出引用;
auto推断时会实际执行,decltype不会执行,只做分析。总之在使用中过程中和const、引用和指针结合时需要特别小心。
持续更新~
欢迎与我分享你的看法。 转载请注明出处:http://taowusheng.cn/