c/c++语言开发共享c++知识复习2.0

16: 内存分配方式 内存分配方式有三种: (1) 从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static 变量。 (2) 在栈上创建。

16:
内存分配方式
内存分配方式有三种:
(1) 从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static 变量。
(2) 在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
(3) 从堆上分配,亦称动态内存分配。程序在运行的时候用malloc 或new 申请任意多少的内存,程序员自己负责在何时用free 或delete 释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多

17: return 语句返回常量字符串
char *getstring2(void)
{
char *p = “hello world”;
return p;
}
void test5(void)
{
char *str = null;
str = getstring2();
cout<< str << endl;
}
函数test5 运行虽然不会出错,但是函数getstring2 的设计概念却是错误的。因为getstring2 内的“hello world”是常量字符串,位于静态存储区,它在程序生命期内恒定不变。无论什么时候调用getstring2,它返回的始终是同一个“只读”的内存块。

18: new/delete, malloc/free
对于非内部数据类型的对象而言,光用maloc/free 无法满足动态对象的要求。对象
在创建的同时要自动执行构造函数, 对象在消亡之前要自动执行析构函数。由于
malloc/free 是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数
和析构函数的任务强加于malloc/free

19: 内存耗尽怎么办
如果在申请动态内存时找不到足够大的内存块,malloc 和new 将返回null 指针,宣告内存申请失败。通常有三种方式处理“内存耗尽”问题。
(1)判断指针是否为null,如果是则马上用return 语句终止本函数。例如:
void func(void)
{
a *a = new a;
if(a == null)
{
return;
}

}
(2)判断指针是否为null,如果是则马上用exit(1)终止整个程序的运行。例如:
void func(void)
{
a *a = new a;
if(a == null)
{
cout << “memory exhausted” << endl;
exit(1);
}

}
(3)为new 和malloc 设置异常处理函数。例如visual c++可以用_set_new_hander 函数为new 设置用户自己定义的异常处理函数,也可以让malloc 享用与new 相同的异常处理函数。详细内容请参考c++使用手册。
上述(1)(2)方式使用最普遍。如果一个函数内有多处需要申请动态内存,那么方式(1)就显得力不从心(释放内存很麻烦),应该用方式(2)来处理。很多人不忍心用exit(1),问:“不编写出错处理程序,让操作自己解决行不行?”不行。如果发生“内存耗尽”这样的事情,一般说来应用程序已经无药可救。如果
不用exit(1) 把坏程序杀死,它可能会害死操作系统

20: extern “c”
extern “c”
{
void foo(int x, int y);
? // 其它函数
}
或者写成
extern “c”
{

include “myheader.h”

? // 其它c 头文件
}
这就告诉c++编译译器,函数foo 是个c 连接,应该到库中找免费精选名字大全_foo 而不是找_foo_int_int。c++编译器开发商已经对c 标准库的头文件作了extern“c”处理,所以我们可以用#include 直接引用这些头文件

21: 令人迷惑的隐藏规则
本来仅仅区别重载与覆盖并不算困难,但是c++的隐藏规则使问题复杂性陡然增加。
这里“隐藏”是指派生类的函数屏蔽了与其同名的基类函数,规则如下:
(1)如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual
关键字,基类的函数将被隐藏(注意别与重载混淆)。
(2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual
关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)。

22: 运算符重载
运算符 规则
所有的一元运算符 建议重载为成员函数
= () [] -> 只能重载为成员函数
+= -= /= *= &= |= ~= %= >>= <<= 建议重载为成员函数
所有其它运算符 建议重载为全局函数

23: 函数内联
inline 是一种“用于实现的关键字”,而不是一种“用于声明的关键字”。
定义在类声明之中的成员函数将自动地成为内联函数,例如
class a
{
public:
void foo(int x, int y) { ? } // 自动地成为内联函数
}
将成员函数的定义体放在类声明之中虽然能带来书写上的方便,但不是一种良好的
风格,上例应该改成:
// 头文件
class a
{
public:
void foo(int x, int y);
}
// 定义文件
inline void a::foo(int x, int y)
{
?
}

24:构造函数的初始化列表 b::b(const a &a) //初始化表里调用了类a 的拷贝构造函数,从而将成员对象m_a 初始化。 m_a(a)
{

}
b::b(const a &a) //构造函数干了两件事:先暗地里创建m_a对象(调用了a 的无参数构造函数),再调用类a 的赋值函数,将参数a 赋给m_a
{
m_a = a;

}

