mirror of
https://github.com/Estom/notes.git
synced 2026-04-05 03:48:56 +08:00
最后的整理
This commit is contained in:
@@ -260,12 +260,15 @@ alloc.deallocate(p,n)
|
||||
```C++
|
||||
#ifndef __JJALLOC__
|
||||
#define __JJALLOC__
|
||||
#endif
|
||||
#include<new> // for placement new
|
||||
#include<iostream> //for cerr
|
||||
#include<cstddef> //for ptrdiff_t
|
||||
#include<cstdlib> // for exit()
|
||||
#include<climits> // for UINT_MAX
|
||||
namespace JJ{
|
||||
namespace my{
|
||||
// 申请内存空间。调用operator new 。
|
||||
// T*参数是为了注册模板类型T
|
||||
template<class T>
|
||||
inline T* _allocate(ptrdiff_t size, T*){
|
||||
//set_new_handler(0);
|
||||
@@ -275,21 +278,28 @@ namespace JJ{
|
||||
}
|
||||
return tmp;
|
||||
}
|
||||
template<class T>
|
||||
inline void _destory(T* ptr){
|
||||
ptr->~T();
|
||||
}
|
||||
|
||||
|
||||
// 释放内存空间。调用operator delete
|
||||
template<class T>
|
||||
inline void _deallocate(T* buffer){
|
||||
::operator delete(buffer);
|
||||
}
|
||||
|
||||
// 创建内存对象。调用placement new
|
||||
template<class T1,class T2>
|
||||
inline void _construct(T1 *p, const T2 &value){
|
||||
new(p)T1(value);
|
||||
new (p)T1(value);
|
||||
}
|
||||
// 通过查询了解到这个操作叫做placement new,就是在指针p所指向的内存空间创建一个T1类型的对象,但是对象的内容是从T2类型的对象转换过来的(调用了T1的构造函数,T1::T1(value))。
|
||||
// 就是在已有空间的基础上重新调整分配的空间,类似于realloc函数。这个操作就是把已有的空间当成一个缓冲区来使用,这样子就减少了分配空间所耗费的时间,因为直接用new操作符分配内存的话,在堆中查找足够大的剩余空间速度是比较慢的。
|
||||
|
||||
// 释放内存对象。调用析构函数。
|
||||
template<class T>
|
||||
inline void _destroy(T* ptr){
|
||||
ptr->~T();
|
||||
}
|
||||
|
||||
|
||||
template <class T>
|
||||
class allocate{
|
||||
public:
|
||||
@@ -299,14 +309,14 @@ namespace JJ{
|
||||
typedef T& reference;
|
||||
typedef const T& const_reference;
|
||||
typedef size_t size_type;
|
||||
typedef ptrdiff_t diference_type;
|
||||
typedef ptrdiff_t difference_type;
|
||||
|
||||
template<class U>
|
||||
struct rebind{
|
||||
typedef allocator<U> other;
|
||||
};
|
||||
// template<class U>
|
||||
// struct rebind{
|
||||
// typedef allocator<U> other;
|
||||
// };
|
||||
|
||||
pointer allocate(size_type n, const void * hint = 0){
|
||||
pointer alloc(size_type n, const void * hint = 0){
|
||||
return _allocate((difference_type)n, (pointer)0);
|
||||
}
|
||||
void deallocate(pointer p, size_type n){
|
||||
@@ -332,6 +342,27 @@ namespace JJ{
|
||||
}
|
||||
};
|
||||
}
|
||||
#include<iostream>
|
||||
using namespace std;
|
||||
int main(){
|
||||
my::allocate<int> al;
|
||||
int * ptr = al.alloc(10);
|
||||
cout<<"alloc:"<<*ptr<<"\t"<<*(ptr+1)<<endl;
|
||||
al.construct(ptr,123);
|
||||
cout<<"construct:"<<*ptr<<"\t"<<*(ptr+1)<<endl;
|
||||
al.destroy(ptr);
|
||||
cout<<"destroy:"<<*ptr<<"\t"<<*(ptr+1)<<endl;
|
||||
al.deallocate(ptr,100);
|
||||
cout<<"deallocate:"<<*ptr<<"\t"<<*(ptr+1)<<endl;
|
||||
int size = al.max_size();
|
||||
cout<<"size:"<<size<<endl;
|
||||
|
||||
int* b=new int[3];
|
||||
cout<<*b<<endl;
|
||||
new (b)int(999);
|
||||
cout<<*b<<endl;
|
||||
cout<<*(b+1)<<endl;
|
||||
}
|
||||
```
|
||||
## 7 实例——shared_ptr的简单实现
|
||||
|
||||
@@ -364,9 +395,9 @@ public:
|
||||
void getcount() { return *use_count }
|
||||
};
|
||||
|
||||
// int SmartPtr<T>::operator-(SmartPtr<T> &t1, SmartPtr<T> &t2) {
|
||||
// return t1.ptr - t2.ptr;
|
||||
// }
|
||||
int SmartPtr<T>::operator-(SmartPtr<T> &t1, SmartPtr<T> &t2) {
|
||||
return t1.ptr - t2.ptr;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
SmartPtr<T>::SmartPtr(T *p)
|
||||
|
||||
117
C++/标准库/12SmartPtr.cpp
Normal file
117
C++/标准库/12SmartPtr.cpp
Normal file
@@ -0,0 +1,117 @@
|
||||
template <typename T>
|
||||
class SmartPtr
|
||||
{
|
||||
private:
|
||||
T *ptr; //底层真实的指针
|
||||
|
||||
int *use_count; //保存当前对象被多少指针引用计数
|
||||
|
||||
public:
|
||||
SmartPtr(T *p); //SmartPtr<int>p(new int(2));
|
||||
|
||||
SmartPtr(const SmartPtr<T> &orig); //SmartPtr<int>q(p);
|
||||
|
||||
SmartPtr<T> &operator=(const SmartPtr<T> &rhs); //q=p
|
||||
|
||||
~SmartPtr();
|
||||
|
||||
T operator*(); //为了能把智能指针当成普通指针操作定义解引用操作
|
||||
|
||||
T *operator->(); //定义取成员操作
|
||||
|
||||
T *operator+(int i); //定义指针加一个常数
|
||||
|
||||
int operator-(SmartPtr<T> &t1); //定义两个指针相减。当定义成友元函数的时候,必须有两个参数。当定义成类的成员函数的时候。相当于调用函数,智能有一个参数。
|
||||
|
||||
int getcount() { return *use_count; };
|
||||
};
|
||||
|
||||
|
||||
template <typename T>
|
||||
int SmartPtr<T>::operator-(SmartPtr<T> &t) {
|
||||
return ptr - t.ptr;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
SmartPtr<T>::SmartPtr(T *p)
|
||||
{
|
||||
ptr = p;
|
||||
try
|
||||
{
|
||||
use_count = new int(1);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
delete ptr; //申请失败释放真实指针和引用计数的内存
|
||||
|
||||
ptr = nullptr;
|
||||
delete use_count;
|
||||
use_count = nullptr;
|
||||
}
|
||||
}
|
||||
template <typename T>
|
||||
SmartPtr<T>::SmartPtr(const SmartPtr<T> &orig) //复制构造函数
|
||||
|
||||
{
|
||||
|
||||
use_count = orig.use_count; //引用计数保存在一块内存,所有的SmarPtr对象的引用计数都指向这里
|
||||
|
||||
this->ptr = orig.ptr;
|
||||
|
||||
++(*use_count); //当前对象的引用计数加1
|
||||
}
|
||||
template <typename T>
|
||||
SmartPtr<T> &SmartPtr<T>::operator=(const SmartPtr<T> &rhs)
|
||||
{
|
||||
//重载=运算符,例如SmartPtr<int>p,q; p=q;这个语句中,首先给q指向的对象的引用计数加1,因为p重新指向了q所指的对象,所以p需要先给原来的对象的引用计数减1,如果减一后为0,先释放掉p原来指向的内存,然后讲q指向的对象的引用计数加1后赋值给p
|
||||
|
||||
++*(rhs.use_count);
|
||||
if ((--*(use_count)) == 0)
|
||||
{
|
||||
delete ptr;
|
||||
ptr = nullptr;
|
||||
delete use_count;
|
||||
use_count = nullptr;
|
||||
}
|
||||
ptr = rhs.ptr;
|
||||
*use_count = *(rhs.use_count);
|
||||
return *this;
|
||||
}
|
||||
template <typename T>
|
||||
SmartPtr<T>::~SmartPtr()
|
||||
{
|
||||
getcount();
|
||||
if (--(*use_count) == 0) //SmartPtr的对象会在其生命周期结束的时候调用其析构函数,在析构函数中检测当前对象的引用计数是不是只有正在结束生命周期的这个SmartPtr引用,如果是,就释放掉,如果不是,就还有其他的SmartPtr引用当前对象,就等待其他的SmartPtr对象在其生命周期结束的时候调用析构函数释放掉
|
||||
|
||||
{
|
||||
getcount();
|
||||
delete ptr;
|
||||
ptr = nullptr;
|
||||
delete use_count;
|
||||
use_count = nullptr;
|
||||
}
|
||||
}
|
||||
template <typename T>
|
||||
T SmartPtr<T>::operator*()
|
||||
{
|
||||
return *ptr;
|
||||
}
|
||||
template <typename T>
|
||||
T *SmartPtr<T>::operator->()
|
||||
{
|
||||
return ptr;
|
||||
}
|
||||
template <typename T>
|
||||
T *SmartPtr<T>::operator+(int i)
|
||||
{
|
||||
T *temp = ptr + i;
|
||||
return temp;
|
||||
}
|
||||
|
||||
#include<iostream>
|
||||
using namespace std;
|
||||
int main(){
|
||||
SmartPtr<int> ptr(new int(123));
|
||||
cout<<*ptr<<endl;
|
||||
return 0;
|
||||
}
|
||||
105
C++/标准库/12allocate.cpp
Normal file
105
C++/标准库/12allocate.cpp
Normal file
@@ -0,0 +1,105 @@
|
||||
#ifndef __JJALLOC__
|
||||
#define __JJALLOC__
|
||||
#endif
|
||||
#include<new> // for placement new
|
||||
#include<iostream> //for cerr
|
||||
#include<cstddef> //for ptrdiff_t
|
||||
#include<cstdlib> // for exit()
|
||||
#include<climits> // for UINT_MAX
|
||||
namespace my{
|
||||
// 申请内存空间。调用operator new 。
|
||||
// T*参数是为了注册模板类型T
|
||||
template<class T>
|
||||
inline T* _allocate(ptrdiff_t size, T*){
|
||||
//set_new_handler(0);
|
||||
T* tmp = (T*)(::operator new)((size_t)(size * sizeof(T)));
|
||||
if (tmp == 0){
|
||||
std::cerr << "out of memory" << std::endl;
|
||||
}
|
||||
return tmp;
|
||||
}
|
||||
|
||||
// 释放内存空间。调用operator delete
|
||||
template<class T>
|
||||
inline void _deallocate(T* buffer){
|
||||
::operator delete(buffer);
|
||||
}
|
||||
|
||||
// 创建内存对象。调用placement new
|
||||
template<class T1,class T2>
|
||||
inline void _construct(T1 *p, const T2 &value){
|
||||
new (p)T1(value);
|
||||
}
|
||||
// 通过查询了解到这个操作叫做placement new,就是在指针p所指向的内存空间创建一个T1类型的对象,但是对象的内容是从T2类型的对象转换过来的(调用了T1的构造函数,T1::T1(value))。
|
||||
// 就是在已有空间的基础上重新调整分配的空间,类似于realloc函数。这个操作就是把已有的空间当成一个缓冲区来使用,这样子就减少了分配空间所耗费的时间,因为直接用new操作符分配内存的话,在堆中查找足够大的剩余空间速度是比较慢的。
|
||||
|
||||
// 释放内存对象。调用析构函数。
|
||||
template<class T>
|
||||
inline void _destroy(T* ptr){
|
||||
ptr->~T();
|
||||
}
|
||||
|
||||
|
||||
template <class T>
|
||||
class allocate{
|
||||
public:
|
||||
typedef T value_type;
|
||||
typedef T* pointer;
|
||||
typedef const T* const_pointer;
|
||||
typedef T& reference;
|
||||
typedef const T& const_reference;
|
||||
typedef size_t size_type;
|
||||
typedef ptrdiff_t difference_type;
|
||||
|
||||
// template<class U>
|
||||
// struct rebind{
|
||||
// typedef allocator<U> other;
|
||||
// };
|
||||
|
||||
pointer alloc(size_type n, const void * hint = 0){
|
||||
return _allocate((difference_type)n, (pointer)0);
|
||||
}
|
||||
void deallocate(pointer p, size_type n){
|
||||
_deallocate(p);
|
||||
}
|
||||
|
||||
void construct(pointer p, const_reference value){
|
||||
return _construct(p, value);
|
||||
}
|
||||
|
||||
void destroy(pointer p){
|
||||
_destroy(p);
|
||||
}
|
||||
pointer address(reference x){
|
||||
return (pointer)&x;
|
||||
}
|
||||
pointer const_address(const_reference x){
|
||||
return (const_pointer)&x;
|
||||
}
|
||||
|
||||
size_type max_size()const{
|
||||
return (size_type)(UINT_MAX / sizeof(T));
|
||||
}
|
||||
};
|
||||
}
|
||||
#include<iostream>
|
||||
using namespace std;
|
||||
int main(){
|
||||
my::allocate<int> al;
|
||||
int * ptr = al.alloc(10);
|
||||
cout<<"alloc:"<<*ptr<<"\t"<<*(ptr+1)<<endl;
|
||||
al.construct(ptr,123);
|
||||
cout<<"construct:"<<*ptr<<"\t"<<*(ptr+1)<<endl;
|
||||
al.destroy(ptr);
|
||||
cout<<"destroy:"<<*ptr<<"\t"<<*(ptr+1)<<endl;
|
||||
al.deallocate(ptr,100);
|
||||
cout<<"deallocate:"<<*ptr<<"\t"<<*(ptr+1)<<endl;
|
||||
int size = al.max_size();
|
||||
cout<<"size:"<<size<<endl;
|
||||
|
||||
int* b=new int[3];
|
||||
cout<<*b<<endl;
|
||||
new (b)int(999);
|
||||
cout<<*b<<endl;
|
||||
cout<<*(b+1)<<endl;
|
||||
}
|
||||
@@ -13,17 +13,46 @@
|
||||
|
||||
补充观点:字节对齐主要是为了提高内存的访问效率,比如intel 32位cpu,每个总线周期都是从偶地址开始读取32位的内存数据,如果数据存放地址不是从偶数开始,则可能出现需要两个总线周期才能读取到想要的数据,因此需要在内存中存放数据时进行对齐。
|
||||
|
||||
|
||||
例如64位操作系统,存储32位int值。如果两个int值占用同一个64位的寻址空间,那么方位第二个int值,需要两次寻址过程。首先找到地址,然后得到偏移。
|
||||
|
||||
|
||||
### 规则
|
||||
|
||||
每个特定平台上的编译器都有自己的默认“对齐系数”(也叫对齐模数)。程序员可以通过预编译命令#pragma pack(n),n=1,2,4,8,16来改变这一系数,其中的n就是你要指定的“对齐系数”。
|
||||
|
||||
1. 数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员的对齐按照#pragma pack指定的数值和这个数据成员自身长度中,比较小的那个进行。
|
||||
2. 结构(或联合)的整体对齐规则:在数据成员完成各自对齐之后,结构(或联合)本身也要进行对齐,对齐将按照#pragma pack指定的数值和结构(或联合)最大数据成员长度中,比较小的那个进行。
|
||||
3. 结合1、2可推断:当#pragma pack的n值等于或超过所有数据成员长度的时候,这个n值的大小将不产生任何效果。
|
||||
3. 结构体作为成员:如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储。
|
||||
4. 结合1、2可推断:当#pragma pack的n值等于或超过所有数据成员长度的时候,这个n值的大小将不产生任何效果。
|
||||
|
||||
|
||||
### 结构体对齐规则
|
||||
|
||||
1. 结构体变量的起始地址能够被其最宽的成员大小整除
|
||||
2. 结构体每个成员相对于起始地址的偏移能够被其自身大小整除,如果不能则在前一个成员后面补充字节
|
||||
3. 结构体总体大小能够被最宽的成员的大小整除,如不能则在后面补充字节
|
||||
3. 结构体总体大小能够被最宽的成员的大小整除,如不能则在后面补充字节
|
||||
|
||||
|
||||
|
||||
### 举例
|
||||
|
||||
可以通过预编译命令#pragma pack(n),n=1,2,4,8,16来改变这一系数,其中的n就是指定的“对齐系数”。
|
||||
|
||||
```
|
||||
#pragma pack(2)
|
||||
|
||||
struct AA {
|
||||
|
||||
int a; //长度4 > 2 按2对齐;偏移量为0;存放位置区间[0,3]
|
||||
|
||||
char b; //长度1 < 2 按1对齐;偏移量为4;存放位置区间[4]
|
||||
|
||||
short c; //长度2 = 2 按2对齐;偏移量要提升到2的倍数6;存放位置区间[6,7]
|
||||
|
||||
char d; //长度1 < 2 按1对齐;偏移量为7;存放位置区间[8];共九个字节
|
||||
|
||||
};
|
||||
|
||||
#pragma pack()
|
||||
```
|
||||
23
C++/面试/19.cpp
Normal file
23
C++/面试/19.cpp
Normal file
@@ -0,0 +1,23 @@
|
||||
#include<iostream>
|
||||
|
||||
using namespace std;
|
||||
|
||||
// 测试结构体内存对齐
|
||||
struct data
|
||||
{
|
||||
char a='1';
|
||||
char b='2';
|
||||
char c='3';
|
||||
char h='5';
|
||||
char e='6';
|
||||
int d = 4;
|
||||
|
||||
};
|
||||
|
||||
int main(){
|
||||
struct data d;
|
||||
cout<<sizeof(d)<<endl;//=12
|
||||
cout<<&d.a<<endl;
|
||||
cout<<d.d<<endl;
|
||||
return 0;
|
||||
}
|
||||
@@ -24,4 +24,145 @@
|
||||
|
||||
4. 字符串中指定两个字符的交换
|
||||
|
||||
5. 查找某字符串是否位于指定的字符串中(采用暴力查找)
|
||||
5. 查找某字符串是否位于指定的字符串中(采用暴力查找)
|
||||
|
||||
|
||||
## 实现
|
||||
```C++
|
||||
/*
|
||||
* C++ string 类的实现
|
||||
* 1. 构造函数和析构函数
|
||||
* 2. 字符串长度
|
||||
* 3. 重载=运算符
|
||||
* 4. 重载+=运算符
|
||||
* 5. 重载<< >> 运算符
|
||||
* 6. 重载比较运算符
|
||||
* 7. 重载[]下标运算符
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
#include <cstring>
|
||||
using namespace std;
|
||||
|
||||
class MyString
|
||||
{
|
||||
private:
|
||||
char * str;
|
||||
int length;
|
||||
public:
|
||||
// 长度
|
||||
int size ()const {
|
||||
return length;
|
||||
};
|
||||
char* getstr()const{
|
||||
return str;
|
||||
}
|
||||
// 默认构造函数
|
||||
MyString();
|
||||
// 字符串构造函数
|
||||
MyString(const char*);
|
||||
// 复制构造函数
|
||||
MyString(const MyString& b);
|
||||
|
||||
// 重载等号运算符
|
||||
MyString& operator=(const MyString &b);
|
||||
// 重载+=运算符
|
||||
MyString& operator+=(const MyString &b);
|
||||
// 重载比较运算符
|
||||
bool operator<(const MyString &b);
|
||||
// 重载下标运算符
|
||||
char& operator[](const int &index) const ;
|
||||
// 重载输入输出操作
|
||||
friend ostream& operator<<(ostream& ,const MyString &b);
|
||||
~MyString();
|
||||
};
|
||||
|
||||
MyString::MyString()
|
||||
{
|
||||
str = new char[1];
|
||||
str[0]='\0';
|
||||
length = 0;
|
||||
}
|
||||
|
||||
MyString::MyString(const char* b){
|
||||
if(b){
|
||||
length = strlen(b);
|
||||
str = new char[length+1];
|
||||
strcpy(str,b);
|
||||
}
|
||||
else{
|
||||
MyString();
|
||||
}
|
||||
}
|
||||
MyString::MyString(const MyString&b){
|
||||
length = b.size();
|
||||
if(length>0)
|
||||
str = new char[length+1];
|
||||
else
|
||||
MyString();
|
||||
}
|
||||
|
||||
MyString& MyString::operator=(const MyString &b){
|
||||
if(&b == this){
|
||||
return *this;
|
||||
}
|
||||
delete[] str;
|
||||
length = b.size();
|
||||
str = new char[length + 1];
|
||||
strcpy(str,b.getstr());
|
||||
return *this;
|
||||
}
|
||||
|
||||
MyString& MyString::operator+=(const MyString&b){
|
||||
if(b.size()==0){
|
||||
return *this;
|
||||
}
|
||||
char* temp = new char[length+b.length+1];
|
||||
strcpy(temp,str);
|
||||
strcat(temp,b.getstr());
|
||||
delete[] str;
|
||||
str = temp;
|
||||
return *this;
|
||||
}
|
||||
|
||||
char& MyString::operator[](const int &index)const {
|
||||
if(index>length)return str[length];
|
||||
return str[index];
|
||||
}
|
||||
|
||||
bool MyString::operator<(const MyString &b){
|
||||
for(int i=0;i<length;i++){
|
||||
if(i>b.size())return false;
|
||||
if(b[i]>str[i])return true;
|
||||
if(b[i]<str[i])return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
MyString::~MyString()
|
||||
{
|
||||
delete[] str;
|
||||
}
|
||||
|
||||
// 外部定义一个函数,内部声明为友元
|
||||
ostream& operator<<(ostream &out,const MyString&b){
|
||||
out<<b.getstr();
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
int main()
|
||||
{
|
||||
// 测试函数
|
||||
MyString s1,s2="123",s3,s4="456";
|
||||
s3=s2;
|
||||
s1=s2;
|
||||
s1+=s1;
|
||||
cout<<s1<<endl;
|
||||
cout<<s2<<endl;
|
||||
cout<<s3<<endl;
|
||||
cout<<(s3<s4)<<endl;
|
||||
cout<<endl;
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
@@ -1,7 +1,14 @@
|
||||
|
||||
## 1 字符串和字符数组区别
|
||||
### const char * arr = "123";
|
||||
|
||||
### const char * arr = "123"; char * brr = "123"; const char crr[] = "123"; char drr[] = "123";
|
||||
### char * brr = "123";
|
||||
|
||||
### const char crr[] = "123";
|
||||
|
||||
### char drr[] = "123";
|
||||
|
||||
```C++
|
||||
const char * arr = "123";
|
||||
//字符串123保存在常量区,const本来是修饰arr指向的值不能通过arr去修改,但是字符串“123”在常量区,本来就不能改变,所以加不加const效果都一样
|
||||
|
||||
@@ -15,4 +22,9 @@ const char crr[] = "123";
|
||||
|
||||
char drr[] = "123";
|
||||
|
||||
//字符串123保存在栈区,可以通过drr去修改
|
||||
//字符串123保存在栈区,可以通过drr去修改
|
||||
```
|
||||
|
||||
|
||||
## 字符串和字符数组作为参数
|
||||
|
||||
265
C++/面试/22 new&delete.md
Normal file
265
C++/面试/22 new&delete.md
Normal file
@@ -0,0 +1,265 @@
|
||||
# C++中的new、operator new与placement new
|
||||
> 参考文献
|
||||
> * [https://www.cnblogs.com/luxiaoxun/archive/2012/08/10/2631812.html](https://www.cnblogs.com/luxiaoxun/archive/2012/08/10/2631812.html)
|
||||
> * [https://blog.csdn.net/linuxheik/article/details/80449059](https://blog.csdn.net/linuxheik/article/details/80449059)
|
||||
|
||||
|
||||
> new operator/delete operator就是new和delete操作符。而operator new/operator delete是全局函数。
|
||||
|
||||
|
||||
## 1 C++中的new/delete
|
||||
|
||||
new operator就是new操作符,**不能被重载**,假如A是一个类,那么A * a=new A;实际上执行如下3个过程。
|
||||
1. 调用operator new分配内存,operator new (sizeof(A))
|
||||
2. 调用构造函数生成类对象,A::A()
|
||||
3. 返回相应指针
|
||||
|
||||
|
||||
## 2 operator new/operator delete
|
||||
### 三种形式
|
||||
operator new是函数,分为三种形式(前2种不调用构造函数,这点区别于new operator):
|
||||
```C++
|
||||
void* operator new (std::size_t size) throw (std::bad_alloc);
|
||||
void* operator new (std::size_t size, const std::nothrow_t& nothrow_constant) throw();
|
||||
void* operator new (std::size_t size, void* ptr) throw();
|
||||
```
|
||||
1. 第一种分配size个字节的存储空间,并将对象类型进行内存对齐。如果成功,返回一个非空的指针指向首地址。失败抛出bad_alloc异常。
|
||||
2. 第二种在分配失败时不抛出异常,它返回一个NULL指针。
|
||||
3. 第三种是placement new版本,它本质上是对operator new的重载,定义于#include <new>中。它不分配内存,调用合适的构造函数在ptr所指的地方构造一个对象,之后返回实参指针ptr。
|
||||
|
||||
### 重载operator new
|
||||
第一、第二个版本可以被用户重载,定义自己的版本,第三种placement new不可重载。
|
||||
|
||||
1. 重载时,返回类型必须声明为void*
|
||||
2. 重载时,第一个参数类型必须为表达要求分配空间的大小(字节),类型为size_t
|
||||
3. 重载时,可以带其它参数
|
||||
|
||||
|
||||
### 实例
|
||||
```C++
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
using namespace std;
|
||||
|
||||
class X
|
||||
{
|
||||
public:
|
||||
X() { cout<<"constructor of X"<<endl; }
|
||||
~X() { cout<<"destructor of X"<<endl;}
|
||||
|
||||
void* operator new(size_t size,string str)
|
||||
{
|
||||
cout<<"operator new size "<<size<<" with string "<<str<<endl;
|
||||
return ::operator new(size);
|
||||
}
|
||||
|
||||
void operator delete(void* pointee)
|
||||
{
|
||||
cout<<"operator delete"<<endl;
|
||||
::operator delete(pointee);
|
||||
}
|
||||
private:
|
||||
int num;
|
||||
};
|
||||
|
||||
int main()
|
||||
{
|
||||
X *px = new("A new class") X;
|
||||
delete px;
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
* X* px = new X; //该行代码中的new为new operator,它将调用类X中的operator new,为该类的对象分配空间,然后调用当前实例的构造函数。
|
||||
* delete px; //该行代码中的delete为delete operator,它将调用该实例的析构函数,然后调用类X中的operator delete,以释放该实例占用的空间。
|
||||
* new operator与delete operator的行为是不能够也不应该被改变,这是C++标准作出的承诺。而operator new与operator delete和C语言中的malloc与free对应,只负责分配及释放空间。但使用operator new分配的空间必须使用operator delete来释放,而不能使用free,因为它们对内存使用的登记方式不同。反过来亦是一样。你可以重载operator new和operator delete以实现对内存管理的不同要求,但你不能重载new operator或delete operator以改变它们的行为。
|
||||
|
||||
### 为什么有必要写自己的operator new和operator delete?
|
||||
|
||||
* 答案通常是:为了效率。缺省的operator new和operator delete具有非常好的通用性,它的这种灵活性也使得在某些特定的场合下,可以进一步改善它的性能。尤其在那些需要动态分配大量的但很小的对象的应用程序里,情况更是如此。具体可参考《Effective C++》中的第二章内存管理。
|
||||
|
||||
## 3 Placement new
|
||||
|
||||
### 概念
|
||||
* placement new 是重载operator new 的一个标准、全局的版本,它不能够被自定义的版本重载
|
||||
```
|
||||
void *operator new( size_t, void * p ) throw() { return p; }
|
||||
```
|
||||
* placement new返还第二个参数。其结果是允许用户把一个对象放到一个特定的地方,达到调用构造函数的效果。
|
||||
|
||||
```
|
||||
Widget * p = new Widget; //ordinary new
|
||||
pi = new (ptr) int; //placement new
|
||||
```
|
||||
* 括号里的参数ptr是一个指针,它指向一个内存缓冲器,placement new将在这个缓冲器上分配一个对象。这个内存缓冲器可以在堆上也可以在栈上。
|
||||
* Placement new的返回值是这个被构造对象的地址(比如括号中的传递参数)。
|
||||
|
||||
|
||||
### 应用场景
|
||||
1. 在对时间要求非常高的应用程序中,因为这些程序分配的时间是确定的;
|
||||
2. 长时间运行而不被打断的程序;
|
||||
3. 以及执行一个垃圾收集器 (garbage collector)。
|
||||
|
||||
|
||||
### Placement new 存在的理由
|
||||
|
||||
1. 用placement new 解决buffer的问题。问题描述:用new分配的数组缓冲时,由于调用了默认构造函数,因此执行效率上不佳。若没有默认构造函数则会发生编译时错误。如果你想在预分配的内存上创建对象,用缺省的new操作符是行不通的。要解决这个问题,你可以用placement new构造。它允许你构造一个新对象到预分配的内存上。
|
||||
|
||||
2. 增大时空效率的问题。使用new操作符分配内存需要在堆中查找足够大的剩余空间,显然这个操作速度是很慢的,而且有可能出现无法分配内存的异常(空间不够)。placement new就可以解决这个问题。我们构造对象都是在一个预先准备好了的内存缓冲区中进行,不需要查找内存,内存分配的时间是常数;而且不会出现在程序运行中途出现内存不足的异常。所以,placement new非常适合那些对时间要求比较高,长时间运行不希望被打断的应用程序。
|
||||
### 实例
|
||||
```C++
|
||||
#include <iostream>
|
||||
using namespace std;
|
||||
|
||||
class A
|
||||
{
|
||||
public:
|
||||
A()
|
||||
{
|
||||
cout << "A's constructor" << endl;
|
||||
}
|
||||
|
||||
|
||||
~A()
|
||||
{
|
||||
cout << "A's destructor" << endl;
|
||||
}
|
||||
|
||||
void show()
|
||||
{
|
||||
cout << "num:" << num << endl;
|
||||
}
|
||||
|
||||
private:
|
||||
int num;
|
||||
};
|
||||
|
||||
int main()
|
||||
{
|
||||
char mem[100];
|
||||
mem[0] = 'A';
|
||||
mem[1] = '\0';
|
||||
mem[2] = '\0';
|
||||
mem[3] = '\0';
|
||||
cout << (void*)mem << endl;
|
||||
A* p = new (mem)A;
|
||||
cout << p << endl;
|
||||
p->show();
|
||||
p->~A();
|
||||
getchar();
|
||||
}
|
||||
```
|
||||
|
||||
1. 用定位放置new操作,既可以在栈(stack)上生成对象,也可以在堆(heap)上生成对象。如本例就是在栈上生成一个对象。
|
||||
2. 使用语句A* p=new (mem) A;定位生成对象时,指针p和数组名mem指向同一片存储区。所以,与其说定位放置new操作是申请空间,还不如说是利用已经请好的空间,真正的申请空间的工作是在此之前完成的。
|
||||
3. 使用语句A *p=new (mem) A;定位生成对象时,会自动调用类A的构造函数,但是由于对象的空间不会自动释放(对象实际上是借用别人的空间),所以必须显示的调用类的析构函数,如本例中的p->~A()。
|
||||
|
||||
## 4 new 、operator new 和 placement new 区别
|
||||
|
||||
### 1 new :不能被重载,其行为总是一致的。它先调用operator new分配内存,然后调用构造函数初始化那段内存。
|
||||
|
||||
new 操作符的执行过程:
|
||||
1. 调用operator new分配内存 ;
|
||||
2. 调用构造函数生成类对象;
|
||||
3. 返回相应指针。
|
||||
|
||||
### 4 operator new:要实现不同的内存分配行为,应该重载operator new,而不是new。
|
||||
|
||||
* operator new就像operator + 一样,是可以重载的。如果类中没有重载operator new,那么调用的就是全局的::operator new来完成堆的分配。同理,operator new[]、operator delete、operator delete[]也是可以重载的。
|
||||
|
||||
* placement new:只是operator new重载的一个版本。它并不分配内存,只是返回指向已经分配好的某段内存的一个指针。因此不能删除它,但需要调用对象的析构函数。
|
||||
|
||||
* 如果你想在已经分配的内存中创建一个对象,使用new时行不通的。也就是说placement new允许你在一个已经分配好的内存中(栈或者堆中)构造一个新的对象。原型中void* p实际上就是指向一个已经分配好的内存缓冲区的的首地址。
|
||||
|
||||
|
||||
## 5 实例——Placement new使用步骤
|
||||
|
||||
在很多情况下,placement new的使用方法和其他普通的new有所不同。这里提供了它的使用步骤。
|
||||
|
||||
### 第一步 缓存提前分配
|
||||
|
||||
有三种方式:
|
||||
|
||||
1. 为了保证通过placement new使用的缓存区的memory alignment(内存队列)正确准备,使用普通的new来分配它:在堆上进行分配
|
||||
|
||||
```
|
||||
class Task ;
|
||||
char * buff = new [sizeof(Task)]; //分配内存
|
||||
(请注意auto或者static内存并非都正确地为每一个对象类型排列,所以,你将不能以placement new使用它们。)
|
||||
```
|
||||
|
||||
1. 在栈上进行分配
|
||||
```
|
||||
class Task ;
|
||||
char buf[N*sizeof(Task)]; //分配内存
|
||||
```
|
||||
3. 还有一种方式,就是直接通过地址来使用。(必须是有意义的地址)
|
||||
```
|
||||
void* buf = reinterpret_cast<void*> (0xF00F);
|
||||
```
|
||||
### 第二步:对象的分配
|
||||
|
||||
在刚才已分配的缓存区调用placement new来构造一个对象。
|
||||
```
|
||||
Task *ptask = new (buf) Task
|
||||
```
|
||||
### 第三步:使用
|
||||
|
||||
按照普通方式使用分配的对象:
|
||||
```
|
||||
ptask->memberfunction();
|
||||
|
||||
ptask-> member;
|
||||
|
||||
//...
|
||||
```
|
||||
### 第四步:对象的析构
|
||||
|
||||
一旦你使用完这个对象,你必须调用它的析构函数来毁灭它。按照下面的方式调用析构函数:
|
||||
```
|
||||
ptask->~Task(); //调用外在的析构函数
|
||||
```
|
||||
### 第五步:释放
|
||||
|
||||
你可以反复利用缓存并给它分配一个新的对象(重复步骤2,3,4)如果你不打算再次使用这个缓存,你可以象这样释放它:delete [] buf;
|
||||
|
||||
跳过任何步骤就可能导致运行时间的崩溃,内存泄露,以及其它的意想不到的情况。如果你确实需要使用placement new,请认真遵循以上的步骤。
|
||||
|
||||
### 代码实现
|
||||
```C++
|
||||
#include <iostream>
|
||||
using namespace std;
|
||||
|
||||
class X
|
||||
{
|
||||
public:
|
||||
X() { cout<<"constructor of X"<<endl; }
|
||||
~X() { cout<<"destructor of X"<<endl;}
|
||||
|
||||
void SetNum(int n)
|
||||
{
|
||||
num = n;
|
||||
}
|
||||
|
||||
int GetNum()
|
||||
{
|
||||
return num;
|
||||
}
|
||||
|
||||
private:
|
||||
int num;
|
||||
};
|
||||
|
||||
int main()
|
||||
{
|
||||
char* buf = new char[sizeof(X)];
|
||||
X *px = new(buf) X;
|
||||
px->SetNum(10);
|
||||
cout<<px->GetNum()<<endl;
|
||||
px->~X();
|
||||
delete []buf;
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
42
C++/面试/23 数组参数.md
Normal file
42
C++/面试/23 数组参数.md
Normal file
@@ -0,0 +1,42 @@
|
||||
|
||||
## 数组参数
|
||||
|
||||
### 三种形式
|
||||
|
||||
void test1(int *s)
|
||||
void test2(int s[])
|
||||
void test3(int s[5])
|
||||
|
||||
|
||||
### 实例
|
||||
|
||||
```
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
using namespace std;
|
||||
|
||||
// 测试字符串和字符数组的参数传递
|
||||
void test1(int *s){
|
||||
cout<<*(s)<<endl;
|
||||
return;
|
||||
}
|
||||
void test2(int s[]){
|
||||
cout<<*s<<endl;
|
||||
return;
|
||||
}
|
||||
void test3(int s[5]){
|
||||
cout<<*s<<endl;
|
||||
return;
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
int s1[]={1,2,3,4,5,6,7,8};
|
||||
int s2[3]={4,5,6};
|
||||
int* s3 = new int(999);
|
||||
test1(s3);
|
||||
test2(s3);
|
||||
test3(s3);
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
28
C++/面试/23.cpp
Normal file
28
C++/面试/23.cpp
Normal file
@@ -0,0 +1,28 @@
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
using namespace std;
|
||||
|
||||
// 测试字符串和字符数组的参数传递
|
||||
void test1(int *s){
|
||||
cout<<*(s)<<endl;
|
||||
return;
|
||||
}
|
||||
void test2(int s[]){
|
||||
cout<<*s<<endl;
|
||||
return;
|
||||
}
|
||||
void test3(int s[5]){
|
||||
cout<<*s<<endl;
|
||||
return;
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
int s1[]={1,2,3,4,5,6,7,8};
|
||||
int s2[3]={4,5,6};
|
||||
int* s3 = new int(999);
|
||||
test1(s3);
|
||||
test2(s3);
|
||||
test3(s3);
|
||||
return 0;
|
||||
}
|
||||
@@ -117,7 +117,7 @@
|
||||
在笔记本上的阶段性安排:
|
||||
1. 第六周:机器学习技术栈的全部完成
|
||||
2. 第七周:基本数据处理-工程实现完成
|
||||
3. 第八周:论文的阅读和复现工作。包括联邦学习和恶意软件机器学习。
|
||||
3. 第八周:彻底接受学弟的论文工作内容。完成基础三篇论文的复现工作。
|
||||
|
||||
## 收获
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
## 计划
|
||||
|
||||
|
||||
* 第九周(4.26-5.2)完成论文阅读计划
|
||||
* 第九周(4.26-5.2)完成论文阅读计划。
|
||||
* 第十周(5.3-5.9)完成论文复现计划1——联邦学习论文复现
|
||||
* 第十一周(5.10-5.16)完成论文复现计划2——恶意软件论文复现
|
||||
* 第十二周(5.17-5.23)完成论文复现计划3——联邦学习+恶意软件
|
||||
|
||||
13
工作日志/2021年4月20日-今日计划.md
Normal file
13
工作日志/2021年4月20日-今日计划.md
Normal file
@@ -0,0 +1,13 @@
|
||||
## 安排
|
||||
|
||||
> 第八周任务:接受学弟的论文工作内容。完成基础三篇论文的复现工作。
|
||||
|
||||
- [ ] 打理好生活(洗衣服、洗澡、准备开始跑步)。吃饭时间会宿舍洗衣服洗澡。
|
||||
- [ ] 面试中相关问题的处理。协程?数据库性能优化?C++性能优化?服务器性能优化。
|
||||
- [ ] 晚上回宿舍洗衣服。
|
||||
- [ ] 完成之前所有的任务。跑通TensorFlow。
|
||||
- [ ] 完成pysyft框架学习
|
||||
- [ ] 完成TensorFlow federated框架的学习。
|
||||
|
||||
|
||||
## 收获
|
||||
@@ -17,7 +17,7 @@
|
||||
- [x] 2021年4月8日16:00 1面。准备以上内容。
|
||||
- [x] 2021年4月9日10:30 1面。面试改到北京了
|
||||
- [x] 2021-04-13 10:30 2面。面试,项目与基础知识
|
||||
- [ ] 04-18 20:00:00 -- 22:00:00 笔试
|
||||
- [x] ~~04-18 20:00:00 -- 22:00:00 笔试~~
|
||||
> 但是TMD之前的面试进度还在。没办法参加第二次面试了,早知道,直接换个事业群,换一波人说不定还好说话。妈卖批。别是上一个boss
|
||||
|
||||
## ~~商汤科技~~
|
||||
@@ -73,7 +73,7 @@
|
||||
- [x] 简历投递https://campus.alibaba.com/myJobApply.htm
|
||||
- [x] 素质测评
|
||||
- [x] 2021年04月09日 19:00 - 2021年04月09日 20:00。完球了没人捞我的专利,待会问问师兄。
|
||||
- [ ] 2021年4月19日 16:00 一面。终于还是来了。还是要好好准备的。把所有的问题复习一遍。估计很难进。
|
||||
- [x] 2021年4月19日 16:00 一面。终于还是来了。还是要好好准备的。把所有的问题复习一遍。估计很难进。
|
||||
|
||||
> 二期简历
|
||||
|
||||
@@ -89,7 +89,7 @@
|
||||
4. 对数据结构、算法有一定了解;
|
||||
5. 优选条件:熟悉TCP/IP协议及互联网常见应用和协议的原理;有IT应用软件、互联网软件、IOS/安卓等相关产品开发经验,不满足于课堂所学,在校期间积极参加校内外软件编程大赛或积极参于编程开源社区组织;熟悉JS/AS/AJAX/HTML5/CSS等前端开发技术。
|
||||
* 流程
|
||||
- [ ] 简历投递https://career.huawei.com/reccampportal/portal5/user-index.html
|
||||
- [x] 简历投递https://career.huawei.com/reccampportal/portal5/user-index.html
|
||||
|
||||
|
||||
## 美团
|
||||
@@ -101,7 +101,7 @@
|
||||
4. 优秀的逻辑思维能力,特别是流程梳理能力和建模能力,善于从复杂系统表象中分析问题。具有较强的解决问题能力,对解决复杂问题充满激情;
|
||||
5. 善于交流,有良好的团队合作精神和协调沟通能力,有一定推动能力。
|
||||
* 流程
|
||||
- [ ] 简历投递https://campus.meituan.com/apply-record
|
||||
- [x] 简历投递https://campus.meituan.com/apply-record
|
||||
|
||||
## 快手
|
||||
|
||||
@@ -115,6 +115,7 @@
|
||||
6. 具有良好的沟通能力和团队合作精神、优秀的分析问题和解决问题的能力。
|
||||
* 流程
|
||||
- [x] 简历投递https://zhaopin.kuaishou.cn/recruit/e/#/official/my-apply/
|
||||
- [x] 2021-04-19 18:00:00 1面
|
||||
## 网易
|
||||
* 岗位:C++开发实习生
|
||||
* 岗位要求
|
||||
@@ -125,5 +126,5 @@
|
||||
5. 十八般武艺样样精通,掌握多线程并发编程技术,掌握各种数据结构和算法;
|
||||
6. 熟悉windows/linux编程环境,如果有MySQL开发经验那就再 好 不 过 啦!!!
|
||||
* 流程
|
||||
- [ ] 简历投递https://campus.163.com/app/personal/apply
|
||||
- [x] 简历投递https://campus.163.com/app/personal/apply
|
||||
|
||||
|
||||
@@ -133,4 +133,6 @@
|
||||

|
||||
|
||||
2. 索引方式系统根据所有进程的状态建立几张索引表。例如,就绪索引表、阻塞索引表等。
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
@@ -61,3 +61,5 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# 互斥朗、信号量、条件变量.md
|
||||
# 互斥朗、信号量、条件变量
|
||||
|
||||
> 参考文献
|
||||
> * [http://blog.chinaunix.net/uid-20205875-id-4865684.html](http://blog.chinaunix.net/uid-20205875-id-4865684.html)
|
||||
@@ -121,7 +121,7 @@ int pthread_mutex_unlock(pthread_mutex_t *mutex);
|
||||
* 读写锁可以由三种状态:读模式下加锁状态、写模式下加锁状态、不加锁状态。一次只有一个线程可以占有写模式的读写锁,但是多个线程可以同时占有读模式的读写锁。
|
||||
* 在读写锁是写加锁状态时,在这个锁被解锁之前,所有试图对这个锁加锁的线程都会被阻塞。当读写锁在读加锁状态时,所有试图以读模式对它进行加锁的线程都可以得到访问权,但是如果线程希望以写模式对此锁进行加锁,它必须阻塞直到所有的线程释放读锁。虽然读写锁的实现各不相同,但当读写锁处于读模式锁住状态时,如果有另外的线程试图以写模式加锁,读写锁通常会阻塞随后的读模式锁请求。这样可以避免读模式锁长期占用,而等待的写模式锁请求一直得不到满足。
|
||||
* 读写锁非常适合于对数据结构读的次数远大于写的情况。当读写锁在写模式下时,它所保护的数据结构就可以被安全地修改,因为当前只有一个线程可以在写模式下拥有这个锁。当读写锁在读状态下时,只要线程获取了读模式下的读写锁,该锁所保护的数据结构可以被多个获得读模式锁的线程读取。
|
||||
* 读写锁也叫做共享-独占锁,当读写锁以读模式锁住时,它是以共享模式锁住的;当他以写模式锁住时,它是以独占模式锁住的。
|
||||
* **读写锁也叫做共享-独占锁**,当读写锁以读模式锁住时,它是以**共享模式锁住**的;当他以写模式锁住时,它是以**独占模式锁住**的。
|
||||
|
||||
### 实现——读写锁
|
||||
> 初始化和销毁
|
||||
@@ -228,8 +228,3 @@ int pthread_cond_broadcast(pthread_cond_t *cond);
|
||||
* 这两个函数用于通知线程条件已经满足. 调用这两个函数, 也称向线程或条件发送信号. 必须注意, 一定要在改变条件状态以后再给线程发送信号.
|
||||
|
||||
|
||||
## 5 管程
|
||||
|
||||
|
||||
## 6 协程
|
||||
|
||||
|
||||
30
操作系统/附录5 协程.md
Normal file
30
操作系统/附录5 协程.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# 协程
|
||||
|
||||
## 1 概念
|
||||
|
||||
协程,又称微线程,纤程,英文名Coroutine。协程看上去也是子程序,但执行过程中,在子程序内部可中断,然后转而执行别的子程序,在适当的时候再返回来接着执行。
|
||||
|
||||
例如:
|
||||
```
|
||||
def A() :
|
||||
print '1'
|
||||
print '2'
|
||||
print '3'
|
||||
def B() :
|
||||
print 'x'
|
||||
print 'y'
|
||||
print 'z'
|
||||
```
|
||||
由协程运行结果可能是12x3yz。在执行A的过程中,可以随时中断,去执行B,B也可能在执行过程中中断再去执行A。但协程的特点在于是一个线程执行。
|
||||
|
||||
## 2 区别
|
||||
|
||||
1. 那和多线程比,协程最大的优势就是协程极高的执行效率。因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显。
|
||||
|
||||
2. 第二大优势就是不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。
|
||||
|
||||
## 3 其他
|
||||
|
||||
在协程上利用多核CPU呢——多进程+协程,既充分利用多核,又充分发挥协程的高效率,可获得极高的性能。
|
||||
|
||||
Python对协程的支持还非常有限,用在generator中的yield可以一定程度上实现协程。虽然支持不完全,但已经可以发挥相当大的威力了。
|
||||
Reference in New Issue
Block a user