一、递归算法
程序直接或间接调用自身的编程技巧称为递归算法。
递归算法通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解。其基本思想是把问题层层分解,把一个不能或不好解决的大问题转化为一个或几个小问题,再把这些小问题进一步分解成更小的小问题,最小问题可以直接解决。
递归的关键在于找出递归定义和递归的终止条件;递归算法解题的三个步骤: 1)分析问题、寻找递归:找出大规模问题与小规模问题的关系,这样通过递归使问题的规模逐渐变小。 2)设置边界、控制递归:找出停止条件。 3)设计函数、确定参数:设计函数体中的操作及相关参数。
二、复杂数据类型
(一)结构体
使用结构体,必须要先声明一个结构体类型,再定义和使用结构体变量。
结构体类型的声明格式: struct 类型名{ 数据类型1 成员名1; 数据类型2 成员名2; … };
也可以把结构体类型声明和变量定义合在一起:struct 类型名{ 数据类型1 成员名1; 数据类型2 成员名2; … } 变量名;
需要注意的地方:(1)可以对结构体变量的整体进行操作。 例如:swap(a[i],a[j]) (2)可以对结构体变量的成员进行操作。 引用结构体变量中成员的格式为:结构体变量名. 成员名 (3)结构体变量的初始化方法与数组类似。
(二)标准库类型string
1.string 表示可变长度的字符序列。string 类支持字符串对象的各种操作:各种初始化方式;字符串之间的复制、比较、连接; 查询字符串长度和判断字符串是否为空;访问字符串中的单个字符。
注意:使用string 类要包含头文件<string>
2.初始化string对象的方式有:(1)默认初始化string s1;(s1是一个空串) (2)拷贝初始化:把=右边的初始值复制到左边新创建的对象中 string s2=s1; string s3="value"; (3)直接初始化:初始值可以有一个或多个 string s3("value"); string s4(n,'c');
3.stirng的常用操作
(1)读写string对象
使用标准库中iostream可以读写string对象;可以用循环读取未知数量的string对象;getline()函数:两个参数:输入流对象和存放读入字符串的string对象
(2)判断string对象是否为空
empty()函数判断string对象是否为空,返回一个布尔值(stringObj.empty())
(3)获取string对象的长度
size()函数返回string对象的长度,即对象中字符的个数。 返回的长度是string::size_type类型
(4)比较string对象
可以用关系运算符比较两个字符串对象。☆两个string相等意味着它们的长度相同,并且所包含的字符也完全相同;字符串的大小关系依照字典顺序定义且区分大小写字母
(5)string对象的赋值和连接
两个字符串可以直接用运算符“+”连接,结果得到一个新的string对象。 "+"运算符要求至少有一个运算对象是string
4.string对象和C风格字符串
字符串字面值不是string类型,而是const char*类型。
string对象和C风格字符串的转换:可以将C风格的字符串直接赋给string对象,反之不可。
用string对象的c_str()操作可以返回一个表示该string对象内容的C风格字符串,结果为const char*类型,即C风格字符串的首地址。
(三)指针和引用
指针
1.直接访问:通过变量的名字直接访问为程序中定义的变量分配的内存单元,存取变量的值
间接访问:使用变量的内存地址找到存放数据的单元,间接访问其中的内容
2.指针
通过指针可以间接操纵它指向的对象(指针不能保存非地址值,也不能被赋值或初始化为不同类型的地址值)
定义指针时,应该对指针进行初始化。
同类型的指针可以进行相等(==)或不相等(!=)的比较操作,比较的结果是布尔类型。
指针可以进行加或减整数值的算术运算。
自增、自减运算适用于指向数组元素的指针。
3.指针的类型
指针的类型即指针指向的类型。每个指针都有相关的类型,需要在定义指针时指出 type* pointer;指针的类型指出了如何解释该内存地址保存的内容,以及该内存区域应该有多大;不同类型指针的表示方法和保存的地址值并没有分别,区别只是指针指向的对象类型不同。
4.取地址运算符“&”
指针存放指定类型对象的地址,要获取对象的地址,使用取地址运算符“&”。
int *pi = &ival; // pi存放int变量ival的地址或者说pi指向ival
5.指针解引用运算符“*”
如果指针指向一个对象,则可以通过指针间接访该对象,使用指针解引用运算符“*”。
int *pi = &x; *pi = y; // 间接操作pi指向的x,即x = y
6.空指针
表示空指针的2种方法: 0 和预处理常量NULL
7.通用指针void
不能操纵void指针指向的对象,只能传送该地址值或者和其他地址值进行比较;不允许void指针到其他类型指针的直接赋值。
8.指针的应用
构建链式的数据结构,如链表和树;管理程序运行时动态分配的对象;作为函数的参数。
9.存储空间分配策略
静态(编译时)分配空间和动态(运行时)分配空间:
静态对象是有名字的变量,可以直接对其进行操作;动态对象没有名字,要通过指针间接地对它进行操作。 静态对象的空间分配与释放由编译器自动处理,动态对象的空间分配与释放必须由程序员显式地管理。
程序使用动态内存的原因:程序不知道自己需要使用多少对象;程序不知道所需对象的准确类型;程序需要在多个对象间共享数据
C++通过new和delete运算符进行动态存储空间的管理:
new 运算符:
new表达式的三种形:1) 分配单个对象:new 类型 或者 new 类型(初始值) 2)分配多个连续存储的对象:new 类型[数组大小] 3)定位new,在指定位置分配空间:new (指针) 类型;(使用定位new,必须包含标准库头文件<new>)。
delete运算符:
new运算符分配的空间用delete运算符释放。 释放new分配的单个对象的delete形式 delete 指针; 释放new分配的数组的delete形式 delete[] 指针; 定位new没有对应的delete表达式。
10.空悬指针
delete后的ip不是空指针,而是“空悬指针”,即指向不确定的单元。
引用
1.引用又称为别名,它可以作为对象的另一个名字; 通过引用可以间接地操纵对象; 在程序中,引用主要用作函数的参数。
2.引用由类型标识符和一个说明符(&)来定义: type& refVariable = leftValue;
引用必须被初始化。一旦初始化完成,引用将和它的初始值对象一直绑定在一起 因为无法令引用重新绑定到另外一个对象,所以引用必须初始化。
引用之间的相互赋值是它们指向的对象之间的赋值,引用关系本身并不改变。
有空指针但没有空引用。
const限定指针
1.指向const对象的指针(非const )
const type *cp; 或者type const *cp;
cp是指向常量的指针,它所指向的内存中的内容不可以改变,即*cp的值不能改变
2.指向非const对象的const指针
type* const cp = initAddressValue;
cp是常量指针,初始化后值不能改变,指向固定的单元
3.指向const对象的const指针
const type* const cp = initAddressValue;
const限定引用
const限定的左值引用不可修改: const引用可以绑定到const对象;不能用非const引用指向const对象。
const左值引用可以绑定到非const对象,但是const引用不能用来修改它所绑定的对象。
const左值引用可以绑定到生成右值的表达式。可以用任意表达式初始化const引用,只要表达式的结果能转换成引用的类型即可。
数组与指针
使用数组时一般会转换为指针,ia和&ia[0]都表示数组第一个元素的地址(可以使用指针对数组进行访问)
一维数组元素在内存中按下标顺序依次存放,一维数组a[n]的元素a[i]在内存中地址是a+i。 多维数组在内存中按行序存储,二维数组a[m][n]的元素a[i][j] 在内存中的地址是a+(i*n+j)。
begin()和end()
begin(数组名) 返回指向数组第一个元素的指针;
end(数组名) 返回指向数组最后一个元素的下一个位置的指针。
三、类和对象
(一)
1.类是用户自定义的类型。类的定义包括行为和属性两个部分。属性以数据表示,行为通过函数实现。
类定义的格式:
class 类名
{
public:
公有数据成员和成员函数;
protected:
保护数据成员和成员函数;
private:
私有数据成员和成员函数;
};
注意事项:
类的数据成员可以是其他类的对象,但不能以类自身的对象作为本类的成员,而类自身的指针和引用可以作为类的成员。
类定义必须以分号“;”结束。
类与结构体的区别:没有明确指定类成员的访问权限时,C++结构体的成员是公有的,而类的成员是私有的。
2.成员函数的定义:
返回值类型 类名::成员函数名(参数表)
{
函数体
}
3.对象:对象是类的实例或实体。
对象成员的访问包括: ●圆点访问形式:对象名.公有成员 ●指针访问形式:对象指针变量名->公有成员
注意细节:
在类的定义中不能对数据成员进行初始化;一般将数据成员定义为私有成员或保护成员,将成员函数定义为公有成员; 类的成员可以是其他类的对象,称为类的组合,但不能以类自身的对象作为本类的成员; 类定义必须以分号“;”结束; class与struct的不同: class中,成员缺省情况是private。 struct中,成员缺省情况是public。
(二)
1.函数重载:函数名相同,但参数类型或者个数不同的一组函数。 编译器根据不同参数的类型和个数产生调用匹配 。函数重载用于处理不同数据类型的类似任务。
2.构造函数和析构函数
构造函数是用于创建对象的特殊的成员函数,当创建对象时系统自动调用构造函数。
★构造函数一定要重载
用户没有定义的构造函数时,系统自动提供缺省版本的构造函数
构造函数的作用:
a为对象分配空间; b对数据成员赋初值;c请求其他资源。
构造函数可以有任意类型的参数,但没有返回类型。
析构函数: 当一个对象作用结束时,系统自动调用析构函数。析构函数在一个类中只有一个。
没有用户定义析构函数时,系统提供缺省版本的析构函数(隐式调用)。
(三)默认构造函数(系统自动生成)
1.形式: 类名::类名(){}
2.通常,利用构造函数创建对象有以下两种方法:
(1) 利用构造函数直接创建对象.其一般形式为: 类名 对象名[(实参表)];
不带参数:当用户没定义构造函数时,调用默认的构造函数;当用户定义了构造函数时,调用无参的构造函数(没有无参构造函数时要出错!系统不会调用默认构造函数)
带实参表:系统按照重载函数匹配原则,调用对应的构造函数。
注意:
定义构造函数至少要两个,其中包括一个不带参的。
1)创建对象时,一定要按照定义的构造函数的形式去创建,参数类型方式不匹配的都是错误的。
2)要创建构造函数,就一定要创建一个不带参数的构造函数。
(2) 利用构造函数创建对象时,通过指针和new来实现。其一般语法形式为:
类名 *指针变量 = new 类名[(实参表)];
3.构造函数的初始化列表——数据成员的初始化
构造函数初始化成员有两种方法:
A.使用构造函数的函数体进行初始化
B.使用构造函数的初始化列表进行初始化
必须使用参数初始化列表对数据成员进行初始化的几种情况:
数据成员为常量; 数据成员为引用类型; 数据成员为没有无参构造函数的类的对象。
类成员的初始化的顺序: 按照数据成员在类中的声明顺序进行初始化,与初始化成员列表中出现的顺序无关。
4.构造函数与set成员函数的区别:
构造函数的作用是赋初值,不能显示调用,但可以在程序的任何地方显式调用对象的set函数;构造函数只能调用一次,而set函数可以调用多次。
写了带参的构造函数就一定要写一个不带参构造函数。
5.类的定义基本构成:数据成员;构造函数(带参、无参);get、set函数;析构函数。
(四)this指针
需要显式引用this指针的三种情况:
1)在类的非静态成员函数中返回类对象本身或对象的引用的时候,直接使用 return *this,返回本对象的地址时,return this。 2)当参数与成员变量名相同时,如this->x = x,不能写成x = x。 3)避免对同一对象进行赋值操作,判断两个对象是否相同时,使用this指针。
(五)复制构造函数
1.复制构造函数用一个已有同类对象创建新对象进行数据初始化
语法形式: 类名 :: 类名(const 类名 & 引用名 , …);
复制构造函数可写在类中,也可以写在类外。 复制构造函数要求有一个类类型的引用参数。
2.深复制与浅复制
在用一个对象初始化另一个对象时,只复制了数据成员,而没有复制资源,使两个对象同时指向了同一资源的复制方式称为浅复制。即:对于复杂类型的数据成员只复制了存储地址而没有复制存储内容。深复制不仅复制了数据成员,也复制了资源。
(六)常成员
1.常成员函数
在类中使用关键字const说明的函数为常成员函数,常成员函数的说明格式: 类型说明符 函数名(参数表) const;
常成员函数不能更新对象的数据,也不能调用非const修饰的成员函数(静态成员函数、构造函数除外)
2.常对象
如果在说明对象时用const修饰,则被说明的对象为常对象。常对象的说明形式: 类名 const 对象名[(参数表)]; 或者 const 类名 对象名[(参数表)]; 在定义常对象时必须进行初始化,而且不能被更新。
(七)静态成员
(1)静态成员函数在类外定义时不用static前缀。 (2)静态成员函数主要用来访问同一类中的静态数据成员。 (3) 私有静态成员函数不能在类外部或用对象访问。 (4)可以在建立对象之前处理静态数据成员。 (5)编译系统将静态成员函数限定为内部连接(在其他文件中不可见)。 (6)静态成员函数中是没有this指针的。 (7)静态成员函数不访问类中的非静态数据成员。如有需要,只能通过对象名(或指向对象的指针)访问该对象的非静态成员。
(八)
1.友元函数(不建议使用,因为破坏了类的封装性)
2.友元类
A是另一个类,B类的成员函数要访问A的私有成员,此时,需要在A类中,把类B声明为A的友元。
若F类是A类的友元类,则F类的所有成员函数都是A类的友元函数。
友元类的声明格式:friend class 类名;
(九)类的包含(类的组合)——建议使用
当一个类中含有已经定义的类类型成员,带参数的构造函数对数据成员初始化,须使用初始化语法形式。
构造函数 ( 形参表 ) : 对象成员1(形参表 ) , … , 对象成员n (形参表 ) ;
若定义了类A,类B包含了类A:
class B
{
private:int b;
A aa;//不能加括号
…
};
(十)管理信息系统开发基本步骤
了解系统要实现什么功能, 对系统功能进行分类汇总;
设计操作类: 一个成员函数实现一个具体功能; 设确定数据成员,设计构造函数;
设计数据类: 整合操作类所需要的数据、设计数据类。 明确如何修饰数据成员、确定数据类的成员函数
编码调试数据类:在main函数中创建数据类对象、调用各成员函数
调试操作类: 先调试最急需的成员函数; 再陆续增加成员函数 设计多组测试数据,用不同的测试数据反复运行程序
注:每写一个函数(功能),就要调试一次(滚雪球的方式)。主函数中的操作类的函数要多测几次,每种情况都要测试。
四、运算符重载
(一)
1.运算符重载规则:
不能重载的运算符: . :: .* ?: sizeof
2.重载运算符函数可以对运算符作出新的解释,但原有基本语义不变
3.运算符函数可以重载为成员函数或友元函数
二元运算符中默认第一个操作数(Object.L)为当前操作数
(二)
1.用成员函数重载运算符
——形参比实际操作数少一个
成员运算符函数的原型在类的内部声明格式: class X { //… 返回类型 operator运算符(形参表); //… } ;
在类外定义成员运算符函数的格式: 返回类型 X::operator运算符(形参表) { 函数体 }
对双目运算符而言,成员运算符函数的形参表中仅有一个参数,它作为运算符的右操作数,此时当前对象作为运算符的左操作数,它是通过this指针隐含地传递给函数的。
对单目运算符而言,成员运算符函数的参数表中没有参数,此时当前对象作为运算符的一个操作数。
▲对于Complex operator+(Complex & c2)的有关说明:
创建一个对象作为运算符重载的形参都是对象,在对象处理过程中,因为是复杂类型,一般都不用直接传数值的方式,而是用传引用(传地址也可以,但一般都是用传引用)。——对象作为形参的时候,一般要用传引用的方式出现,甚至还可以加const来限定。
2. 用友元函数重载
——参数不少,怎么写就怎么用(有几个操作数就写几个形参)
友元函数重载运算符常用于运算符的左右操作数类型不同的情况
C++中不能用友元函数重载的运算符有: = () [] ->
成员运算符函数与友元运算符函数的比较:
(1) 成员运算符函数比友元运算符函数少带一个参数(后置的++、--需要增加一个形参)。
(2) 双目运算符一般可以被重载为友元运算符函数或成员运算符函数,但当操作数类型不相同时,必须使用友元函数。
如果第一个数是默认值,则用成员函数重载;若第一个数不是默认值,则用友元函数重载运算符。
(三)
1.赋值运算符重载用于对象数据的复制
operator= 必须重载为成员函数
重载函数原型为: 类名 & 类名 :: operator= ( 类名 ) ;
2.[] 和 () 只能用成员函数重载,不能用友元函数重载
重载下标运算符 [] :[] 运算符用于访问数据对象的元素。 重载格式: 类型 类 :: operator[] ( 类型 ) ;
重载函数调用符 ():() 运算符用于函数调用。 重载格式 类型 类 :: operator() ( 参数表 ) ;
(四)重载流插入和流提取运算符
istream 和 ostream 是 C++ 的预定义流类;
cin 是 istream 的对象,cout 是 ostream 的对象;
运算符 << 由ostream 重载为插入操作,用于输出基本类型数据;
运算符 >> 由 istream 重载为提取操作,用于输入基本类型数据;
用友元函数重载 << 和 >> ,输出和输入用户自定义的数据类型(重载输出运算符“<<”和输入运算符“>>”只能被重载成友元函数)。
定义输出运算符“<<”重载函数的一般格式:(输入运算符“>>”类似)
ostream& operator<<(ostream& out,class_name& obj)
{
out<<obj.item1;
out<<obj.item2;
.. .
out<<obj.itemn;
return out;
}
学习感受:
相比于大一的时候,我感觉这段时间所听、所学习的理解起来轻松的多了,上半学期学了数据结构和java,对类和对象以及指针有了一定程度的理解和掌握,而且吸取了以前的教训,对自己的学习态度做出了一些改变,上课期间认真地听讲,现在再学习这门课发现老师讲的已经很透彻了,我学习起来也不那么吃力了。
对于最近这两次的作业,可能是对基本的语法掌握的不太好,看到题目然后思考后虽然脑子里有了大体的思路,但是具体做起来还是有点困难,写出来之后也是出错,在不断地修改与调试的过程中也发现了自己存在很多的问题,对以前学过的知识没有真正地完全掌握,而且缺乏学习和修改调试代码的耐心。应该对之前学过的东西加强巩固,对所学的知识及时地应用练习,做到真正地理解和掌握。
在以后的学习中,我应该更加地努力,付出应有的时间认真学习,磨练自己的耐性,写出更好的代码。