链接:https://pan.baidu.com/s/1Am83Ut449WCbuTiodwJWgg?pwd=1688
提取码:1688
上午:类和对象
下午:类和对象高级应用
教学内容:
1、构造函数\析构函数\拷贝构造函数
构造函数:
每一个对象的创建都必须初始化,如果在没有写初始化函数(即构造函数),系统会默认写构造函数,但有些时候必须自己写构造函数。(比如,在定义初始化时候要申请空间或者牵涉指针)
构造函数的特点:
构造函数具有一些特殊的性质
• 构造函数的名字必须与类名相同
• 定义对象时被系统自动调用
• 可以有任意类型的参数,但不能有返回值
• 被定义为公有的,但其它时间无法被调用
例如:
//*****************************************
class Complex{
private:
double real;
double imag;
public:
Complex(double a=10, double b=10)//构造函数,与类同名,无返回值,同时可以缺省使用
{
real = a;
imag = b;
cout<<"In Constructor t!"<
}
...
};
//**************************
析构函数:
每一个对象在结束的时候,系统必须收回空间,收回空间由析构函数来完成,如果在类中没有析构函数,系统默认自己写析构函数。有些时候必须自己写析构函数(比如,在定义初始化时候要申请空间或者牵涉指针)
析构函数有以下一些特点:
• 析构函数与构造函数名字相同,但它前面必须加一个波浪号(~)
• 析构函数没有参数,也没有返回值,而且不能重载,因此在一个类中只能有一个析构函数
• 当撤消对象时,编译系统会自动地调用析构函数
例如:
//******************************************
class Complex{
private:
double real;
double imag;
public:
Complex(double r = 0.0,double i = 0.0) //构造函数,有缺省设置
{
cout<<"construction…"<
real = r;
imag = i;
}
~Complex() //析构函数,没有返回值,没有参数,前面加~,对象在接收时自动调用
{
cout<<"destruction…"<
}
...
}
//****************************
拷贝构造函数:
在程序中,如果对象赋值,在类中如果没有拷贝构造函数,编译器会自动编写拷贝构造函数,但是如果在有指针或有申请空间的类中,直接拷贝会出现,二个对象指向同一个空间的问题,可能会出现双释放空间,从而产生段错误。
自定义拷贝构造函数
classname(const classname &ob) //classname类名称
{
//拷贝构造函数的函数体
}
例如:
//********************************
class StringData
{
private: char *str;
publie:
StringData(char *s) //构造函数
{
str = new char[strlen(s)+1];
strcpy(str,s);
}
~StringData() //析构函数
{
delete str;
}
//…
};
int main()
{
StringData x(“abc”); //重新创建一个对象
StringData y(x); //StringData y = x;
//拷贝一个对象,所有东西都拷贝了,此时对象y的str指针指向x对象new的空间,当x(y)
//结束有一个结束后,new的空间被析构函数delete了,当最后y(x)也结束时,又会调用一
//次析构函数,此时重复delete,而空间不存在,产生段错误。
}
//*******************************
为了解决上面的重复delete问题,必须自己编写拷贝构造函数,即
StringData(const StringData &ob)
{
str = new char[strlen(s)+1];
strcpy(str,ob.str);
}
//*****************************************************
2、对象数组
对象数组是指每一个数组元素都是对象的数组,也就是说,若一个类有若干个对象,我们把这一系列的对象用一个数组来存放。
例如:
//*****************************************
class Exam
{
private:
int x;
int y;
public:
Exam(int a ,int b = 9 ){ x = a; y = b;}
int getX(){ return x; }
int getY(){return y; }
};
int main()
{
Exam ob[2] = {4,8}; //第一种方式:初始化表
Exam ob1[2] = {Exam(11),Exam(21,22)}; //第二种方式:初始化表
for(int k = 0; k < 2; k++){
cout<
}
cout<
for(int j = 0; j < 2; j++){
cout<
}
}
//**************************************
第一方式:适用于只要输入一个参数的时候,如果要输入2个参数构造,就必须使用第二种方式。对象数组使用方法和C语言数组一样。
3、对象指针
在C语言中能够直接访问结构,或通过指向该结构的指针来访问结构。类似地,在C++语句中可以直接访问对象,也可以通过指向该对象的指针访问对象。对象指针是C++的重要特性之一。
例如:
//***************************************
class Exe{
int a;
public:
void setA(int x){ a=x;}
};
int main()
{
Exe ob,*p;
ob.setA(2);
ob.showA();
p=&ob;
p->showA(); //和结构体指针使用方法一样
}
//******************************************
4、this指针
它是成员函数所属对象的指针,它指向类对象的地址。成员函数通过这个指针可以知道自己属于哪一个对象。this指针是一种隐含指针,它隐含于每个类的成员函数。
例如:
//******************************************
class Exam{
private:
int x;
public:
void load(int va1) //实际上是void load(Exam *this,int val)
{
this->x = va1; //与 x=val等价,每一个对象都有一个this指针,是隐藏的
cout<<"load--- this value: "<
}
int getX()
{
cout<<"getx--- this value: "<
return this->x; //与return x;等价
}
};
int mian()
{
Exam ob1;
ob1.load(100); //实际调用函数是ob1.load(&ob1,100);
}
//**************************************************
例如:
//*************************************************
//******************************************************
可以利用this将一些操作序列连接成一个单独的表达式:
a.set(13,23).show();
等效:a.set(13,23);a.show();
//******************************************************
class ABC{
public:
int x;
public:
ABC(int x = 9){...} //构造函数
ABC(const ABC &tt){...} //拷贝构造函数
~ABC(){...} //析构函数
ABC& set(int x) //形参与数据成员同名,一定要返回引用对象
{
this->x = x;
return *this;
}
void show(){...} //打印函数
};
int main()
{
ABC p;
p.set(5).show(); //执行完p.set(5)返回的是对象p,接下去就相当于p.show()
return 0;
}
//*****************************************************
5、在函数中对以对象作为形参的三种方式:
fun1(ABC p) {……} //值方式
fun2(ABC *p) {……} //指针方式
fun3(ABC &p){……} //引用方式
值方式传递:是利用拷贝构造函数,重新建立一个新的对象,不会影响实参对象。
指针方式传递:传入对象的地址,操作会改变对象。
引用方式传递:直接对对象操作,操作会改变对象。
6、静态成员
在C++中可以用关键字static声明为静态的,这些成员称为静态成员,成员可以是:数据成员和函数成员。
静态成员仅仅属于类所有,同类的对象共享静态成员。
例如:(静态数据成员)
//*************************************
class Student{
public:
static int count; //声名为静态数据成员
int StudentNo;
public:
Student(){
count++;
StudentNo = count;
}
void print(){...}
};
int Student::count; //定义静态数据成员并初始化,全局变量不赋值默认为0
int main()
{
Student s1; //调用一次构造函数,count=1;
Student s2;//调用第二次构造函数,cout=2,此时对象s1和s2的count=2
Student s3;//调用第三次构造函数,cout=3,此时对象s1、s2和s3的count=3
...
} //静态数据成员是属于类的,不独属于一个对象
//****************************************************
静态成员函数是一种特殊的成员函数,它不属于某一个特定的对象。一般而言,静态成员函数访问的基本上是静态数据成员或全局变量。
例如:(静态函数成员)
//**********************************************
enum TTT{aaa = 3, bbb}; //枚举,bbb=4
class SmallCat{
private:
double weight;
static double total_weight; //静态数据成员
static double total_number; //静态数据成员
public:
enum TTT{aaa, bbb};
SmallCat(double w){ //构造函数
weight = w;
total_weight += w;
total_number++;
}
void display(){
cout<<"小猫的重量是:"<
}
static void totalDisp(); //声明静态成员函数,也可以在类中定义
// {
// cout<
// cout<
// }
};
void SmallCat::totalDisp() //外部定义静态成员函数
{
// cout<
cout<
}
double SmallCat::total_weight = 0;
double SmallCat::total_number = 0;
int main()
{
TTT t;
t = bbb;
cout<<"t = "<
SmallCat::TTT tt;
tt = SmallCat::bbb;
cout<<"tt = "<
// SmallCat::total_weight=0;
SmallCat::totalDisp(); //通过类调用静态成员函数
SmallCat w1(1.8),w2(1.6),w3(1.5);
w1.display();
w2.display();
w3.display();
SmallCat::totalDisp(); //通过类调用静态成员函数
cout<
w1.totalDisp();
return 0;
}//因为静态函数是属于类,如果在函数中出现对象数据,在没有对象时候会出现段错误
//*******************************************************
在一般的成员函数中都隐含有一个this指针,用来指向对象自身,而在静态成员函数中没有this指针,因为它不与特定的对象相联系,调用时使用:
类名::静态成员函数名()
如:SmallCat::totalDisp(),当然使用:
对象.静态成员函数名()
也是正确的。如:w1.totalDisp()
7、类对象作为成员
使用对象成员时需要注意的问题是构造函数的定义方式,即类内部对象的初始化问题。
构造函数定义方式:
外部函数格式 X::X(参数表0):成员名1(参数表1),…,成员名n(参数n表)
内部函数格式 X(参数表0):成员名1(参数表1),…,成员名n(参数n表)
例如:
//*************************************************
class MyString{
private:
char *str;
public:
MyString(char *s = "aa"){
str = new char[strlen(s)+1];
strcpy(str,s);
cout<<"构造MyString "<
}
MyString(const MyString & t){
str = new char[strlen(t.str)+1];
strcpy(str,t.str);
cout<<"拷贝构造MyString\n";
}
~MyString(){
cout<<"析构MyString\n";
delete str;
}
void print(){
cout<
}
};
class Girl{
private:
MyString name;
MyString address;
int age;
public:
Girl(char *st, char *ad, int ag):name(st),address(ad)
{
age = ag;
cout<<"构造girl\n";
}
// Girl(char *st, char *ad, int ag):name(st),address(ad),age(ag) //与上面等效
// {
// cout<<"构造girl\n";
// }
Girl(const Girl & t):name(t.name)
{
age = t.age;
cout<<"拷贝构造 girl"<
}
void print(){
name.print();
cout<<"age:"<
}
~Girl(){
cout<<"析构girl\n";
}
};
int main()
{
MyString sss("hello");
Girl obj("chao hao","hhsdfsdf",8);
//obj.print();
Girl temp(obj);
return 0;
}
//***************************************************
内联性和外联函数
类的成员函数可以分为内联函数和外联函数。
1)内联函数是指那些定义在类体内的成员函数,即该函数的函数体放在类体内。
2)外联函数是声明在类体内,定义在类体外的成员函数。外联函数的函数体在类的实现部分。(外联函数和C函数一样的调用,类外定义函数必须加上一个声明语化,类名::)
内联函数在调用时不是像一般的函数那样要转去执行被调用函数的函数体,执行完成后再转回调用函数中,执行其后语句,而是在调用函数处用内联函数体的代码来替换,这样将会节省调用开销,提高运行速度。
内联函数与带参数的宏定义的代码效率是一样的,但是内联函数要优于宏定义,因为内联函数遵循函数的类型和作用域规则,它与一般函数更相近,在一些编译器中,一旦关上内联扩展,将与一般函数一样进行调用,调试比较方便。
外联函数变成内联函数的方法很简单,只要在函数头前面加上关键字inline就可以了。
#include
using namespace std;
class A
{
public:
A(int x, int y) //内联函数
{
X=x;Y=y;
}
int a() //内联函数
{
return X;
}
int b() //内联函数
{
return Y;
}
int c();
int d();
private:
int X,Y;
};
//inline定义内联函数
inline int A::c()
{
return a()+b();
}
inline int A::d()
{
return c();
}
int main()
{
A m(3,5);
int I=m.d();
cout<<"d()return:"<
}
输出结果:d()return:8
说明:类A中,直接定义了3个内联函数,又使用inline定义了2个内联函数。内联函数一定要在调用之前进行定义,并且内联函数无法递归调用。区别就在于,运行时侯的效率与定义的方法不同。
调用函数
//********************************
op.setPoint(1,2)实际上是
op.point::setPoint(1,2)的缩写形式
//op是对象,point是类名
//*******************************