标签:虚拟继承 函数地址 配置 对象 运算 html volatile 管理 保护
以下内容均摘抄自他人博客,正确性有待考察,请以质疑的态度阅读学习,若有错误请留言指正
内存分配器(Memory Allocator)负责内存管理,实现动态内存的分配和释放。内存分配器分为两级。第一级分配器直接调用C函数分配内存,第二级分配器则采用内存池来管理内存。如果申请的内存块足够大,那么启动第一级分配器,否则启动第二级分配器。这种设计的优点是可以快速分配和释放小块内存,同时避免内存碎片;缺点是内存池的生命周期比较长,并且很难显式释放。
第一级分配器只是简单的调用函数malloc()、realloc()和free()。为了保证内存按照指定字节数对齐,则需要调用函数_aligned_malloc()、_aligned_realloc()和_aligned_free(),因此实际分配的内存块可能大于申请内存的大小。
第二级分配器需要维护16个空闲块链表和一个内存池。每个链表中的空闲块的大小都是固定的,默认对齐字节数为8,则各个链码空闲块大小依次为n、2n、3n、4n、5n、6n、7n、8n、9n、10n、11n、12n、13n、14n、15n、16n。内存池由两个指针来描述,free_start记录起始地址,free_end记录结束地址。另外两个变量heap_size和used_size分别纪录堆大小和已用内存大小。
内存池管理的内存块大小只有固定的16个规格, 当所需内存块大于16n时,则使用第一级分配器进行内存分配。否则,按照以下步骤进行内存分配:
内存池向系统申请的内存空间,在使用过程中会被划分为更小的内存块,而这些小内存块的使用和归还几乎是随机的。如果试图对这些小内存块进行合并和释放,其高昂的代价会大幅降低内存池的性能。但在内存池的已用内存大小为0时,释放内存是安全的。内存分配器维护一个指针链表,用于内存空间的统一释放。
虚函数的实现要求对象携带额外的信息,这些信息用于在运行时确定该对象应该调用哪一个虚函数。典型情况下,这一信息具有一种被称为 vptr(virtual table pointer,虚函数表指针)的指针的形式。vptr 指向一个被称为 vtbl(virtual table,虚函数表)的函数指针数组,每一个包含虚函数的类都关联到 vtbl。当一个对象调用了虚函数,实际的被调用函数通过下面的步骤确定:找到对象的 vptr 指向的 vtbl,然后在 vtbl 中寻找合适的函数指针。
点击链接跳转
构造函数不能声明为虚函数的原因是:
内联函数不能为虚函数,原因在于虚表机制需要一个真正的函数地址,而内联函数展开以后,就不是一个函数,而是一段简单的代码(多数C++对象模型使用虚表实现多态,对此标准提供支持),可能有些内联函数会无法内联展开,而编译成为函数。
采用 Vector存储一些数据,但是发现在执行 clear() 之后内存并没有释放。
方法:vector 的 clear 不影响 capacity , 应该 swap 一个空的 vector。交换后临时变量注销,则vector的内存分配得到释放。
元素需要具备构造、析构、赋值、拷贝等函数,关联容器还要求元素可以比较大小。
引申:指针作为容器的元素时,额外的内存管理问题直接以普通指针作为容器的元素时。我们知道,指针就是一个地址值,因此以指针为元素的容器存放的就是一些内存地址,而不是真正的数据。但是,容器只负责指针元素一级的内存问题,即它负责指针元素本身的内存分配和释放,而不会负责指针指向对象的内存管理事务,因为那是程序员的责任。
点击链接跳转
#include <iostream>
class String
{
public:
String(const char *str=NULL);//普通构造函数
String(const String &str);//拷贝构造函数
String & operator =(const String &str);//赋值函数
~String();//析构函数
protected:
private:
char* m_data;//用于保存字符串
};
//普通构造函数
String::String(const char *str){
if (str==NULL){
m_data=new char[1]; //对空字符串自动申请存放结束标志'\0'的空间
if (m_data==NULL){//内存是否申请成功
std::cout<<"申请内存失败!"<<std::endl;
exit(1);
}
m_data[0]='\0';
}
else{
int length=strlen(str);
m_data=new char[length+1];
if (m_data==NULL){//内存是否申请成功
std::cout<<"申请内存失败!"<<std::endl;
exit(1);
}
strcpy(m_data,str);
}
}
//拷贝构造函数
String::String(const String &str){ //输入参数为const型
int length=strlen(str.m_data);
m_data=new char[length+1];
if (m_data==NULL){//内存是否申请成功
std::cout<<"申请内存失败!"<<std::endl;
exit(1);
}
strcpy(m_data,str.m_data);
}
//赋值函数
String& String::operator =(const String &str){//输入参数为const型
if (this==&str) //检查自赋值
return *this;
int length=strlen(str.m_data);
delete [] m_data;//释放原来的内存资源
m_data= new char[length+1];
if (m_data==NULL){//内存是否申请成功
std::cout<<"申请内存失败!"<<std::endl;
exit(1);
}
strcpy(m_data,str.m_data);
return *this;//返回本对象的引用
}
//析构函数
String::~String(){
delete [] m_data;
}
void main(){
String a;
String b("abc");
system("pause");
}
多态性可以简单的概括为“1个接口,多种方法”,在程序运行的过程中才决定调用的机制程序实现上是这样,通过父类指针调用子类的函数,可以让父类指针有多种形态。
(多态是指相同的操作或函数、过程可作用于多种类型的对象上并获得不同的结果。不同的对象,收到同一消息可以产生不同的结果,这种现象称为多态。)
对于虚函数调用来说,每一个对象内部都有一个虚表指针,该虚表指针被初始化为本类的虚表。所以在程序中,不管你的对象类型如何转换,但该对象内部的虚表指针是固定的,所以呢,才能实现动态的对象函数调用,这就是C++多态性实现的原理。
总结(基类有虚函数):
一个由C/C++编译的程序占用的内存分为以下几个部分
C++语言支持函数重载,C语言不支持函数重载,函数被C++编译器编译后在库中的名字与C语言的不同,假设某个函数原型为:
void foo(int x, inty);
该函数被C编译器编译后在库中的名字为: _foo 而C++编译器则会产生像: _foo_int_int 之类的名字。为了解决此类名字匹配的问题,C++提供了C链接交换指定符号 extern "C"。
防止头文件被重复包含
拷贝构造函数前加 “explicit” 关键字
类型转换有c风格的,当然还有c++风格的。c风格的转换的格式很简单(TYPE)EXPRESSION,但是c风格的类型转换有不少的缺点,有的时候用c风格的转换是不合适的,因为它可以在任意类型之间转换,比如你可以把一个指向const对象的指针转换成指向非const对象的指针,把一个指向基类对象的指针转换成指向一个派生类对象的指针,这两种转换之间的差别是巨大的,但是传统的c语言风格的类型转换没有区分这些。还有一个缺点就是,c风格的转换不容易查找,他由一个括号加上一个标识符组成,而这样的东西在c++程序里一大堆。所以c++为了克服这些缺点,引进了4新的类型转换操作符
int i;float f; f=(float)i;
或者
f=static_cast<float>(i);
const int *fun(int x,int y){}
int *ptr=const_cast<int *>(fun(2.3))
int i; char *ptr="hello freind!";
i=reinterpret_cast<int>(ptr);
使用宏和内联函数都可以节省在函数调用方面所带来的时间和空间开销。二者都采用了空间换时间的方式,在其调用处进行展开:
volatile的本意是“易变的” 因为访问寄存器要比访问内存单元快的多,所以编译器一般都会作减少存取内存的优化,但有可能会读脏数据。当要求使用volatile声明变量值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。精确地说就是,遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问;如果不使用valatile,则编译器将对所声明的语句进行优化。(简洁的说就是:volatile关键词影响编译器编译的结果,用volatile声明的变量表示该变量随时可能发生变化,与该变量有关的运算,不要进行编译优化,以免出错)
C++提供了关键字explicit,可以阻止不应该允许的经过转换构造函数进行的隐式转换的发生。声明为explicit的构造函数不能在隐式转换中使用。
动态分配就是用运算符new来创建一个类的对象,在堆上分配内存。
静态分配就是A a;这样来由编译器来创建一个对象,在栈 上分配内存。
class A {
protected:
A(){}
~A(){}
public:
static A* create() {
return new A();
}
void destory() {
delete this;
}
};
class A {
private:
void* operator new(size_t t){} // 注意函数的第一个参数和返回值都是固定的
void operator delete(void* ptr){} // 重载了new就需要重载delete
public:
A(){}
~A(){}
};
C++编译器在实现const的成员函数的时候为了确保该函数不能修改类的实例的状态,会在函数中添加一个隐式的参数const this*。但当一个成员为static的时候,该函数是没有this指针的。也就是说此时const的用法和static是冲突的。
我们也可以这样理解:两者的语意是矛盾的。static的作用是表示该函数只作用在类型的静态变量上,与类的实例没有关系;而const的作用是确保函数不能修改类的实例的状态,与类型的静态变量没有关系。因此不能同时用它们。
Overload(重载):在C++程序中,可以将语义、功能相似的几个函数用同一个名字表示,但参数或返回值不同(包括类型、顺序不同),即函数重载。
简单的说智能指针就是将基本类型指针封装为类对象指针(这个类肯定是个模板,以适应不同基本类型的需求),并在析构函数里编写delete语句删除指针指向的内存空间。
智能指针(smart pointer)是存储指向动态分配(堆)对象指针的类,用于生存期控制,能够确保自动正确的销毁动态分配的对象,防止内存泄露。它的一种通用实现技术是使用引用计数(reference count)。智能指针类将一个计数器与类指向的对象相关联,引用计数跟踪该类有多少个对象共享同一指针。每次创建类的新对象时,初始化指针并将引用计数置为1;当对象作为另一对象的副本而创建时,拷贝构造函数拷贝指针并增加与之相应的引用计数;对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用计数(如果引用计数为减至0,则删除对象),并增加右操作数所指对象的引用计数;调用析构函数时,构造函数减少引用计数(如果引用计数减至0,则删除基础对象)。
因为switch后面只能带自动转换为整形(包括整形)的类型,比如字符型char,unsigned int等,实数型不能自动转换为整形。
标签:虚拟继承 函数地址 配置 对象 运算 html volatile 管理 保护
原文地址:https://www.cnblogs.com/weiweng/p/12493588.html