初识C++,所以看了下谭浩强老师的《C++程序设计》,第一次看,先了解一下C++中比较突出的概念。以下笔记是摘录自第8,9,11,12章节。先了解基本概念,明白以下C++语言和面向对象的思想,具体的细节问题以后具体分析。
目录
基于对象的程序设计
类和对象的特性
一,对象
1,类
1,概念
2,封装与信息隐蔽
3,抽象
4,继承和重用
5,多态
2,面向对象程序设计的特点
1,面向过程中,所有数据都是公用的。
2,面向对象中,数据与外界分隔。
把数据和操作封装成一个对象。
程序设计者的任务:1,设计所需的各种类和对象。
2,考虑怎样向有关对象发送信息,以完成所需的任务。
3,类和对象的作用
一组数据是与一组操作相对应的。因此人们把相关的数据和操作放在一起,形成一个整体,与外界相对分隔。这就是面向对象的程序设计中的对象。
面向过程: 程序 = 算法 + 数据结构
面向对象: 对象 = 算法 + 数据结构
程序 = (对象 + 对象 + ……)+ 消息
或 程序 = 对象s + 消息
4,面向对象的软件开发
1,面向对象分析
2,面向对象设计
3,面向对象编程
4,面向对象测试
5,面向对象维护
二,类的声明和对象的定义
1,类和对象的关系
2,声明类类型
3,定义对象的方法
1,先声明类类型,然后再定义对象
2,在声明类的同时定义对象
3,不出现类名,直接定义对象(不用)
4,类和结构体的异同
1,C++使struct 具有类的特点,用struct声明的结构体类型实际上就是类。
2,用struct声明的类,如果对其成员不作private或public的声明,系统默认是public。
用class定义的类,如果不作private或public的声明,系统默认是private。
三,类的成员函数
1,成员函数的性质
成员函数可以访问本类对象中任何成员(包括公有的和私有的),可以引用在本作用域中有效的数据。
2,在类外定义成员函数
"::"是作用域限定符,也叫作用于运算符。
如果不带"::"则说明该函数不属于任何类。是全局函数。
类函数必须先在类体中作原型声明,然后在类外定义。也就是说类体的位置应在函数定义之前,否则编译会出错。
3,内置成员函数
inline在类中具体的用法
4,成员函数的存储方式
一个对象所占的空间大小只取决于该对象中数据成员所占的空间,而与成员函数无关。函数的目标代码是存储在对象空间之外的。
如果定义了10个对象,这些对象的成员函数对应的是同一个函数代码段,而不是10个不同的函数代码段。
this指针 初识。
四,对象成员的引用
1,通过对象名和成员运算符访问对象中的成员
对象名.成员名
2,通过指向对象的指针访问对象中的成员
Time t, *p; p = &t; cout << p->hour;
3,通过对象的引用访问对象中的成员
如果为一个对象定义了一个引用,实际上他们是同一个对象,只是用不同的名字表示而已。
五,类的封装性和信息隐蔽
1,公有接口和私有实现的分离
当接口与实现分离时,只要类的接口没有改变,对私有实现的修改不会引起程序的其他部分的修改。
主要功能是使程序的设计、修改和调试简单。
2,类声明和成员函数定义的分离
类声明头文件是用户使用类库的接口
包含成员函数定义的文件就是类的实现。
类的声明和函数定义是分别放在两个文件中。
3,面向对象程序设计中的几个名词
1,方法: 类的成员函数在面向对象程序理论中被称为“方法”。
怎样使用类和对象
一,利用构造函数对类对象进行初始化
1,对象的初始化
类是一种抽象类型,并不占内存空间,所以无处容纳数据。
对象代表一个实体,每一个对象都有它确定的属性。
每一个对象都应当在它建立之时就有确定的内容,否则就是去了对象的意义。
2,用构造函数实现数据成员的初始化
构造函数: 是一种特殊的成员函数,与其他成员函数不同,不需要用户来调用它,而是在建立对象时自动执行。
是在声明类的时候由类的设计者定义的。
1,构造函数的名字必须与类名同名,而不能任意命名。
2,它不具有任何类型,不具有返回值。
3,构造函数不需用户调用,也不能被用户调用。
4,可以用一个类对象初始化另一个类对象
5,在构造函数的函数体中不仅可以对数据成员赋初值,而且可以包含其他语句,例如cout。
但是一般不提倡在构造函数中加入与初始化无关的内容,以保持程序的清晰。
6,如果用户自己没有定义构造函数,则C++系统会自动生成一个构造函数,只是这个构造函数的函数体时空的,没有参数,也不执行初始化操作。
方式:
1)在类内定义构造函数
2)在类内对构造函数进行声明而在类外定义构造函数
3,带参数的构造函数
构造函数名(类型1 形参1,类型2 形参2 ……)
定义对象的形式: 类名 对象名(实参1, 实参2, ……)
4,用参数初始化表对数据成员初始化
这种方法不在函数体内对数据成员初始化,而是在函数首部实现。
类名 :: 构造函数名([参数表])[:成员初始化表]
{
[构造函数体]
}
//其中方括号内为可选项。
说明:如果数据成员是数组,则应当在构造函数的函数体中用语句对其赋值,而不是在参数初始化表中对其初始化。
class Student
{
public :
Student(int n, char char s, nam[]):num(n), sex(s)
{
strcpy(name, nam);
}
private:
int num;
char sex;
char name[20];
};
Student stud1(10101, 'm', "wang_li");
5,构造函数的重载
概念:在一个类中可以定义多个构造函数,以便为对象提供不同的初始化的方法,供用户选择。
这些构造函数具有相同的名字,而参数的个数或参数的类型不相同。
例子:
#include <iostream>
using namespace std;
class Box
{
public :
Box(); //声明一个无参的构造函数
Box(int h, int w, int len) : height(h), width(h), length(len)
{} //定义一个有参的构造函数,用参数的初始化表对构造成员初始化
int colume();
private:
int length;
int width;
int length;
};
Box::Box() //类外定义无参构造函数Box
{
height = 10;
width = 10;
length = 10;
}
int Box :: volume() //类外定义声明成员函数volume
{
return (height * width * length);
}
如上,在建立对象时可以给出无参和两个参数,系统会分别调用相应的构造函数。
6,使用默认参数的构造函数
构造函数中的参数的值既可以通过实参传递,也可以指定为某些默认值,即如果用户不指定实参值,编译系统就使形参取默认值。
1)应该在什么地方指定构造函数的默认参数?
应该在声明构造函数时指定默认值,而不能只在定义构造函数时指定默认值。
2)声明构造函数时,形参名可以省略
Box(int = 10, int = 10, int = 10);
3)如果构造函数的全部参数都指定了默认值,则在定义对象时可以给一个或几个实参,也可以不给出实参。由于不需要实参也可以调用构造函数,因此全部参数都指定了默认值的构造函数也属于默认构造函数。
一个类只能有一个默认构造函数,也就是说,可以不用参数而调用的构造函数,一个类只能有一个。
4)在一个类中定义了全部是默认参数的构造函数后,不能再定义重载构造函数。
一般不应同时使用构造函数的重载和有默认参数的构造函数。
二,析构函数
析构函数是一个特殊的成员函数,它的作用与构造函数相反。析构函数是与构造函数作用相反的函数。
当对象的生命周期结束时,会自动执行析构函数,具体以下4种情况,程序会执行析构函数:
1)如果在一个函数中定义了一个对象,当这个函数被调用结束时,对象应该释放,在对象释放前自动执行析构函数。
2)静态局部对象在函数调用结束时对象并不释放,因此也不调用析构函数,只在main函数结束或调用exit函数结束程序时,才调用static局部对象的析构函数。
3)如果定义了一个全局的对象,则在程序的流程离开其作用域时(如main函数结束或调用exit函数)时,调用该全局的对象的析构函数。
4)如果用new运算符动态地建立了一个对象,当用delete运算符释放该对象时,先调用该对象的析构函数。
析构函数的作用并不是删除对象,而是在撤销对象占用的内存之前完成一些清理工作,使这部分内存可以被程序分配给新对象使用。
一个类可以有多个构造函数,但只能有一个析构函数,它没有函数参数,也不能被重载。
三,调用构造函数和析构函数的顺序
1,同一类存储类别的对象而言:
先进后出
先构造的后析构,后构造的先析构。
2,系统在什么时候调用构造函数和析构函数:
1)如果在全局范围中定义对象(即在所有函数之外定义的对象),那么他的构造函数在本文件模块中的所有函数执行之前调用。
2)如果定义的时局部自动对象(例如在函数中定义对象),则在建立对象时调用其构造函数。如果对象所在的函数被多次调用,则每次建立对象时都要调用构造函数。在函数调用结束、对象释放时先调用析构函数。
3)如果在函数中定义静态局部对象,则只在程序第一次调用此函数定义对象时调用构造函数一次,在调用函数结束时对象并不释放,因此也不调用析构函数,只在main函数结束或调用exit函数结束程序时,才调用析构函数。
注意:主要考虑的时对象的存储类别和生命周期。
四,对象数组
1,定义一个类的对象数组。
2,初始化,调用构造函数。
单个参数的初始化。
多个参数的初始化,编译系统只为每个对象元素的构造函数传递一个实参,所以在定义数组时提供的实参个数不能超过数组元素个数。
实现方法是在花括号中分别写出构造函数名并在括号内指定实参。
五,对象指针
1,指向对象的指针
类名 * 对象指针名;
2,指向对象成员的指针
1)指向对象数据成员的指针
数据类型名 * 指针变量名
2)指向对象成员函数的指针
#1普通的函数指针:
类型名(* 指针变量名)(参数列表);
void (*p)();
p = fun;
(*p)();
#2类的公有成员函数的指针变量:
数据类型名 (类名 :: *指针变量名) (参数列表);
指向一个公有成员函数的一般形式:
指针变量名 = &类名 ::成员函数名;
void (Time :: *p2)(); //*的优先级高于()
p2 = &Time::get_time;
3,this指针
作用:就是区分同类不同对象中成员的调用,如何去找对应的对象。
在每一个成员函数中都包含一个特殊的指针,这个指针的名字是固定的,成为this。
它是指向本类对象的指针,它的值是当前被调用的成员函数所在的对象的其实地址。
this指针是隐式使用的,它是作为参数被传递给成员函数的。
这是编译系统自动实现的。
六,公用数据的保护
既要使数据能在一定范围内共享,又要保证它不被任意修改,这时可以把有关的数据定义为常量。
1,常对象
定义对象时加上const,指定对象为常对象。常对象必须要有初值。
Time const t1(12, 34, 56);
凡保证数据成员不被改变的对象,可以声明为常对象。
类名 const 对象名[(实参表)];
const 类名 对象名[(实参表)];
两者等价。 初始化后不可改变。
说明:如果一个对象被声明为常对象,则通过该对象只能调用它的常成员函数,而不能调用该对象的普通成员函数(除了由系统自动调用的隐式的构造函数和析构函数),常成员函数时常对象的唯一的对外接口。
1)那么怎样才能引用常对象中的数据成员呢?将该成员函数声明为const即可:
void get_time() const; //常成员函数
2)常成员函数可以访问,但是不允许修改常对象中的数据成员的值。
以上保证了常对象中的数据成员的值绝对不会改变。
一定要修改常对象中的某个数据成员的值 可以用关键字 mutable
如: mutable int count;
2,常对象成员
1)常数据成员
注意:只能通过构造函数的参数初始化表对常数据成员进行初始化,任何其他函数都不能对常数据成员赋值。
构造函数只能用参数初始化表对常数据成员进行初始化。
Time :: Time(int h) : hour(h) {} //合法
Time :: Time(int h) {hour = h;} //非法
2)常成员函数
声明常成员函数的一般形式:
类型名 函数名 (参数表) const //const 是函数类型的一部分
3,指向对象的常指针
将指针变量声明为const型,这样指针变量始终保持为初值,不能改变,即其指向不变。
定义指向常对象的常指针变量的一般形式:
类名 * const 指针变量名;
Time *const ptr1 = &tl1;
往往用常指针作为函数的形参,目的是不允许在函数执行过程中噶便指针变量的值,使其始终指向原来的对象。
4,指向常对象的指针变量
定义指向常变量的指针变量的一般形式为:
const char *ptr;
定义指向常对象的指针变量的一般形式为:
const Time *p;
5,对象的常引用
6,const型数据的小结
七,对象的动态建立和释放
new , delete
需要定义一个执行本类的对象的指针变量来存放该地址。
Box *pt;
pt = new Box;
Box *pt = new Box(12, 15, 18);
delete pt;
八,对象的赋值和复制
1,对象的赋值
对象名1 = 对象名2;
stu2 = stu1;
1)对象的赋值只对其中数据成员赋值,而部队成员函数赋值。
2)类的数据成员中不能包括动态分配的数据,否则在赋值时可能出现严重后果。
2,对象的复制
Box box2(box1); //用已有的对象box1去克隆出一个新对象box2
Box box2 = box1;
#普通构造函数和复制构造函数的区别。
九,静态成员
1,静态数据成员
2,静态成员函数
static int volume();
静态成员函数和非静态成员函数的根本区别时:非静态成员函数有this指针,而静态成员函数没有this指针。由此决定了静态成员函数不能访问本类中的非静态成员。
在c++中,静态成员函数主要用来访问静态数据成员,而不访问非静态成员。
十,友元
可以访问与其有好友关系的类中的私有成员,友元包括友元函数和友元类。
1,友元函数
如果在本类以外的其他地方定义了一个函数(这个函数可以使不属于任何类的非成员函数,也可以使其他类的成员函数),在类体中用friend对其进行声明,此函数就称为本类的友元函数。
友元函数可以访问这个类中的私有成员。
1)将普通函数声明为友元函数
#include <iostream>
using namespace std;
class Time{
public:
Time(int , int , int );
friend void display(Time &);
private:
int hour;
int minute;
int sec;
};
Time :: Time(int h, int m, int s)
{
hour = h;
minute = m;
sec = s;
}
void display(Time & t) //这是普通函数,形参t是Time类对象的引用
{
cout << t.hour << " : " t.minute << " : " << t.sec << endl;
}
int main()
{
Time t1(10, 13, 56);
display(t1); //调用display函数, 实参t1是Time类对象
return 0;
}
输出结果:
10:13:56
注意:在引用这些私有数据成员时,必须加上对象名。 因为display不是Time类的成员函数,没有this指针,不能默认引用Time类的数据成员,必须指定要访问的对象。
2)友元成员函数
friend 函数不仅可以是一般函数(非成员函数),而且可以是另一个类中的成员函数。
还有一个概念 提前引用声明
#include <iostream>
using namespace std;
class Date;
class Time
{
public:
Time (int, int, int);
void display(Date &);
private :
int hour;
int minute;
int sec;
};
class Date{
public:
Date(int, int, int);
friend void Time :: display(Date &);
//声明Time中的display函数为本类的友元成员函数
private:
int month;
int day;
int year;
};
Time :: Time(int h, int m, int s)
{
hour = h;
minute = m;
sec = s;
}
void Time :: display(Date &d)
{
cout << d.month << "/" << d.day << "/" << d.year << endl;
cout << hour << " : " << minute << " : " << sec << endl;
}
Date :: Date(int m, int d, int y)
{
month = m;
day = d;
year = y;
}
int main()
{
Time t1(10, 13, 56);
Date d1(12, 25, 2004);
t1.display(dl); //调用t1中的display函数,实现Date类对象d1.
return 0;
}
2,友元类
友元类B中的所有函数都是A类的友元函数,可以访问A类中的所有成员。
在A类的定义体中用以下的语句声明B类为其友元类:friend B;
即 friend 类名;
关于友元类的2点说明:
1)友元的关系是单向的而不是双向的。
2)友元的关系不能传递,如果B类是A类的友元类,C类是B类的友元类,不等于C类是A类的友元类。
在实际工作中,除非的确必要,一般不把整个类声明为友元类,而只将确实有需要的成员函数声明为友元函数,这样更安全一些。
十一,模板类
有两个或多个类,其功能是相同的,仅仅是数据类型不同,可以定义一个类模板,它可以有一个或多个虚拟的类型参数。
template <class numtype>
class Compare
{
public:
Compare(numtype a, numtype b)
{x = a; y = b}
numtype max()
{return (x > y)? x: y}
numtype min()
{return (x < y)? x: y}
private:
numtype x, y;
};
1)声明类模板时要增加一行
template <class 类型参数名>
2)原有的类型名int 换成虚拟类型名numtype.
由于类模板包含类型参数,因此又称为参数化的类。
类模板是类的抽象,类是类模板的实例。
必须用实际名去取代虚拟的类型,具体做法如下:
Compare <int> cmp(4, 7);
类模板名 <实际类型名> 对象名[(参数表)];
说明:
1)类模板的类型餐宿可以有一个或多个,每个类型前面都必须加class.
2)和使用类一样,使用类模板时要注意其作用于,只能在其有效作用域内用它定义对象。
3)模板可以有多层次,一个类模板可以作为基类,派生除派生模板类。
面向对象的程序设计
继承和派生
一,继承和派生的概念
父类(old),子类(new)
子类-->父类 这叫类的继承
父类-->子类 这叫类的派生
单继承:一个派生类只有一个基类。
多重继承:一个派生类有两个或多个基类。
派生类是基类的具体化,而基类则是派生类的抽象。
二,派生类的声明方式
“:”
class 派生类名: [继承方式] 基类名
{
派生类新增加的成员
};
三,派生类的成员
1)从基类接收成员。
派生类把基类的全部的成员(不炮廓构造函数和析构函数)接收过来,也就是说是没有选择的。
有些类是专门作为基类设计的,在设计时充分考虑到派生类的要求。
2)调整从基类接收的成员。
接收基类成员是程序员不能选择的,但是可以通过指定继承方式来改变基类成员在派生类中的访问属性。
3)在声明派生类时增加的成员。
它体现了派生类对基类的功能的扩展。
在声明派生类时,一般还应当自己定义派生类的构造函数和析构函数,因为构造函数和析构函数时不能从基类继承的。
总结:派生类是基类定义的延续,派生类是抽象基类的具体实现。
四,派生类成员的访问属性
在讨论访问属性时,要考虑以下几种情况:
1)基类的成员函数访问基类成员。
2)派生类的成员函数访问派生类自己增加的成员。
3)基类的成员函数访问派生类的成员。
4)派生类的成员函数访问基类的成员。
5)在派生类外访问派生类的成员。
6)在派生类外访问基类的成员。
1,公有继承
2,私有继承
3,保护成员和保护继承
4,多级派生时的访问属性
五,派生类的构造函数和析构函数
1,简单的派生类的构造函数
2,有子对象的派生类的构造函数
3,多级派生时的构造函数
4,派生类构造函数的特殊形式
5,派生类的析构函数
六,多重继承
1,声明多重继承的方法
2,多重继承派生类的构造函数
3,多重继承引起的二义性问题
4,虚基类
1)虚基类的作用
2)虚基类的初始化
3)虚基类的简单应用举例
七,基类与派生类的转换
八,继承和组合
*九,继承在软件开发中的重要意义
多态性和虚函数
一,多态性的概念
polymorphism
一个事物有多种状态,
在面向对象方法中一般是这样描述多态性的:向不同的对象发送同一个信息,不同的对象在接收时会产生不同的行为(即方法)。
也就是说:每个对象可以用自己的方式去相应共同的消息。所谓消息,就是调用函数,不同的行为是指不同的实现,即执行不同的函数。
二,例子
三,利用虚函数实现动态多态性
1,虚函数的作用
出租车和公交车的例子,动态多态和静态多态。
虚函数:就是在基类声明函数是虚拟的,并不是实际存在的函数,然后在派生类中才正式定义此函数。
在程序运行期间,用指针指向某一派生类对象,这样就能调用指针指向的派生类对象中的函数,
而不会调用其他派生类中的函数。
2,静态关联和动态关联
静态关联:函数重载属于静态关联。在运行前进行关联的,也叫早期关联。
3,在什么情况下应当声明虚函数
1,两点注意:
1)
2)
2,考虑因素
1)
2)
3)
4)
4,虚析构函数
四,纯虚函数与抽象类
1,纯虚函数
2,抽象类
3,应用实例