不知不觉已经将近把类学完,最近几天在整理笔记的时候才发现学了很多东西。
很喜欢一句话:你可以很短时间内把知识点记熟,却不能很短时间内提升代码能力。
在类的学习中,这句话更是体现的淋漓尽致。类的知识点是琐碎的,而且很多,当然也要把知识点记熟,然而运用知识点写出具体程序才是最好记熟知识点的方式。
类中通常包含数据类,而数据类可以有多个,而操作类目前写的程序一个就够了。操作类和数据类分开写条理分明,便于理解。
在写多个类时,应该一个一个写,而且写完要在主函数内调试一下再写下一个类。这样可以保证错误尽少地出现,而且便于程序的调试。
当一个类包含另一个类时,应该注意其包含方式及初始化方式。
只有多写程序,多练才可以把面向对象编程学好。
一.类的定义
类要用关键词class,类似与结构体的struct。
//类的定义格式
class student(类名)
{
public://一般情况下public内为函数,是对外界的接口,外界调用函数来对数据进行操作;
......;
private://类中数据成员私有化,为了保证程序封装性,以及不被外界操作(外界通过函数对数据操作),数据通常定义为私有;
......;
protected:
......//内容通常为受保护的函数和数据成员;
};//类的最后一定要加分号结束!
//类的定义举例
class point
{
private:
int x,y;//不能直接int x=0,y=0;
public:
void setX(int a){x=a;};//分号结束
void setY(int b){y=b;};
void show(){cout<<x<<" "<<y<<endl;};
};//分号一定要有
二.数据成员初始化
需要注意,不可直接对数据成员初始化。后面的构造函数和析构函数会作进一步补充
例如
正确的初始化方式:
class point
{
private:
int x,int y;
public:
point(int a,int b):x(a),y(b){}//这样把形参a传给x,把形参b传给y;达到赋值的目的;
//再定义一个无参类型
point()
{x=0;
y=0;
}//同样达到对成员赋予初值的目的;///这时的函数名和类名相同;
三.类中函数的写法;
1.
class point
{
private:
int x,y;//不能直接int x=0,y=0;
public:
void setX(int a);//先在类中声明 格式为 返回值类型+函数名+(形参)+分号//然后在类外进行函数原型写入
void setY(int b);
void show();
};//分号一定要有
inline void point::setX(int a)//在类外,函数的写法为 返回值类型+类名+::+函数名+(形参)
{
x=a;
}
void point::setY(int b)//返回值类型+类名+::+函数名+(形参)
{
y=b;
}
void point::show()
{
cout<<x<<" "<<y<<endl;
}
2.内联函数
格式:inline+函数原型
作用:提高效率
但仅适用与小程序,不可递归调用;
例如:
//内联函数写法 inline+返回值类型+类名+::+函数名+(形参)
//一个小程序总结一下
#include<bits/stdc++.h>
using namespace std;
class rect//定义类
{
private:
int x,y;
public:
int set(int a,int b){x=a;y=b;};
void area();//函数声明
void perimeter(){cout<<2*(x+y)<<endl;};
};
inline void rect::area()//inline 函数 类外写函数原型
{
cout<<x*y<<endl;
}
int main()
{
rect rect1;
rect1.set(5,5);
rect1.area();
}
3.重载函数
四.对象生成
类是包含一类问题,对象是一个具体的东西;创建类后,需要定义具体对象;
对象的定义方法为
类名+对象名+分号
例如:rect rectssssss;
定义完对象后,就要考虑怎样操作对象中的函数成员,调用函数成员常见的方式有
1.
对象名+.+函数名+(形参)
例如 :rect1.set(5,5);
2.指针方法
int main()
{
rect *plus=new rect;//new运算符,开辟一个空间,把空间地址初始值赋予给指针变量plus;
plus->set(5,5);
plus->area();//指针变量+ -> +函数名();//通过这种方式调用函数
}
或者
(*plus).set(6,6);
(*plus).perimeter();
与上个指针方法调用函数效果相同;
格式为(*指针变量名)+.+函数名(形参)
五.构造函数和析构函数
1.构造函数
创建对象时,系统自动调用构造函数;
构造函数为对象分配空间,为数据成员赋予初值;
构造函数与类名相同;
若没定义构造函数,系统自动生成一个默认形式的构造函数 类名::类名(){}
例如:
#include<bits/stdc++.h>
using namespace std;
class date
{
int year,month,day;
public:
date();//无参构造函数
date(int a,int b,int c);//有参构造函数
void show()
{
cout<<year<<endl;
}
};
date::date()//类名+::+类名+()
{
year=0;
month=0;
day=0;
}
date::date(int a,int b,int c)//括号内为形参
{
year=a;
month=b;
day=c;//具体在花括号内赋值
}
int main()
{
date d1;
d1.show();//自动调用无参函数
date d2(5,5,5);
d2.show();
}
2.利用构造函数创建对象
第一种方式:
类名+对象名+(实参名)
例如上面程序的 date d2(5,5,5)
第二种方式 :
利用指针方式
date *p;
p=new date(5,5,5);//指针变量名=new 类名 实参表
//date *p=new date(5,5,5);与上述方法一样
p->show();//或者(*p).show();
3.初始化列表方式
~1.使用构造函数的函数体进行初始化
(在类外)date::date(int years,int months,int days)
{
year=years;
month=months;
day=days;
}
~2.使用构造函数的初始化列表进行初始化
(在类内)date(int years,int months,int days):year(years),month(months),day(days){此处内容看情况具体确定}
4.必须使用初始化列表进行数据成员初始化的情况
~1.数据成员为常量或引用数据
~2.数据对象为 没有无参构造函数的类的对象
例如:
#include<bits/stdc++.h>
using namespace std;
class A
{
int s;
public:
A(int x):s(x){}
void put(){cout<<s<<endl;};
};
class B
{
A a;//对象
int x,&y;//引用数据
const float pi;//常数据成员
public:
B(int c):a(c),x(c),y(x),pi(3.14){}
void show()
{
cout<<x<<endl<<y<<endl<<pi<<endl;
a.put();
}
};
int main()
{
B b(1);
b.show();
return 0;
}
5.利用初始化列表初始化顺序
public:
CMyClass(int x, int y):m_y(x),m_x(m_y)
{
cout<<"m_x="<<m_x<<endl;
cout<<"m_y="<<m_y<<endl;
}
如果 :
private:
int m_x,m_y;先初始化的是前者,而前者依靠m_y初始化,此时m_y还没有值,所以m_x是随机数,m_y后来是x的值;
6.构造函数的重载
#include<bits/stdc++.h>
using namespace std;
class rec
{
private:
int l,h,s;
public:
rec(int a,int b):l(a),h(b){cout<<l*h<<endl;}//有参构造函数
rec()//无参构造函数
{
l=0;
h=0;
cout<<l*h<<endl;
}
};
int main()
{
rec s;//调用无参构造函数
rec s1(6,6);//调用有参构造函数
return 0;
}
7.析构函数
对象生存期结束时,需要做清理工作,比如:释放成员(指针)所占有的存储空间
析构函数可以完成上述工作。
析构函数不能重载,无返回值,不能有参数。
在类外实现析构函数:
class rec
{
private:
int l,h,s;
public:
~rec();//类内声明析构函数///
rec(int a,int b):l(a),h(b){cout<<l*h<<endl;}
rec()
{
l=0;
h=0;
cout<<l*h<<endl;
}
};
rec::~rec()//类外实现析构函数
{
cout<<"aaaaa"<<endl;
}
int main()
{
rec s;
rec s1(6,6);
return 0;
}
8.this指针
~1.在类的非静态成员函数中返回类对象本身或对象的引用的时候,直接使用 return *this,返回本对象的地址时,return this。
例如:public:
Person(string n, int a) {
name = n; //这里的 name 等价于this->name
age = a; //这里的 age 等价于this->age
}
int get_age(void) const{ return age; }
Person& add_age(int i) {age += i; return *this; }//引用类型add_age(int i)
private:
string name;
int age;
Person Li("Li", 20);//定义对象Li
cout<<"Li age = "<< Li.get_age()<<endl;//输出20
cout<<"Li add age = "<< Li.add_age(1).get_age()<<endl; //输出20+1结果
//增加1岁的同时,可以对新的年龄直接输出;
~2.参数与成员变量名相同时,如this->x = x,不能写成x = x。
例如上面程序
Person(string name, int age) {
this->name = name; //this->name
this->age = age; //this->age
}
~3.避免对同一对象进行赋值操作,判断两个对象是否相同时,使用this指针
public:
void init(int x,int y) { X =x; Y = y;};
void assign(Location& pointer);//引用类型
int GetX(){ return X; }
int GetY(){ return Y; }
};
void Location::assign(Location& pointer)
{
if(&pointer!=this) //同一对象之间的赋值没有意义,所以要保证pointer不等于this,保证新的对象成员X,Y赋值为pointer对象里的X,Y;
{ X=pointer.X; Y=pointer.Y; }
}
主函数
Location x;
x.init(5,4);
Location y;
y.assign(x);
cout<<"x.X = "<< x.GetX()<<" x.Y = "<<x.GetY();
cout<<"y.X = "<< y.GetX()<<" y.Y = "<<y.GetY();
输出5,4;
六.复制构造函数
1.复制构造函数用一个已有同类对象创建新对象进行数据初始化
格式为
类名 :: 类名(const 类名 & 引用名 , …);
const保护实参对象只读
如果不定义一个这样的函数,系统默认生成一个
复制构造函数名与类名相同。
例如:
Box(int =10, int =10, int =10);
Box(const Box & b)//用对象b 初始化新对象
{height=2*b.height; width=2*b.width;length=2*b.length;}
int volume();
private:
int length, height, width;
主函数:
Box box1(1,2,3),box2(box1),box3=box2;//结果box2,box3都被box1初始化;
2.深复制与浅复制
浅复制对于数据类型复杂的成员只复制了地址而没有复制内容;
默认复制构造函数只是简单的浅复制;
例如
class Person
{
public:
Person(char* name1,int a,double s);
void display(){cout<<name<<"\t"<<age<<"\t"<<salary<<endl;}
~Person(){delete name;}
private:
char* name;
int age;
double salary;
};
Person::Person(char* name1,int a,double s)
{
name=new char[strlen(name1)+1];//name接受其首地址,申请空间
strcpy(name,name1);//把 数组name1复制给name;
age=a;
salary=s;
}
int main()
{
Person *P1=new Person("WangWei ",8,3880); //调用构造函数创建对象P1
P1->display();
Person P2(*P1); //调用复制构造函数,用P1的数据初始化对象P2
delete P1;
P2.display();
return 0;
}
但结果p2输出为乱码
因为删除p1地址后,原本俩对象公用的某些地址被删除;
同时复制地址和内容的为深复制///
格式为
定义:类名::类名(const类名 &对象名);
成员变量的处理:对复杂类型的成员变量,使用new操作符进行空间的申请,然后进行相关的复制操作
例如上面程序可改为
class Person
{
public:
Person(char* name1,int a,double s);
Person(const Person& po);
void display(){cout<<name<<"\t"<<age<<"\t"<<salary<<endl;}
~Person(){delete name;}
private:
char* name;
int age;
double salary;
};
Person::Person(char* name1,int a,double s)
{
name=new char[strlen(name1)+1];//name=new char [strlen(name1)+1];
strcpy(name,name1);
age=a;
salary=s;
}
Person::Person(const Person& P0) //复制构造函数的实现
{
name=new char[strlen(P0.name)+1];
strcpy(name,P0.name);
age=P0.age;
salary=P0.salary;
cout<<"ff"<<endl;
}
int main()
{
Person *P1=new Person("WangWei ",8,3880); //调用构造函数创建对象P1
P1->display();
Person P2(*P1); //调用复制构造函数,用P1的数据初始化对象P2
delete P1;
P2.display();
return 0;
}
3.常数据成员静态成员常对象常函数
长数据成员///
class Mclass
{ public :int k;
const int M; //说明常数据成员
Mclass() : M(5) { } //用初始式对常数据成员赋值,长数据成员只能用初始化列表方式初始化
void testFun()
{ //M++; //错误,不能在成员函数中修改常数据成员
k++; //可以修改一般数据成员
}
} ;
常对象//
类名 const 对象名[(参数表)];
或者
const 类名 对象名[(参数表)];
在定义常对象时必须进行初始化,而且不能被更新!
而且 不允许直接或间接更改常对象的数据成员。
规定常对象只能调用它的常成员函数、静态成员函数、构造函数(具有公有访问权限)。
//常成员函数
类型说明符 函数名(参数表) const;
const是函数类型的一个组成部分,因此在函数的实现部分也要带关键字const。
常成员函数不能更新对象的数据,也不能调用非const修饰的成员函数(静态成员函数、构造函数除外)
例如:
void constFun ( ) const
{ x ++ ; y ++ ; }//非法,常成员函数不能更新数据成员值
4.静态成员
static声明静态成员,静态成员与静态成员函数协同操作,为同类对象共享
在类外进行静态数据成员的声明
类型 类名::静态数据成员[=初始化值]; //必须进行声明
不能在成员初始化列表中进行初始化
如果未进行初始化,则编译器自动赋初值(默认值是0)初始化时不能使用访问权限
例如:
class counter
{ static int num ;
public :
void setnum ( int i ) { num = i ; }
void shownum() { cout << num << '\t' ; }
} ;
int counter :: num = 0 ;//在类外声明值 返回值类型+类名+::+静态成员名+=值
静态函数仅可以访问静态成员,
或是静态成员函数或是静态数据成员。
对静态成员的引用不需要用对象名
5.静态成员函数
static 返回类型 静态成员函数名(参数表);
调用格式:
类名::静态成员函数名(实参表)
对象. 静态成员函数名(实参表)
对象指针->静态成员函数名(实参表)
七.友元函数
如果在本类(类A)以外的其他地方定义了一个函数(函数B)
这个函数可以是不属于任何类的非成员函数,
也可以是其他类的成员函数,
在类体中用friend对其(函数B)进行声明,此函数就称为本类(类A)的友元函数。
友元函数(函数B)可以访问这个类(类A)中的私有成员
例如:用友元函数计算点距离
#include<iostream>
using namespace std ;
#include<math.h>
class Point
{ public:
Point(double xi, double yi) { X = xi ; Y = yi ;}
double GetX() { return X ; }
double GetY() { return Y ; }
friend double Distance ( Point & a, Point & b ) ;
private: double X, Y ;
} ;
double Distance(Point & a, Point & b )
{ double dx = a.X - b.X ;
double dy = a.Y - b.Y ;
return sqrt ( dx * dx + dy * dy ) ;
}
int main()
{ Point p1( 3.0, 5.0 ) , p2( 4.0, 6.0 ) ;
double d = Distance ( p1, p2 ) ;
cout << "This distance is " << d << endl ;
}
八.类的包含
类的包含在写软件时很好用,而且形式简单。
例如用类的包含写点的距离函数
class Point
{ public:
Point( int xi=0, int yi=0 ) { x = xi; y = yi; }
int GetX() { return x; }
int GetY() { return y; }
private: int x; int y;
};
//定义点类
class Distance
{ public:
Distance( Point xp1, Point xp2 );
double GetDis() { return dist; }
private:
Point p1, p2;
double dist;
};
//定义距离类
再如,用一个类初始化另一个类
class A{
public:
A( ){ cout<<"This is A."<<endl; } //缺省构造函数
A(int i) { cout<<"This is A, and it's value = "<<i<<endl; }
};
class B{
public:
B( ) { cout<<"This is B"<<endl; } //缺省构造函数
B(int i):a2(i) { cout<<"Hello B!"<<endl;}//此处把i值传递给a2//
//private:
A a1,a2; //声明两个A类对象a1和a2
};
int main( )
{
B b1, b2(9);
return 0;
}
再例如学生类可以与成绩类有包含关系
class Student
{
private:
string name;
string stu_no;
Score score1;
public:
Student(string name1, string stu_no1, float s1, float s2, float s3):score1(s1,s2,s3){//score中的数据成员用s1,s2,s3初始化!!!!!!!!这样就比较方便
name=name1;
stu_no=stu_no1;
}
又如atm机中记录类可以与时间类包含
九.对象数组
格式:
类名 数组名[下标表达式];
例如:
int main(){
Point p1[4];//与单个元素不同之处
p1[0]=Point(1,1);
}
例如统计多个学生成绩,在学生类中应该定义对象 student student1[100];
这样比较便于管理学生各种信息;
代码如下:
#include<bits/stdc++.h>
using namespace std;
class score
{
float math,English,Chinese;
float ave;
public:
score(float a,float b,float c)
{
math=a;
English=b;
Chinese=c;
ave=(a+b+c)/3;
}
score()
{
math=0;English=0;Chinese=0,ave=0;
}
void show();
};
void score::show()
{
cout<<ave<<endl;
}
int main()
{
score scoress[100];
float a,b,c;
for(int i=0;i<=2;i++)
{
cin>>a>>b>>c;
scoress[i]={a,b,c};//赋值形式;
scoress[i].show();
}
return 0;
}