600 likes | 789 Views
C++ 介绍. 面向对象编程. 声明和定义 声明一个类,例如 class Cat { int itsAge; int itsWeight void setAge(int anAge); int getAge(); }. 面向对象编程. 定义一个对象 ,例如 Cat Tom; 注意:不要把声明和定义混淆,声明用来说明类是什么(抽象数据类型),定义就是为一个具体的对象分配内存。不要给类赋值,而应该给一个对象的数据成员赋值。. 私有和公有. 私有成员( private) 只能在类本身的方法内访问,公有成员( public) 则可以被该类的所有对象访问。
E N D
面向对象编程 • 声明和定义 声明一个类,例如 class Cat { int itsAge; int itsWeight void setAge(int anAge); int getAge(); }
面向对象编程 定义一个对象 ,例如 Cat Tom; 注意:不要把声明和定义混淆,声明用来说明类是什么(抽象数据类型),定义就是为一个具体的对象分配内存。不要给类赋值,而应该给一个对象的数据成员赋值。
私有和公有 • 私有成员(private)只能在类本身的方法内访问,公有成员(public)则可以被该类的所有对象访问。 • 类默认时所有成员都是私有的 Tom.itsAge=5 ;//wrong 作为一般的设计原则,应该保持类的成员为私有,提供公有的成员函数来访问和设置私有的数据成员。
构造函数和析构函数 • 构造函数 构造函数在每次生成对象的时候调用,构造函数创建并初始化类的对象,构造函数可以根据需要带有参数,但不会有返回值。构造函数是和类同名的类方法。 • 析构函数 析构函数在对象撤销后清除并释放内存。析构函数与类同名,前加~,析构函数没有参数,没有返回值。
构造函数和析构函数 • 构造函数没有参数或者没有构造函数时,创建对象 Cat Tom; • 构造函数带有一个整型的参数时,创建对象 Cat Tom(5);
构造函数和析构函数 注意:如果没有声明构造函数或析构函数,编译器会自动创建一个。只要给出了构造函数,编译器就不会再提供构造函数,因此如果创建了一个带参数的构造函数,除非在自己创建一个默认的构造函数,否则将没有默认构造函数。
成员函数const • 例如:int Function() const • 如果将一个类的方法声明为const,则必须保证该方法不会改变该类的任何一个成员变量的值。 例如在cat类中,setAge(int anAge)会改变成员变量的值,因此不能是const型,而getAge()就可以。 • 编程中应当尽可能多的使用const,这样做编译器可以帮你发现很多错误,比自己检查快得多,省力得多。
类的声明和方法定义 • 类的声明告诉编译器这个类是什么,具有什么样的数据,拥有什么样的函数。类的声明也称之为类的界面,它告知用户如何和类打交道。 • 函数定义告诉编译器该函数的功能是什么。函数的定义又称为类方法的实现。类的实现细节只是类的作者所关心的事情,类的客户无须操心。
类的声明和方法定义 • 注意:通常的方法是将类的界面保存在.h的头文件中,而类方法的实现保存在一个.cpp文件中。大多数情况下,应该将声明和实现分为两个文件,因为类的客户不关心类的实现细节,往往只要阅读头文件就可以得到需要的信息,而忽略具体实现文件。
内嵌(inline)实现 • C++支持类方法的内嵌。将某个函数的定义放到该类的声明中,可以使该函数自动成为内嵌函数。例如 int getAge() const {return itsAge;} void setAge(int anAge){ itsAge= anAge }; • 注意:内嵌函数的函数体紧跟在类方法的声明后,大括号后不加分号。
指针 • 指针是保存内存地址的变量。 声明一个指针: int *pAge=0; • 初始化为0的指针叫做空指针,所有指针在定义时都应当被初始化,在不知道该赋什么值的时候就赋为0,没有初始化的指针称之为失控指针(wild pointer)
栈和自由存储区(堆) 要使用指针应当先对内存的空间有所了解。内存分为5个区域: • 全局名字空间 • 自由存储区 • 寄存器 • 代码空间 • 栈
栈和自由存储区(堆) 在程序运行中,局部变量和函数的形参,函数返回地址位于栈中,代码位于代码区,全局变量位于全局变量区,寄存器用于内部管理,保存栈顶指针和指令指针。所有的剩余空间都被看作是自由存储区,也就是堆。
new&delete • 在C++中采用new分配自由存储空间的内存,new的返回值是内存的地址,必须赋给一个指针。例如 int * pPointer=new int • 当不在需要那块内存空间时,必须对指向该内存的指针使用delete,作用是释放内存,把它交还给自由存储区。 • 注意:指针本身是一个局部变量,当声明指针的函数返回时,指针的作用域结束了,就被丢弃了,但是用new分配的内存不会释放,这块内存就不能被其他程序使用了,造成内存泄漏。
内存泄漏 • 考虑一下代码,存在什么问题 0 int * pPointer=new int; 1 *pPointer=72 2 pPointer=new int; 3 *pPointer=73
内存泄漏 • 错误:最初的内存区域成为了不可用的空间,指向它的指针被重新赋值了,再也没法访问它了,也没法释放了。造成内存泄漏的是在没有删除一个指针之前就对其重新赋值。正确的方式是在第1行代码后添加delete pPointer
内存泄漏 • 注意:在程序中每使用一次new,都应该使用一次delete,保持指针对指向的内存区域的跟踪和保证在使用完后把它们归还给自由存储区是非常重要的。 • 最好在写完代码后,数一数new和delete的数量,delete的数量是否大于等于new的数量。
在自由存储区内创建对象 • 在C++中,可以像定义一个整型指针一样,定义一个指向任何对象的指针。 Cat *pCat=new Cat; Cat *pCat=new Cat(black)带参数的构造函数
访问数据成员 • 对于对象通过成员运算符“.”来访问它的数据成员和函数。对于指向对象的指针采用以下代码: (*pCat).GetColor(); • C++为对象指针提供了一个简单的运算符->,例如 pCat-> GetColor();
迷途指针 (失控指针或悬浮指针) • 当对一个指针调用delete时,释放了它指向的内存,但删除后没有将它设置成空指针,这样如果没有重新赋值就再次调用该指针时就会引起不可预料的后果!!! 通常删除指针之后就不要再使用它,良好的习惯是在删除了一个指针后,将该指针设置为空指针
迷途指针 (失控指针或悬浮指针) 0 int * pInt=new int; 1 *pInt=10; 2 cout<<*pInt<<endl; 3 delete pInt; 4 long *pLong=new long; 5 *pLong=9000; 6 cout<<*pLong<<endl; 7 *pInt=20; 8 cout<<*pInt<<endl; 9 cout<<*pLong<<endl; 10 delete pLong;
迷途指针 (失控指针或悬浮指针) • 输出结果: 10 100 20 65556
const指针 • 在指针的类型之前或者之后可以使用关键字const. const int * pOne; int * const pTwo; • 如果声明了一个指向const型对象的指针,那么该指针只能调用const成员函数。
const指针 const Rectangle * pConstRect =new Rectangle; Rectangle * const pConstPtr=new Rectangle; pConstRect->SetWidth(10); pConstPtr->SetWidth(10); • SetWidth()执行赋值操作
const指针 • SetWidth()是Rectangle的成员方法,执行的是对成员变量的赋值操作。第3句不合法,const型对象只能调用const成员函数。
指针算法 • 指针支持加减运算,在数组中常常会用到指针的数学运算。
引用 • 引用就是一个别名,当声明一个引用的时候,应该把它初始化为另一个对象名,也就是目标。从此时起,引用就成为了目标的替代名,所有对引用的操作实际都是对目标的操作。例如,存在一个someInt的整形变量。 int &rSomeRef=someInt; rSomeRef 就是someInt的别名
引用 • 注意: • 引用用来创建对象的别名; • 必须初始化所有的引用; • 不要向引用重新赋值。 • 对引用进行取址运算返回的地址是什么? • 任何对象都可以被引用,包括用户定义的对象。注意:引用的是对象,而不是类 Cat &rCatRef=Cat;//wrong Cat &rCatRef=Tom;//correct
值传递 • C++中默认的参数传递是值传递 • 当一个参数通过值传递传递给一个函数的时候,传递的是该参数的一个拷贝 • 函数的操作是针对该参数的拷贝的
引用 • 函数的两个限制:其一是使用值传递参数,其二是返回值只能有一个; • 打破这种能够限制有两种方法,一个是采用指针进行传递,还有就是采用引用传递。
返回多个值 • 函数只能返回一个值,如果需要返回多个值怎么办,解决方法就是使用引用传递给函数两个对象,函数把要返回的值赋给这两个对象。因为使用引用传递对象允许函数改变原对象,这样函数就可以返回两条信息,返回值可以用来报错。 • 同样传递对象,采用引用会比指针更容易阅读和保存。
int factor(int n, int* pSquared, int* pCubed) • { • int value=0; • if(n>20) • value=1; • else • { • *pSquared=n*n; • *pCubed=n*n*n; • value=0; • } • return value; • } int factor(int n, int* pSquared, int* pCubed); int main() { int num,squared,cubed; short error; cout<<"enter a num(0-20)"; cin>>num; error=factor(num,&squared, &cubed); if(!error) { cout<<"num"<<num<<endl; cout<<"squared"<<squared<<endl; cout<<"cubed"<<cubed<<endl; } else cout<<"error"<<endl; return 0; }
使用引用传递来提高效率 • 每次通过值传递的方式给函数传递一个对象时,都会建立一个该对象的拷贝。每次通过值从函数返回一个对象时,也会建立另一个拷贝。这些对象被复制到栈中,比较费时也比较费内存。 • 另外每次生成一个临时的拷贝时都要调用复制构造函数,当函数返回时,对象的临时拷贝被删除,此时调用对象的析构函数,如果返回的是通过值传递的对象,那么必须建立该对象的拷贝再删除。
引用与指针 • 引用和指针的差别,引用不能被重新赋值,如果需要使用一个变量指向不同的对象,就必须使用指针。
引用 • 尽可能使用引用传递参数 • 尽可能用引用返回 • 尽可能用const保护引用和指针 • 不要返回对不在作用域中的对象的引用,例如
example Cat &Function() { Cat Tom(5,9); return Tom; }//wrong
复制构造函数 • 复制构造函数在每次复制一个对象的时候调用,当按照值传递对象时,无论是传递到函数内还是作为函数的返回值,均会生成该对象的一个临时拷贝,如果这个对象是用户定义的,则调用这个类的复制构造函数。复制构造函数只有一个参数,即对同一类对象的引用,通常将这个引用设为常量,避免函数对该对象的修改。例如Cat(const Cat& Tom)
浅拷贝 • 默认复制构造函数只是把作为参数传递的对象中的每一个成员变量复制到新对象的成员变量中去,这叫做成员拷贝(浅拷贝),对于大多数成员变量来说是可行的,但是成员变量如果是指向自由存储区的指针时,这样做会存在风险。
Free storage New object Old object 5 It’s member variables It’s member variables shallow copy 浅拷贝
Free storage New object Old object 5 It’s member variables It’s member variables Shallow copy 浅拷贝
深拷贝 • 创建自己的复制构造函数并根据需要分配内存,在分配完内存后,原来的值就可以复制到新的内存中,这就叫做深层复制。
深拷贝 例如Cat类中包含成员变量int * itsAge和int* itsWeight; Cat::Cat(const Cat &rhs) { itsAge=new int; itsWeight=new int; *itsAge=*(rhs. itsAge); *itsWeight=*(rhs.itsWeight); }
Free storage 5 New object Old object 5 It’s member variables It’s member variables Deep copy 深拷贝