C++笔记
指针
函数指针
函数名就表示函数的代码在内存中的起始地址。调用函数的通常形式"函数名(参数表)“的实质就是"函数代码首地址(参数表)”。
函数名在表示函数代码起始地址的同时,也包括函数的返回值类型和参数的个数、类型、排列次序等信息。因此在通过函数名调用函数时,编译系统能够自动检查实参与形参是否相符,用函数的返回值参与其他运算时,能自动进行类型一致性检查。
1 |
|
数组指针
1 |
|
this指针
this 指针实际上是类成员函数的一个隐含参数。在调用类的成员函数时,目的对象的地址会自动作为该参数的值,传递给被调用的成员函数,这样被调函数就能够通过this 指针来访问目的对象的数据成员。对于常成员函数来说,这个隐含的参数是常指针类型的。
指向类的成员的指针
1 |
|
动态内存分配
在程序运行过程中申请和释放的存储单元也称为堆对象,申请和释放过程一般称为建立和删除。
new 数据类型(初始化参数列表);
如果内存申请成功. new 运算便返回一个指向新分配内存首地址的类型的指针,可以通过这个指针对堆对象进行访问. 如果申请失败,会抛出异常.
对于基本数据类型,如采不希望在分配内存后设定初值,可以把括号省去
如采保留括号,但括号中不写任何数值,则表示用。对该对象初始化
delete 指针名;
对象的析构函数将被调用。
对于用 new 建立的对象,只能使用 delete 进行一次删除操作
new 分配的内存,必须用 delete 加以释放,否则会导致动态分配的内存无法回收,使得程序占据的内存越来越大,造成"内存泄漏"。
new 动态创建一维数组时,在方括号后仍然可以加小括号"()", 初始化0;
利用动态内存分配操作实现了数组的动态创建,使得数组元素的个数可以根据运行时的需要而确定。但是建立和删除数组的过程使得程序略显烦琐,更好的方法是将数组的建立和删除过程封装起来,形成一个动态数组类。
assert 的含义是"断言",它是标准 c+十的 cassert 头文件中定义的一个宏,用来判断一个条件表达式的值是否为 true ,如果不为 true ,程序会中止,并且报告出错误,这样就很容易将错误定位。一个程序一般可以以两种模式编译 调试(debug) 模式和发行( release) 模式, assert 只在调试模式下生效,而在发行模式下不执行任何操作,这样兼顾了调试模式的调试需求和发行模式的效率需求。
字符串和string容器
1 |
|
指针与引用
所以说指针是一种底层的机制。引用则是一种较高层的机制,在语言概念上它是另一变量的"别名",把地址这一概念隐藏起来了,但在引用运行时的实现机制中,还不得不借助于地址。二者可以说是殊途同归,差异主要是语言形式, 最后都是靠存储地址来实现的。引用相当于指针常量, 引用是为了方便使用对指针的包装.
有些时候引用不能代替使用指针:
- 使用函数指针,由于没有函数引用,所以函数指针无法被引用替代。
- 用 new 动态创建的对象或数组,需要用指针来存储它的地址。
- 以数组形式传递大批量数据时,需要用指针类型接收参数
T &s = *(new T()); delete &s;, 这样其实可以, 一般不用.
指针类型安全性
reinterpret_cast可以将一种类型的指针转换为另一种类型的指针. 具有很大的危险性和不确定性,c++ 标准只保证用 reinterpret_cast 转换后与再转换回的值相同.
C 允许 void 指针隐含地转换为其他任何类型的指针,而 c++ 规定这种情况只能显式转换,这是 c++ 相比的一个安全之处。
void 指针的另一个用途在于,有时一个指针可能会指向不同类型的对象, void 指针只起一定的传递作用,最终使用该指针时,还需要根据情况将指针还原为它原先的类型。
有很多从C标准 继承而来的函数会使用 void 指针作为参数和返回值,例如将二段内存空间设为一个固定值 (memset) 、比较两段内存空间 (memcmp) 、复制一段内存空间(memcpy) 、动态分配一段内存空间 (malloc) 、释放动态分配的内存空间 (free) 等,这些操作都是不管具体的数据类型,把不同类型的数据当作无差别的二进制序列。其中,动态内存管理的函数( malloc free 等)已经可以被 c++ new delete 关键字全面替代,而直接内存操作的函数 (memset memcmp , memcpy 等)只能针对对象的二进制表示进行处理,不符合面向对象的要求,一般不用,至多对一些基本数据类型的数组使用。
总结起来,保证指针类型安全性的办法有以下几种。
- 除非非常特殊的底层用途, reinterprt_cast 不要用。
- 继承标准 的涉及 void 指针的函数,一般不要用,至多对一些基本数据类型及其数组使用。
- 如果一定需要用 void 指针,那么用 static_cast void 指针转换为具体类型的指针时,一定要转换为最初的类型(即当初转换到该 void 指针的指针类型)。
堆对象管理
通常使用的局部变量,在运行栈上分配空间,空间分配和释放的过程是由编译器生成的代码控制的,一个函数返回后相应的空间会自行释放;而静态生存期变量,其空间的分配是由连接器完成的,它们占用的空间大小始终是固定的,在运行过程中无须释放。然而,用 new 在程序运行时动态创建的堆对象,则必须由程序用 delete 显式删除。如果动态生成的对象不再需要使用也不用 delete 删除,会使得这部分空间始终不能被其他对象利用,造成内存资源的泄漏。
有时确实需要在不同类之间转移堆对象的归属。例如,如果一个函数需要返回一个对象,为了避免复制构造函数因传递返回值被调用(因为大对象的复制构造会有较大开销) ,可以在函数内用 new 建立该对象,再将该对象的地址返回,但这就要求调用这个函数的类确保这个返回的堆对象最后被删除。每当遇到这种情况,都应当在函数的注释中明确指出,函数的调用者应当负责删除函数所返回的堆对象。这实际上是类的对外接口约定的一部分,不过能否正确履行不由编译器来检查,而需完全由编程者来保证。
解决动态对象的管理问题,也可以借助于共享指针。共享指针是一种具有指针行为的特殊的类,它会在指向一个堆对象的所有指针都不再有效时,自动将其删除。虽然使用共享指针要付出一定的效率代价,但安全性很好,容易使用。
cosnt_cast
const_cast 只用于将常指针转换为普通指针,将常引用转换为普通引用,而不用来将常对象转换为普通对象,因为这是没有意义的。因为对象(而非引用)的转换会生成对象的副本,而使用常对象本来就可以直接生成普通对象的副本.
1 |
|
知识点:
-
指针取值两种方式
1
2
3
4
5
6
7
8
9int main() {
int nums[] = {0, 1, 2, 3, 4, 5, 6, 7};
int* p = &nums[4];
cout << p[0] << endl;
cout << p[-2] << endl;
cout << *(p + 2) << endl;
return 0;
} -
常引用指向的对象可以是常量,
const int& a = 1;
参考
- 《C++语言程序设计(第4版)》 IBSN 9787302227984
- C++参考手册
- Microsoft C++文档