本文章仅记录《C++面向对象程序设计》(第2版)书中个人认为的重点,基础知识点不做记录。
原书购买链接:http://www.tup.tsinghua.edu.cn/booksCenter/book_06840601.html
目录
一.运算符和表达式
1.位运算符
-
C++提供一组位运算符。位运算符将操作数解释为有序的二进制位的集合。每个位是0或1。位运算符允许程序员设置或测试独立的位或一组位。通常使用无符号的整值数据类型进行位操作。
-
具体语法和规则参见以下链接内容
-
按位与和按位或经常用于检测某个数位的值
示例:
unsigned char byte; if((byte & 0x80) == 0) //测试byte的最高位是否为0 doSomething;
此处十六进制数0x80的二进制表示为10000000,如果变量byte的最高位为0,那么运算之后会得到二进制数00000000
-
利用位运算的特性,还可以实现一些有趣的算法
//交换变量x和y的值 x = x^y; y = x^y; x = x^y; //这个算法的性能未必最佳,但是最节省空间
2.条件运算符
-
条件运算符为简单的if-else语句提供了一种便利的替代表示法
它是C++唯一的三元运算符
-
语法:
exprl1 ? exprl2 : exprl3
首先计算exprl1,如果值为true,则计算exprl2,exprl2的值作为条件表达式的值;否则计算exprl3,exprl3的值作为条件表达式的值
3.逗号运算符
-
逗号运算符分隔两个或多个表达式,这些表达式从左向右计算,逗号表达式的结果是最右边表达式的值
-
示例:
a = 1, b = 2, a+b //表达式的值是3
二.类型转换
1.隐式类型转换
- C++定义了一组内置类型对象之间的标准转换,在必要时编译器自动应用这些转换。隐式类型转换发生在下列情况下:
- 混合类型的算术表达式中。在这种情况下,最宽的数据类型成为转换的目标类型。这也称为算术转换。
- 用一种类型的表达式赋值给另一种类型的对象。在这种情况下,目标转换类型是被赋值对象的类型,即,将赋值号右边的表达式转换为左操作数的类型
- 一个表达式作为参数传递给一个函数调用,表达式的类型与形参类型不同。这时,目标转换类型是形参的类型,即,将实参转换为形参类型
- 从函数返回一个表达式,表达式的类型与返回类型不同。在这种情况下,目标转换类型是函数的返回类型。
2.显式类型转换
-
显式转换也被称为强制类型转换,共有以下四种:
static_cast
、dynamic_cast
、const_cast
、reinterpret_cast
-
使用强制类型转换会关闭C++的类型检查设施,容易引起错误。
-
const_cast
只能用于转换常量,并去除该表达式的const限定,而其他三种转换无法去除常量性{ const int i = 0; int* j = &i; //错误 j = cosnt_cast<int*1>(&i); //正确 }
-
static_cast
可以显式进行编译器隐式进行的任何类型转换、“窄化“转换、具有潜在危险的类型转换int i = 10, j = 4; double q = i / j; //q的值是2.0 q = static_cast<double>(i) / j; //q的值是2.5
三.指针和引用
1.指针
-
定义指针时可以把
*
写在类型后面,也可以把*
写在指针变量名前面int *pi1; int* pi2;
-
指针不能保存非地址值,也不能被赋值或初始化为不同类型的地址值
-
定义指针时,应该对指针进行初始化。使用未初始化的指针是引发运行时错误的一大原因,且难以发现和调试
C++引入了字面值nullptr,用nullptr初始化或赋值给一个指针,会是指针成为空指针
如果实在不确定指针指向何处时,应该初始化为nullptr
- C++提供了一种通用指针,即
void*
指针,它可以持有任何类型的地址值。void*
只能表明相关的值是个地址,但是该地址保存的对象类型不知道。因而不能操纵void指针指向的对象,而只能传送该地址值或者和其他地址值进行比较。C++也不允许void指针到其他类型指针的直接赋值
2.new和delete
- 静态内存非陪实在程序执行之前进行的,因而效率比较高,但是缺乏灵活性,因为它要求程序在执行之前就知道所需类型和数量。而存储未知数目的元素却需要动态内存分配的灵活性。系统为所有程序提供了一个运行时可用的内存池,这个内存池被称为程序的自由存储区或堆。C++语言通过new和delete两个运算符来进行动态存储空间的管理
(1)new
-
new运算符在堆上动态分配空间,创建对象,并返回对象的地址。
语法:
new 类型
new 类型(初始值)
new 类型[数组大小]
示例:
int* ip1 = new int; //在堆上分配一个int类型的对象,并返回它的地址 int* ip2 = new int(100); //在堆上分配一个int对象,初始化为100,返回它的地址 int* ipa = new int[100]; //在堆上分配一个大小为100的int数组并返回数组的首地址
-
用new分配的数组大小不必是常量,可以在程序运行期间指定
示例:
int size; cin>>size; int* nums = new int [size]; //数组大小size在程序运行期间指定 for(int i =0;i<size;i++) cin>>nums[i];
若在运行期间指定数组大小,数组大小的变量一定要提前初始化或赋值
-
new表达式的另一种种形式允许程序员将对象创建在已经分配好的内存中,这种形式被称为定位new表达式
new (指针)类型
#include <iostream> #include <new> using namespace std; char* buf = new char [1000]; //预分配一段空间,首地址在buf中保存 int main() { int* pi = new (buf) int; //在buf创建一个int对象,此时不再重新从堆上分配空间 }
使用定位new,必须包含标准库头文件
(2)delete
-
堆上的空间在使用之后必须释放,否则会造成内存泄漏。释放new分配的单个对象的delete形式为
delete 指针
-
delete之后的指针ip不是空指针,而是“空悬指针”。此时继续间接使用ip指向的单元是非法的,会引起不可预料的运行错误
处理方式为:将被delete的指针赋为空指针,或者使其指向其他变量以重新使用该指针
-
释放new分配的数组指针的delete形式为
delete [] 指针
四.引用
1.左值引用
-
引用又称别名,它可以作为对象的另一个名字。一般使用“引用”来指“左值引用”
定义左值引用的语法:
类型 &引用变量 = 初始值;
-
引用必须被初始化,初始值是一个有内存地址的对象,如变量
引用一旦初始化,就不能再绑定到其他的对象,对引用的所有操作都会被应用在它所绑定的对象上。
-
引用只能绑定到对象上,不能与字面值或某个表达式的计算结果绑定在一起。
因为引用本身不是一个对象,所以不能定义引用的引用
2.右值引用
-
右值引用有一个重要的性质:只能绑定到一个将要销毁的对象。因此,可以自由地将一个右值引用的资源移动到其他对象中
-
定义右值引用的形式如下:
类型 &&右值引用变量 = 右值表达式;
-
右值引用必须初始化,初始值是右值表达式,不能将右值引用直接绑定到一个左值上
五.枚举
-
枚举类型定义了一组命名的整数常量,以提高代码的可读性
enum TrafficLight {red, green, yellow}; //Trafficlight枚举类型定义了三个常量:0、1、2分别和名字red、green以及yellow关联 TrafficLight stop = red; //此时可以用TrafficLight定义枚举变量
-
未指定值的枚举成员,编译器会赋给它相邻的下一个整数值
示例:
enum ShapeType {circle=10, square=20, rectangle};
此时rectangle成员的值是21
-
可以用未命名的枚举类型定义常量
示例:
enum {False, True}; //定义了两个常量False和True,值分别是0和1