25: 构造和析构的次序
构造从类层次的最根处开始,在每一层中,首先调用基类的构造函数,然后调用成员对象的构造函数。析构则严格按照与构造相反的次序执行,该次序是唯一的,否则编译器将无法自动执行析构过程。
一个有趣的现象是,成员对象初始化的次序完全不受它们在初始化表中次序的影响,只由成员对象在类中声明的次序决定。这是因为类的声明是唯一的,而类的构造函数可以有多个,因此会有多个不同次序的初始化表。如果成员对象按照初始化表的次序进行构造,这将导致析构函数无法得到唯一的逆序

26: 拷贝构造函数与赋值函数
编译器将以“位拷贝”的方式自动生成缺省的函数。倘若类中含有指针变量,那么这两个缺省的函数就隐含了错误。
拷贝构造函数和赋值函数非常容易混淆,常导致错写、错用。拷贝构造函数是在对象被创建时调用的,而赋值函数只能被已经存在了的对象调用。以下程序中,第三个语句和第四个语句很相似,你分得清楚哪个调用了拷贝构造函数,哪个调用了赋值函数吗?
string a(“hello”);
string b(“world”);
string c = a; // 调用了拷贝构造函数,最好写成 c(a);
c = b; // 调用了赋值函数
本例中第三个语句的风格较差,宜改写成string c(a) 以区别于第四个语句

27:
// string 的普通构造函数
string::string(const char *str)
{
if(str==null)
{
m_data = new char[1];
*m_data = ‘/0’;
}
else
{
int length = strlen(str);
m_data = new char[length+1];
strcpy(m_data, str);
}
}

// 拷贝构造函数
string::string(const string &other)
{
// 允许操作other 的私有成员m_data
int length = strlen(other.m_data);
m_data = new char[length+1];
strcpy(m_data, other.m_data);
}

// 赋值函数
string & string::operate =(const string &other)
{
// (1) 检查自赋值
if(this == &other)
return *this;
// (2) 释放原有的内存资源
delete [] m_data;
// (3)分配新的内存资源,并复制内容
int length = strlen(other.m_data);
m_data = new char[length+1];
strcpy(m_data, other.m_data);
// (4)返回本对象的引用
return *this;
}

// string 的析构函数
string::~string(void)
{
delete [] m_data;
// 由于m_data 是内部数据类型,也可以写成 delete m_data;
}

28: 在编写派生类的赋值函数时,注意不要忘记对基类的数据成员重新赋值
class base
{
public:

base & operate =(const base &other); // 类base 的赋值函数
private:
int m_i, m_j, m_k;
};
class derived : public base
{
public:

derived & operate =(const derived &other); // 类derived 的赋值函数
private:
int m_x, m_y, m_z;
};
derived & derived::operate =(const derived &other)
{
//(1)检查自赋值
if(this == &other)
return *this;
//(2)对基类的数据成员重新赋值
base::operate =(other); // 因为不能直接操作私有数据成员
//(3)对派生类的数据成员赋值
m_x = other.m_x;
m_y = other.m_y;
m_z = other.m_z;
//(4)返回本对象的引用
return *this;
}

29;
函数返回值采用“引用传递”的场合并不多,这种方式一般只出现在类的赋值函数中,目的是为了实现链式表达。
例如
class a
{?
a & operate = (const a &other); // 赋值函数
};
a a, b, c; // a, b, c 为a 的对象
?
a = b = c; // 正常的链式赋值
(a = b) = c; // 不正常的链式赋值,但合法
如果将赋值函数的返回值加const 修饰,那么该返回值的内容不允许被改动。上例中,语句 a = b = c 仍然正确,但是语句 (a = b) = c 则是非法的。

30: 继承和组合的规则
(1)若在逻辑上b 是a 的“一种”,并且a 的所有功能和属性对b 而言都有意义,则允许b 继承a 的功能和属性。
(2)若在逻辑上a 是b 的“一部分”(a part of),则不允许b 从a 派生,而是要用a 和其它东西组合出b。

31: 引用本身不占存储单元,系统也不给引用分配存储单元。故:对引用求地址,就是对目标变量求地址。&ra与&a相等

本文来自网络收集,不代表计算机技术网立场,如涉及侵权请联系管理员删除。

ctvol管理联系方式QQ:251552304

本文章地址:https://www.ctvol.com/c-cdevelopment/606281.html

(0)
上一篇 2021年5月13日
下一篇 2021年5月13日

精彩推荐