- 用new和不用new创建类的区别
第一种和第二种没什么区别,一个隐式调用,一个显式调用,两者都是在进程虚拟地址空间中的栈中分配内存,而第三种使用了new,在堆中分配了内存,而栈中内存的分配和释放是由系统管理,而堆中内存的分配和释放必须由程序员手动释放,所以这就产生一个问题是把对象放在栈中还是放在堆中的问题,这个问题又和堆和栈本身的区别有关:
这里面有几个问题:
1.堆和栈最大可分配的内存的大小
2.堆和栈的内存管理方式
3.堆和栈的分配效率
首先针对第一个问题,一般来说对于一个进程栈的大小远远小于堆的大小,在linux中,你可以使用ulimit -s (单位kb)来查看一个进程栈的最大可分配大小,一般来说不超过8M,有的甚至不超过2M,不过这个可以设置,而对于堆你会发现,针对一个进程堆的最大可分配的大小在G的数量级上,不同系统可能不一样,比如32位系统最大不超过2G,而64为系统最大不超过4G,所以当你需要一个分配的大小的内存时,请用new,即用堆。
其次针对第二个问题,栈是系统数据结构,对于进程/线程是唯一的,它的分配与释放由操作系统来维护,不需要开发者来管理。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时,这些存储单元会被自动释放。栈内存分配运算内置于处理器的指令集中,效率很高,不同的操作系统对栈都有一定的限制。 堆上的内存分配,亦称动态内存分配。程序在运行的期间用malloc申请的内存,这部分内存由程序员自己负责管理,其生存期由开发者决定:在何时分配,分配多少,并在何时用free来释放该内存。这是唯一可以由开发者参与管理的内存。使用的好坏直接决定系统的性能和稳定。
由上可知,但我们需要的内存很少,你又能确定你到底需要多少内存时,请用栈。而当你需要在运行时才知道你到底需要多少内存时,请用堆。
最后针对第三个问题,栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率 比较高。堆则是C/C++函数库提供的,它的机制是很复杂的,例如为了分配一块内存,库函数会按照一定的算法(具体的算法可以参考数据结构/操作系统)在 堆内存中搜索可用的足够大小的空间,如果没有足够大小的空间(可能是由于内存碎片太多),就有可能调用系统功能去增加程序数据段的内存空间,这样就有机会 分 到足够大小的内存,然后进行返回。显然,堆的效率比栈要低得多。 - 指针,引用,取地址
总而言之,和类型在一起的是引用,和变量在一起的是取址
实例如下:1)引用在赋值=的左边,而取地址在赋值的右边,比如
int a=3;
int &b=a; //引用
int *p=&a; //取地址
2)和类型在一起的是引用,和变量在一起的是取址。 举例同样如上,还有下例:
int function(int &i)
{
} //引用
3)对于vector,上面2条同样适合
vector vec1(10,1); //initialize vec1: 10 elements, every element’s value is 1
vector &vec2 = vec1; // vec2 is reference to vec1
vector *vec3 = &vec2; //vec3 is addresss of vec1 and vec2
指针和引用的区别
1.首先,引用不可以为空,但指针可以为空。前面也说过了引用是对象的别名,引用为空——对象都不存在,怎么可能有别名!故定义一个引用的时候,必须初始化。因此如果你有一个变量是用于指向另一个对象,但是它可能为空,这时你应该使用指针;如果变量总是指向一个对象,i.e.,你的设计不允许变量为空,这时你应该使用引用。如下图中,如果定义一个引用变量,不初始化的话连编译都通不过(编译时错误)
而声明指针是可以不指向任何对象,也正是因为这个原因,使用指针之前必须做判空操作,而引用就不必。
2.其次,引用不可以改变指向,对一个对象"至死不渝";但是指针可以改变指向,而指向其它对象。说明:虽然引用不可以改变指向,但是可以改变初始化对象的内容。例如就++操作而言,对引用的操作直接反应到所指向的对象,而不是改变指向;而对指针的操作,会使指针指向下一个对象,而不是改变所指对象的内容。
3.再次,引用的大小是所指向的变量的大小,因为引用只是一个别名而已;指针是指针本身的大小,4个字节
4.最后,引用比指针更安全。由于不存在空引用,并且引用一旦被初始化为指向一个对象,它就不能被改变为另一个对象的引用,因此引用很安全。对于指针来说,它可以随时指向别的对象,并且可以不被初始化,或为NULL,所以不安全。const 指针虽然不能改变指向,但仍然存在空指针,并且有可能产生野指针(即多个指针指向一块内存,free掉一个指针之后,别的指针就成了野指针)
总之,用一句话归纳为就是:指针指向一块内存,它的内容是所指内存的地址;而引用则是某块内存的别名,引用不改变指向。
- c++ 中explicit用法
在c++中explicit关键字只能修饰只有一个参数,或者只有一个参数需要赋值的构造函数,他的作用是用来声明该构造函数是非隐式的,跟他相对的是implicit关键字,通常来说构造函数都是默认使用implicit,explicit的作用是创建类的时候可以进行强制类型转换.
class CxString // 使用关键字explicit的类声明, 显示转换
{
public:
char *_pstr;
int _size;
explicit CxString(int size)
{
_size = size;
// 代码同上, 省略...
}
CxString(const char *p)
{
// 代码同上, 省略...
}
};
// 下面是调用:
CxString string1(24); // 这样是OK的
CxString string2 = 10; // 这样是不行的, 因为explicit关键字取消了隐式转换
CxString string3; // 这样是不行的, 因为没有默认构造函数
CxString string4("aaaa"); // 这样是OK的
CxString string5 = "bbb"; // 这样也是OK的, 调用的是CxString(const char *p)
CxString string6 = 'c'; // 这样是不行的, 其实调用的是CxString(int size), 且size等于'c'的ascii码, 但explicit关键字取消了隐式转换
string1 = 2; // 这样也是不行的, 因为取消了隐式转换
string2 = 3; // 这样也是不行的, 因为取消了隐式转换
string3 = string1; // 这样也是不行的, 因为取消了隐式转换, 除非类实现操作符"="的重载
上面也已经说过了, explicit关键字只对有一个参数的类构造函数有效, 如果类构造函数参数大于或等于两个时, 是不会产生隐式转换的, 所以explicit关键字也就无效了. 例如:
class CxString // explicit关键字在类构造函数参数大于或等于两个时无效
{
public:
char *_pstr;
int _age;
int _size;
explicit CxString(int age, int size)
{
_age = age;
_size = size;
// 代码同上, 省略...
}
CxString(const char *p)
{
// 代码同上, 省略...
}
};
// 这个时候有没有explicit关键字都是一样的
但是, 也有一个例外, 就是当除了第一个参数以外的其他参数都有默认值的时候, explicit关键字依然有效, 此时, 当调用构造函数时只传入一个参数, 等效于只有一个参数的类构造函数, 例子如下:
class CxString // 使用关键字explicit声明
{
public:
int _age;
int _size;
explicit CxString(int age, int size = 0)
{
_age = age;
_size = size;
// 代码同上, 省略...
}
CxString(const char *p)
{
// 代码同上, 省略...
}
};
// 下面是调用:
CxString string1(24); // 这样是OK的
CxString string2 = 10; // 这样是不行的, 因为explicit关键字取消了隐式转换
CxString string3; // 这样是不行的, 因为没有默认构造函数
string1 = 2; // 这样也是不行的, 因为取消了隐式转换
string2 = 3; // 这样也是不行的, 因为取消了隐式转换
string3 = string1; // 这样也是不行的, 因为取消了隐式转换, 除非类实现操作符"="的重载
class List {
private:
Node * p_head;
int length;
……
Public:
int GetLength () const;
bool GetNodeInfo(const int index,Node & buffer) const {…… }
bool DeleteNode(const int index);
…………
}
可以看到,在GetLength和GetNodeInfo两个成员函数的参数列表后面出现了一个const。这个const指明了这个函数不会修改该类的任何成员数据的值,称为常量成员函数。如果定义的类是一个const修饰的类,那么该类不可以调用类中任何不被const修饰的方法.如下例所示:
class ss{
public:
explicit ss (int a):aa(a){}
**int d() {** // **改成int d() const**可以通过
return aa;
}
private:
int aa;
};
int main(){
const ss s(5);
int a = s.d();//会报错,虽然s.d()并没哟修改任何值,但是因为ss的类的定义中没有对d()进行const修饰,所以会报错.
cout<<a;
return 0;
}
倘使一个成员函数被调用多次,那么他将在每次调用的时候都会被检查。这显然大大不利。而如果在定义类的时候加上const限定符对常函数加以标记,那么编译器只是检查一次就好,在const对象调用成员函数时,const函数将会被直接放行。所以,C++采取了const限定符描述常函数方案而摈弃了后者。所以,即使一个函数没有修改对象值的行为,如果没有加上const限定符说明是常函数,那么const对象依然不能调用它。
然而,有些时候,我们却必须要让const函数具有修改某个成员数据值的能力。比如一些内部的状态量,对外部用户无所谓,但是对整个对象的运行却大有用处,如支持缓存的技术。遇到这种问题,我们可以把一个成员数据定义为mutable(多变的),它表示这个成员变量可以被const成员函数修改却不违法。比如下面定义了一个is_valid类成员:
class List
{
private:
……
mutable bool is_valid;
……
public:
bool CheckList() const
{
if(length >= 1) then return is_valid =true;
else return is_valid = false; //正确!
};
- const char *a与char const *a与char * const a的区别
const和指针的基础知识:
①如果const位于星号的左侧,则const就是用来修饰指针所指向的变量,即指针指向为常量;
可以不先初始化,因为指针内容是常量,但指针本身不是常量
不能通过指针改变所指向的值
int b=400;
const int * a =&b;//但值本身可通过自己修改;指针也可以指向别的值
等价于
const int *a;
a=&b;
②如果const位于星号的右侧,const就是修饰指针本身,即指针本身是常量。
定义时必须初始化
指针只能永远指向这个变量(不能进行加减或其他导致指针值改变的计算操作)
但指向的变量可变,可通过指针改变该变量的值。
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。
指向常量的指针(C++Primer上叫 指向常量的指针)(底层const):自觉不去(靠它自己也不能)改变所指对象,而该对象若不是常量对象则其值通过其他方式改变所指对象。总之,const可绑定非const。
常量指针(C++Primer上叫 常量指针)(顶层const):由于指针为常量,所以声明时必须初始化,且初始化后存放在指针中那个地址不可改变。但此地址对应的数可被改变:指针常量若所指对象是常量,指针常量不能修改其所指对象的值;指针常量若所指对象是非常量,指针常量也能通过指针修改其所指对象的值。
解答
const char *a声明(*a),(*a)是const char类型的。a被一个解引用运算符修饰,故a是个普通的指针,可以修改,但是a所指向的数据(即*a)由于const的修饰而不可通过指针a去修改。
char *const a声明(*const a),(*const a)是char类型的。a被一个解引用运算符和一个const关键词修饰,故a是个不可修改的指针,但可通过指针a去修改a所指向的数据(即*a)。
char const *a和const char *a是同一个意思。
如果既不允许a被修改,也不允许a所指向的数据被修改,那么需要声明为const char * const a。
- delete 和析构函数关系
delete和析构函数间的关系及细节
原文见:http://blog.sina.com.cn/s/blog_78c5ff950102vjoe.html
个人心得:先看以下代码
#include
#include
using namespace std;
class shape
{
public:
char x;
shape(char m)
{
x=m;
cout << "构造"<<endl;
cout <<x<<endl;
}
~shape()
{
cout << "析构"<<endl;
cout<<x<<endl;
}
};
int main()
{
shape a(‘a’);
shape* b = new shape(‘b’);
delete b;
system("pause");
return 0;
}
当有那句delete b时,运行结果如图,delete和析构函数间的关系及细节也就是说delete b之后执行了shape的析构函数,而按了任意键之后,会立马闪出一个析构a来,这说明程序执行完成后,即在a所在的作用域结束后,执行了shape的析构函数
而当没有delete b时,运行结果如图delete和析构函数间的关系及细节,这说明暂时没有执行b的shape析构函数,按了任意键后,跟上面一样,也只会闪出一个析构a来,没有析构b
这说明,直接声明的对象,比如shape a,即在栈上面的对象,所在作用域结束后,会自动执行析构函数,而new出来的在堆上的对象,不调用delete,即使它所在的作用域已经结束,也不会调用析构函数,根据另外一篇文章内所述,可能程序结束后,OS(操作系统)会回收其堆内占用的内存,这样就不知道会干什么,会不会调用析构了。
delete详解:首先调用析构函数,完成类成员的释放,比如类成员有vector也指向了堆上的内存,就需要在析构函数中同样使用delete释放这块内存,或者说它自身处于一个容器当中,就需要在这个容器中erase它
然后再free掉整个对象的内存;
delete b过后,b仍然指向改内存,即地址不变,但指针可能为悬垂指针,访问它可能带来意想不到的结果,也可能正确访问,不确定,所以建议delete后,把指针设置成NULL,后面也可根据指针是否为NULL判断是否可用
- override和final
override重载
当你在父类中使用了虚函数时候,你可能需要在某个子类中对这个虚函数进行重写,以下方法都可以:
class A
{
virtual void foo();
}
class B :public A
{
void foo(); //OK
virtual foo(); // OK
void foo() override; //OK
}
final
class Base
{
virtual void foo();
};
class A : Base
{
void foo() final; // foo 被override并且是最后一个override,在其子类中不可以重写
void bar() final; // Error: 父类中没有 bar虚函数可以被重写或final
};
class B final : A // 指明B是不可以被继承的
{
void foo() override; // Error: 在A中已经被final了
};
class C : B // Error: B is final
{
};