C++课程笔记(复合类型与函数)
C++课程笔记
第四章
4.1指针和引用
指针可以持有对象的地址,引用则是对象的别名
4.1.1指针
指针持有一个对象的地址,称为指针“指向”该对象,通过指针可以间接操纵它指向的对象
定义指针
每个指针都有相关的类型,要在定义指针时指出,指针的类型即指针指向的类型类型
指针变量;
指针存放指定类型对象的地址,要获取对象的地址,使用取地址运算符“&”
int a=10;
int pi=&a;
如果指针指向一个对象,则可以通过指针间接访该对象,使用指针解引用运算符“”
int a=10,b=10;
int *pi=&a;
*pi=b;
指针本身也是对象,指向一个对象的指针有两个存储单元与之相关,一个是指针自己的存储单元,里面存放着所指对象的地址;
另一个就是指针指向的对象的存储单元,里面存放该对象的值。也可以定义存放指针对象的地址的指针
int **ppi=π
指针不能保存非地址值,也不能被赋值或初始化为不同类型的地址值
空指针
指针值为0时是一个空指针,即不指向任何对象的指针,不能对空指针解引用
表示空指针的3种方法
指针值为0
int *pi=0;
用nullptr初始化或赋值给一个指针(C++11)
int *pi=nullptr;
使用头文件中的预处理常量NULL
int *pi=NULL;
指针的运算
同类型的指针可以进行相等(==)或不相等(!=)的比较操作,比较的结果是布尔类型
指针可以进行加或减整数值的算术运算
自增、自减运算适用于指向数组元素的指针
void*指针
可以持有任何类型的地址值,即通用指针
只能表明相关的值是个地址,但是该地址保存的对象类型不知道,不能操纵void指针指向的对象,只能传送该地址值或者和其他地址值进行比较,且不允许void指针到其他类型指针的直接赋值
4.1.2new和delete
堆(heap)、自由存储区、动态存储区
系统为所有程序提供了一个运行时可用的内存池,这个内存池被称为程序的自由存储区或堆
动态内存管理方法
1.C++通过new和delete运算符进行动态存储空间的管理
(1)new运算符(用于动态空间的申请)
在堆上动态分配空间,创建对象,并返回对象的地址,一般将new返回的地址保存在指针变量中,以便间接访问堆上的对象。
new表达式的三种形式
分配特定类型单个对象,并返回其地址
new 类型 或者 new 类型(初始值)
分配指定类型和大小的数组
new 类型[数组大小]
在堆上分配指定类型和大小的数组(连续内存空间),返回数组首地址,用new分配数组时,并未得到一个数组类型的对象,而是得到一个数组元素类型的指针
定位new,在指定位置分配空间
new (指针) 类型;
定位new在指针指向的空间中创建一个指定类型的对象,程序员可以预先分配大量的内存,以后通过定位new表达式在这段内存中创建对象
使用定位new,必须包含标准库头文件
注:堆上的空间在使用后必须释放,否则会造成内存泄漏,即动态内存空间使用后未回收,导致一直被他占据
(2)delete运算符
new运算符分配的空间用delete运算符释放
释放new分配的单个对象的delete形式
delete 指针;
释放new分配的数组的delete形式
delete[] 指针;
定位new没有对应的delete表达式
执行delete运算后,指针ip指向的空间被释放,不能再使用ip指向的内存,但是ip这个指针变量自己的存储空间不受影响
delete后的ip不是空指针,而是“空悬指针”,即指向不确定的单元
delete之后,继续通过ip间接使用这个单元是非法的,会引起不可预料的运行错误
2.左值引用类型
左值引用又称为别名,它可以作为对象的另一个名字,通过左值引用可以间接地操纵对象,在程序中,引用主要用作函数的参数
(1)引用的定义和初始化
引用由类型标识符和一个取地址符(&)来定义
类型& 引用变量 = 初始值;
引用必须被初始化,初始值是一个有内存地址的对象
左值引用的初始化和一般变量的初始化不同
一般在初始化变量时,初始值会被复制到新建的对象中
定义引用时,程序把引用和它的初始值绑定在一起,而不是将初始值拷贝给引用
值引用的初始化和一般变量的初始化不同
因为无法令引用重新绑定到另外一个对象,所以引用必须初始化
使用左值引用时注意
引用并非对象,是为已存在的对象所起的另一个名字
引用只能绑定到对象(有内存地址)上,不能与字面值或某个表达式的计算结果绑定在一起
引用本身不是对象,所以不能定义引用的引用
4.1.5const限定指针和引用
const限定词将一个对象限定为常量。Const也可限定指针和引用。
1.const限定指针
(1)指向const对象的指针(非const )
const 类型 cp; 或者类型 const cp;
cp是指向常量的指针,它所指向的内存中的内容不可以改变,即cp的值不能改变
const int ival=1024;
const int pi=&ival;
(2)指向非const对象的const指针
类型 const cp = &a;
cp是常量指针,初始化后值不能改变,指向固定的单元(a值可改变,pi指向a不可改变)
C++允许将一个非const地址赋值给const指针
int a=1024;
int const pi=&a;
(3)指向const对象的const指针
const 类型 const cp = &a;
例
const int ival =5;
const int const pi = &ival;
//pi是一个指向const对象的const指针
第一个const限定int,表示指针指向的单元是常量;
第二个const限定pi,表示指针的值也是一个常量
因此,指针pi所在内存的值不允许改变,它所指向内存的值也不能改变
2.const限定左值引用
把引用绑定到const对象上,成为常量的引用。const限定的左值引用不可修改它绑定的对象。
(1)const引用可以绑定到const对象
不能用非const引用指向const对象
(2)const左值引用可以绑定到非const对象,但是const引用不能用来修改它所绑定的对象
int ival = 10;
const int &r1= ival;
//正确:允许将const引用绑定到非const对象上
ival = 20; //正确
r1 = 20; //错误:r1是const引用,不能修改
(3)const左值引用可以绑定到生成右值的表达式
可以用任意表达式初始化const引用,只要表达式的结果能转换成引用的类型即可
(4)const限定引用的含义
const引用仅对引用自己可参与的操作进行了限定,对所指向的对象本身是不是常量未作限定。因为指向的对象也可能不是const,所以允许通过其他途径改变它的值
4.2 结构体
结构体把一组来自不同类型的数据组合在一起构成复合类型,其中的每个数据都是结构体的成员。
结构体由关键字struct定义,语法形式:
struct 结构体类型名{
成员声明;
};
结构体的成员不能独立使用,必须由结构体类型的变量通过成员选择运算符“.”来选择,或者由结构体类型的指针通过“->”运算符选择。
定义结构体类型之后,就可以创建该类型的变量,结构体变量的成员在内存中按声明的顺序依次存放
4.3内置数组
数组与指针
使用数组时一般会转换为指针,数组名称表示数组第一个元素的地址,它的类型是类型的指针。
例
int a[5];
ia是一个int类型的指针常量
ia和&ia[0]都表示数组第一个元素的地址
可以使用指针对数组进行访问
注:当使用数组名作为一个auto 变量的初始值时,得到的类型是指针而不是数组
decltype(数组名)返回的是数组类型:大小和元素类型
数组元素和地址
一维数组元素在内存中按下标顺序依次存放,一维数组a[n]的元素a[i]在内存中地址是a+i。多维数组在内存中按行序存储,二维数组a[m][n]的元素a[i][j] 在内存中的地址是a+(in+j)
使用指针访问数组时需要控制指针的范围,确保指针指向数组的元素
1.begin()和end()
让指针在数组上的使用更简单更安全
在头文件中定义
用法
(1)begin(数组名)
返回指向数组第一个元素的指针
(2)end(数组名)
返回指向数组最后一个元素的下一个位置的指针
2.数组上的指针运算
指向数组的指针可以执行解引用、自增、自减、加或减一个整数、两个指针相减、比较等运算
指针加或减一个整数n,结果仍是指针
新指针指向的元素与原来的指针相比前进或后退了n 个位置
两个指针相减的结果是它们之间的距离
参与运算的两个指针必须指向同一个数组中的元素
两个指针如果指向同一个数组的元素,可以利用关系运算符对其进行比较
4.4 标准库类型string
标准库string类
string 表示可变长度的字符序列,字符串是对象
string 类支持字符串对象的各种操作
各种初始化方式
字符串之间的复制、比较、连接
查询字符串长度和判断字符串是否为空
访问字符串中的单个字符
使用string 类要包含头文件
初始化string对象的方式
string的常用操作
1.读写string对象
(1)使用标准库中iostream可以读写string对象
可以用循环读取未知数量的string对象
输入操作符返回输入流对象,如果输入流对象处于有效状态,表示没有遇到文件结束或非法输入
//读取输入流中的单词,直到文件结束
string word;
while(cin >>
word)
cout << word << endl;
(2)getline()函数
两个参数:输入流对象和存放读入字符串的string对象
从指定输入流中读取内容,遇到换行符为止;将所读内容存入指定的string对象中,流中的换行符被读取并丢弃
返回参数输入流对象
//每次读取一行文本,直到文件结束
string line;
while(getline(cin,
line))
cout << line << endl;
2. 判断stirng对象是否为空
empty()函数判断string对象是否为空,返回一个布尔值
3.size()函数返回string对象的长度,即对象中字符的个数
返回的长度是string::size_type类型
4.比较string对象
可以用关系运算符比较两个字符串对象
(1)两个string相等意味着它们的长度相同,并且所包含的字符也完全相同
(2)字符串的大小关系依照字典顺序定义且区分大小写字母
5.string的赋值和连接
(1)允许把一个string对象的值赋给另一个string对象,也可以为string对象赋一个字符串字面值常量
(2)两个字符串可以直接用运算符“+”连接,结果得到一个新的string对象,"+"运算符要求至少有一个运算对象是string
4.5 标准库类型vector
vector表示对象的集合,其中所有对象的类型都相同,可以通过索引访问各个对象。
vector是对象,其中容纳着其他对象,被称为容器
vector的用法
vector是长度可变的向量,可替代内置数组
。要使用vector,必须包含头文件
1.定义vector时必须指定元素的类型,格式为:
vector<元素类型> 变量名;
定义的同时可以初始化vector对象,常用方法
vector支持的操作2.vector的大小可以动态变化
可以在初始化时指定vector对象的大小和初始值,常见用法是创建一个空vector,根据需要动态添加元素。
(1)push_back()
将一个值添加到vector的末尾,并使vector的大小增加
(2)pop_back()
删除vector对象末尾的元素
在vector中添加删除元素的注意事项
push_back()函数向vector中添加元素时,会创建参数的一个副本,添加的是这个新值而不是原始值
使用pop_back()函数删除vector对象中的元素时,要先检测对象是否为空
如果要用循环向vector对象中添加元素,不能使用范围for,范围for不能改变所遍历的容器的大小
不能用下标运算符向vector中添加元素
下标运算符只能访问已经存在的元素
3.访问vector中的元素
使用下标运算符可以获取vector中指定位置的元素
vector类型对象的下标类型是vector::size_type,用下标访问vector中的元素要确保下标的合理范围
下标运算符不改变vector对象的大小
第五章函数
5.1 函数基础
函数是一个命名的代码块,通过调用函数可以执行相应的代码,用来实现各种算法,完成特定的任务,库函数,程序员自定义的函数。
1.函数定义
函数可以被看作是一个由用户定义的操作
函数一般用一个名字表示,即函数名
函数的操作数称为参数,由参数列表指定
函数的结果称为返回值,其类型称为函数返回类型
函数执行的动作在函数体中指定
函数定义的语法形式为
返回类型 函数名(参数列表) { 函数体 }
2.函数定义——参数列表
函数的参数列表不能省略,如果函数没有任何参数,可以用空参数表或void参数表表示
参数列表由逗号分隔的形参类型列表构成,每个形参类型后跟一个可选的形参名字
函数必须指定一个返回类型,函数返回类型可以是内置类型、复合类型或用户自定义的类类型,函数和内置数组不能用作返回类型,但是,可以返回函数指针和数组首地址
如果函数不返回任何结果,将其返回类型声明为void
3.函数调用
调用执行函数的形式
函数名(实参列表);
形参和实参
函数定义中的参数被称为形式参数,简称形参
在调用函数时提供的数据称为实际参数,简称实参
函数调用时提供的实参个数和类型必须与函数定义中的形参个数和类型匹配
编译器进行参数类型检查,尝试自动类型转换
3.函数调用和返回
(1)函数调用时完成两项工作
一是用实参初始化函数对应的形参
二是将控制权转移给被调用的函数
此时,主调函数的执行暂时中断,被调函数开始执行
(2)遇到return语句时,结束函数的执行,return语句的作用
一是返回return语句中的值(如果有的话)
二是将控制权从被调函数转移回主调函数
函数的返回值用于初始化调用表达式的结果,之后继续完成调用所在的表达式的剩余部分
4.函数声明
函数在使用之前必须声明,一个函数可以在程序中多次声明,函数定义也可以被用作声明,但是函数在程序中只能定义一次
函数声明的形式
返回类型 函数名(参数列表) ;
5.2参数传递
参数传递是指用函数调用的实参来初始化函数形参存储区的过程,函数的形参是局部对象,仅在函数的作用域内可见
每次调用函数时,会创建形参变量,并用传入的实参初始化形参,形参的类型决定了实参初始化形参的方式
1.参数传递方式
(1)按值传递
当实参的值被复制给形参时,形参和实参是两个独立的对象,实参被称为按值传递,或传值调用函数
把实参的值复制到形参的存储区中,即用实参值初始化形参
(2)传指针:使用指针参数是传地址值
(3)传引用,按引用传递参数
当形参是引用类型时,对应的实参被称为按引用传递,或者传引用调用函数,此时,引用形参绑定到实参,是实参对象的别名
2.函数调用的过程
(1)函数调用和返回
函数调用会使程序的控制权传递给被调函数,而当前活动函数的执行被挂起
当被调函数执行完成时,主调函数从调用语句之后的语句恢复执行
函数在执行完函数体的最后一条语句或遇到return语句时返回
默认情况下,函数的返回值是按值传递的
(2)函数的活动记录
函数在运行时使用的程序运行栈中分配的存储区,一直与该函数相关联,直到函数结束时被自动释放
按值传递实参时,函数不会访问当前调用的实参,函数中处理的只是实参的副本,这些副本在运行栈中存储,改变这些值不会影响实参
函数执行结束后,函数的活动记录将从栈中弹出,这些局部值就消失了
3.按值传递参数的适用性
按值传递参数的特点
简单直接,不会改变实参的内容
按值传递参数不适合的情况
大型的类对象或结构体变量作为实参时
在运行栈中分配空间并复制对象,时间和空间开销过大
4.引用参数
(1)当参数是引用时,函数接收的是实参的左值而不是值的副本,即形参是实参的引用,或者说是别名
函数操纵的形参是实参的别名,因而可以改变实参的值
(2)引用参数的用途
通过传递引用在函数内修改实参的值,向主调函数返回额外的结果,向函数传递大型的结构体变量或类对象
将参数声明为const引用,避免在函数内部修改引用参数,类的拷贝构造函数和重载的运算符函数中
使用引用参数传递大对象
可以避免大对象参数的复制,且可以访问实参对象
将参数声明为const引用,避免在函数内部修改引用参数
5.参数传递方式的选择
(1)对于内置类型的小对象,传值的方式更简单直接
(2)如果想在函数内改变实参,使用传引用或传指针的方式,传指针的语法比引用复杂一些,但使用起来更清晰明确。对于类类型的对象,尽量使用引用传递参数,效率更高。使用const限定可以避免实参被修改
(3)特殊要求
数组和函数作参数时必须传指针
拷贝构造函数的参数必须传引用
6.数组参数
数组作参数时,将传递数组第一个元素的地址
数组的长度与参数声明无关,如果在函数内部要使用数组的长度,应该将它作为单独的一个参数
被调函数内对参数数组的改变将应用到数组实参上
将形参数组声明为const表明不希望改变数组元素
用标准库中的string和vector对象作为函数的参数
string和vector是对象,不需要额外的参数指定长度
5.3 返回类型和return语句
函数的执行结果由return语句返回,return语句结束当前正在执行的函数,将控制权返回给函数的调用者
return语句有两种形式:
return;
用在返回类型为void的函数中,不是必需的
return 表达式;
非void的函数必须返回一个与声明类型匹配的值,否则会引起编译错误
1.返回值
默认情况下,函数的返回值是按值传递的
得到控制权的函数将接收return语句中指定的表达式值的副本
返回值的方式和初始化变量或形参的方式完全一样
返回的值用于初始化调用点的一个临时量,该临时量就是函数调用的结果
2.返回引用
将函数声明为返回引用,则不需要对return语句中的表达式进行复制,而是返回对象本身。函数返回的引用仅是它所指向对象的一个别名
3.函数的返回类型
函数的返回类型决定了函数调用表达式的类型,以及是否是左值
调用一个返回引用的函数得到左值
其他返回类型得到右值
返回引用的函数调用表达式可以作为左值使用
可以为返回类型是非const引用的函数的结果赋值
对引用返回值的修改会改变实际返回的对象,为了避免这种情况,可以将返回值声明为const
5.4 函数重载
多个函数可以共享同一个函数名,针对不同的参数类型提供不同的操作
重载函数
如果同一个作用域内的几个函数名字相同但形参列表不同,则它们是重载函数
调用重载函数时,编译器会根据实参的类型推断出要调用的是哪个函数
1.重载函数的判断
重载函数的参数表必须不同,或参数个数不同,或参数类型不同。返回类型不能区分两个重载函数,因为调用函数时可以忽略函数的返回值
const限定词对形参类型的影响
非指针和引用上的const限定词不能区分重载函数
const限定指针或引用时,可以实现函数重载
2.重载函数的调用
调用函数时,如果存在多个重载函数,编译器将根据函数调用中指定的实参进行选择
用实参的类型和个数与函数的形参进行匹配