对于在类定义体中定义、且不包含循环等控制语句的成员函数,C++会将他们作为内置函数来处理。也就是说程序调用这些成员函数时,并不是真正的执行函数的调用过程,而是将函数代码嵌入到程序的调用点,大大减少了函数调用机制所带来的开销。
为什么有了malloc/free还要new/delete
- malloc 与 free 是 C++/C 语言的标准库函数,new/delete 是 C++的运算符。它们都可用于申请动态内存和释放内存.对于非内部数据类型的对象而言,光用 malloc/free 无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。由于malloc/free 是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free。即如果有一个对象你使用malloc来申请这个对象的内存
test *p = (test *)malloc(sizeof(test) )
,那么此时根本就不会执行这个类的构造函数,此时只是申请了这个类这么大的内存,并把类指针p指向这块内存。
因此 C++语言需要一个能完成动态内存分配和初始化工作的运算符 new,以及一个能完成清理与释放内存工作的运算符 delete。注意 new/delete 不是库函数。
我们不要企图用 malloc/free 来完成动态对象的内存管理,应该用 new/delete。由 于 内 部 数 据 类 型 的 “ 对 象 ” 没 有 构 造 与 析 构 的 过 程 , 对 它 们 而 言malloc/free 和new/delete 是等价的。
内存耗尽怎么办
如果在申请动态内存时找不到足够大的内存块,malloc和new将返回NULL指针,宣告内存申请失败。通常有三种方式处理“内存耗尽”问题。
- 判断指针是否为NULL,如果是则马上用return语句终止本函数。
- 判断指针是否为NULL,如果是则马上用exit(1)终止整个程序的运行。
- 为new和malloc设置异常处理函数。例如Visual C++可以用_set_new_hander函数为new设置用户自己定义的异常处理函数,也可以让malloc享用与new相同的异常处理函数。详细内容请参考C++使用手册。
如果发生内存耗尽这种事情,一般来说应用程序已经无法解决了.如果不用exit(1)把程序杀死,那么它可能会害死操作系统.
C++中的explicit关键字
explicit的作用是防止由构造函数定义的隐式转换。即声明为explicit的构造函数不能在隐式转换中使用。
隐式转换:可以使用单个参数来调用的构造函数定义了从形参类型到该类类型的一个隐式的转换。
例如:
class things
{
public:
things(const std::string&name =""):
m_name(name),height(0),weight(10){}
int CompareTo(const things & other);
std::string m_name;
int height;
int weight;
};
这里things的构造函数可以只用一个实参完成初始化。所以可以进行一个隐式转换,像下面这样:
things a;
....//在这里初始化并被使用;
std::string nm = "book_1";
int result = a.CompareTo(nm);
这段程序使用一个string类型对象作为实参传递给things的CompareTo函数,但是本来需要的是一个things类的对象作为实参。于是编译器将string类的nm隐式的转换(构造)为一个things类的对象,新生成的临时的things对象被传递给CompareTo函数,并在离开这一段函数后就被析构。
这种行为的正确与否取决于业务需要。假如你只是想测试一下a的重量与10的大小之比,这么做也许是方便的。但是假如在CompareTo函数中还涉及到了要除以初始化为0的height属性,那么这么做可能就是错误的。需要在构造tings之后更改height属性不为0。所以要限制这种隐式类型转换。
那么这时候就可以通过将构造函数声明为explicit,来防止隐式类型转换。
explicit关键字只能用于类内部的构造函数声明上,而不能用在类外部的函数定义上。现在things类像这样:
class things
{
public:
explicit things(const std::string&name =""):
m_name(name),height(0),weight(0){}
int CompareTo(const things & other);
std::string m_name;
int height;
int weight;
};
这时你仍然可以通过显示使用构造函数完成上面的类型转换:
things a;
................//在这里被初始化并使用。
std::string nm ="book_1";
//显示使用构造函数
int result = a.CompareTo(things(nm));
google的c++规范中提到explicit的优点是可以避免不合时宜的类型变换,缺点无。所以google约定所有单参数的构造函数都必须是显示的,只有极少数情况下拷贝构造函数可以不声明称explicit。例如作为其他类的透明包装器的类。
effective c++中说:被声明为explicit的构造函数通常比其non-explicit兄弟更受欢迎。因为它们禁止编译器执行非预期(往往也不被期望)的类型转换。除非我有一个好理由允许构造函数被用于隐式类型转换,否则我会把它声明为explicit。我鼓励你遵循相同的政策。
C++ 中的const函数
在C++中,若一个变量被声明为const,则任何对于该变量的值的修改操作都是不允许的,而在面向对象的程序设计当中,为了体现封装性通常不允许直接修改类对象的数据成员。若要修改类对象,应调用公有成员函数来完成。为了保证const对象的常量性,编译器须区分不安全与安全的成员函数(即区分试图修改类对象与不修改类对象的函数)。例如:
const Screen blankScreen;
blankScreen.display(); // 对象的读操作
blankScreen.set(‘*’); // 错误:const类对象不允许修改
- 在C++中,只有被声明为const的成员函数才能够被一个const对象调用:声明const类型的成员函数时,只需要在成员函数的参数列表后加上关键字const,若该函数的定义在类体之外,定义时也必须加上const.声明为const的成员函数,不允许修改类的任何数据成员.但是,把一个成员函数声明为const并不能保证不修改指针指向的对象,编译器不会把这种修改检测为错误(即数据成员是指针变量,const只保证不修改其指向的地址,而指向的地址的内容被修改并不会算是错误).
- const 成员函数可以被具有相同参数列表的非const成员函数重载,如:
class Screen {
public:
char get(int x,int y);
char get(int x,int y) const;
};
const Screen cs;
Screen cs2;
char ch = cs.get(0,0); // 调用const成员函数
ch = cs2.get(0,0); // 调用非const成员函数
小结:
- const成员函数可以访问非const对象的非cosnt数据成员,const数据成员,也可以访问cosnt对象内的所有的数据成员.
- 非const成员函数可以访问非const对象的非const数据成员,const数据成员,但是不可以访问const对象的任意数据成员.
- 在声明一个成员函数时,若该成员函数并不对数据成员进行修改,应该尽可能的将其声明为const成员.
C++ 的mutable关键字
- mutable是"可变的,易变的",跟constant(既C++中的const)是反义词.在C++中,mutable也是为了突破const的限制而设置的.被mutable修饰的变量,将会永远处于可变的状态,即使在一个const函数当中.若一个类的成员函数不会修改对象的数据成员,那么我们通常将其声明为const.但是,有些时候我们需要在const的函数里面修改一些和类的状态无关的数据成员,那么这个数据成员就应该被mutable来修饰. 例如:假设我们需要在计算对象调用输出的次数,而输出函数是一个const函数.如果用普通的变量来计数的话,那么该计数值在const函数中是不能够被改变的.所以我们需要使用mutable来修饰这个计数.
class ClxTest{
public:
ClxTest:m_iTimes(0)();
void Output() const { m_iTimes++;}
int GetOutputTimes() const { return m_iTimes; }
private:
mutable int m_iTimes; // 计数器m_iTimes被mutable修饰,那么他就可以突破const的限制,在被const修饰的函数里面也能够被修改.
};
网友评论