c++ —类与对象入门(1)
类定义的语法形式:
class 类名称
{
public:
共有的(外部接口)
private:
私有成员
protect:
保护型成员
};
共有成员: 是类与外部的借口,任何外部函数都可以访问共有类型数据和函数。
私有成员: 仅允许本类中的函数访问,而类外的任何函数都不能访问。(如果私有成员在紧跟类名称的后面声明,则关键字private可以省略)
保护性成员: 与private类似,差别表现在继承与派生时对派生类的影响不同。(以后再细讲)
对象定义的语法形式:
要实现类中的功能,就要通过定义好的其对象来实现。
语法:
类名 对象名
例:
Clock myClock;
访问类内成员的方式:
对象名 . 类名
(仅访问public成员)
如何定义类的成员函数:
1、在类中要声明函数原型;
2、可以在类外给出函数体实现,并在函数名前使用类名加以限定;
3、也可以直接在类中给出函数体,形成内联成员函数;
3、允许声明重载函数和带默认参数值的函数
内联成员函数:
1、为提高运行效率,较简单的函数可以声明成内联形式;
2、内联函数体中不要有复杂结构(如循环语句和switch语句);
3、类中声明内联成员函数方式:将函数体放在类的声明中,并用inline关键字
类与对象实例:
在类外给出函数体实现:
#include <iostream>
using namespace std;
class Clock{
public:
void setTime(int newH,int newM,int newS);//函数声明,此函数用来设置一个时间
void showTime();//函数声明,此函数用来将设置的时间显示出来
private:
int hour,minute,second;
};
void Clock::setTime(int newH,int newM,int newS)//类外给出设置时间函数体的实现
{
hour=newH;
minute=newM;
second=newS;
}
void Clock::showTime()//类外给出显示时间函数体实现
{
cout<<hour<<":"<<minute<<":"<<second<<endl;
}
int main()
{
Clock myClock;
myClock.setTime(11,11,11);//设置的时间为11:11:11
myClock.showTime();//显示时间
return 0;
}
用内联函数在类中给出函数体实现:
#include <iostream>
using namespace std;
class Clock{
public:
inline void setTime(int newH,int newM,int newS)//内联函数inline
{
hour=newH;
minute=newM;
second=newS;
}
inline void showTime()//内联函数inline
{
cout<<hour<<":"<<minute<<":"<<second<<endl;
}
private:
int hour,minute,second;
};
int main()
{
Clock myClock;
myClock.setTime(11,11,11);
myClock.showTime();
return 0;
}
注:
1、其中inline也可以省略。
2、在类声明之后一定要在 “}” 后加上 “;” 。
构造函数:
构造函数作用: 类中的特殊函数,在对象创建时使用特定的值构造对象,将对象初始化为一个特定的初始状态。
构造函数形式:
1、函数名与类名相同;
2、不能定义返回值类型,也无return;
3、形式参数可有可无,有的话也可以带默认值;
4、可以为内联函数;
5、可以重载。
构造函数调用时机: 在创建对象时自动调用。
默认构造函数:
1、参数列表为空的构造函数;
2、全部参数都有默认值的构造函数。
例如以下两个构造函数如果在同一个类中出现,就会报错:
Clock ();
Clock (int newH=0;int newM=0;int newS=0);
因为在调用时二者均可以不给实参来调用,这时编译器就不知道该调用哪个了。
隐含生成的构造函数:
1、若未定义构造函数,编译器将自动生成一个默认构造函数;
2、其参数列表为空,不为数据成员设置初始值;
3、若类内定义了成员的初始值,则使用类内定义的初始值;
4、若类内未定义成员初始值,则以默认方式初始化;
5、基本类型的数据默认初始化的值时不确定的。
构造函数举例:
#include <iostream>
using namespace std;
class Clock{
public:
Clock(int newH,int newM,int newS);//构造函数无类型,前面什么也不加
void setTime(int newH,int newM,int newS);
void showTime();
private:
int hour,minute,second;
};
Clock::Clock(int newH,int newM,int newS): //此处冒号不要忘记
hour(newH),minute(newM),second(newS) //将成员初始化
{
}
void Clock::setTime(int newH,int newM,int newS)
{
hour=newH;
minute=newM;
second=newS;
}
void Clock::showTime()
{
cout<<hour<<":"<<minute<<":"<<second<<endl;
}
int main()
{
Clock myClock(0,0,0);
myClock.showTime(); //显示的时间为0:0:0
return 0;
}
默认构造函数举例:
#include <iostream>
using namespace std;
class Clock{
public:
Clock(int newH,int newM,int newS); //构造函数
Clock(); //默认构造函数
void setTime(int newH,int newM,int newS);
void showTime();
private:
int hour,minute,second;
};
Clock::Clock(int newH,int newM,int newS):
hour(newH),minute(newM),second(newS)
{} //构造函数实体
Clock::Clock():
hour(0),minute(0),second(0)
{} //默认构造函数实体
void Clock::setTime(int newH,int newM,int newS)
{
hour=newH;
minute=newM;
second=newS;
}
void Clock::showTime()
{
cout<<hour<<":"<<minute<<":"<<second<<endl;
}
int main()
{
Clock myClock1(11,11,11); //此处调用的是构造函数
Clock myClock2; //此处调用的是默认构造函数
myClock1.showTime();
myClock2.showTime();
return 0;
}
在写程序时,“int a;”这种定义后没有初始化的情况时常发生,就如例子中的“Clock myClock2;”一样,在定义完之后,才会用setTime函数来对这个对象赋值,就如定义完a后用a时才会“a=1”赋值。但若没有默认构造函数,“Clock myClock2;”的形式就会报错,所以为了避免这种情况发生,都会在类中定义一个默认构造函数来让程序更加通用。
委托构造函数:
构造函数中调用另一个构造函数。
#include <iostream>
using namespace std;
class Clock{
public:
Clock(int newH,int newM,int newS);
Clock();
void setTime(int newH,int newM,int newS);
void showTime();
private:
int hour,minute,second;
};
Clock::Clock(int newH,int newM,int newS):
hour(newH),minute(newM),second(newS)
{}
Clock::Clock():Clock(0,0,0) //委托构造函数
{}
void Clock::setTime(int newH,int newM,int newS)
{
hour=newH;
minute=newM;
second=newS;
}
void Clock::showTime()
{
cout<<hour<<":"<<minute<<":"<<second<<endl;
}
int main()
{
Clock myClock1(11,11,11);
Clock myClock2;
myClock1.showTime();
myClock2.showTime();
return 0;
}
复制构造函数
复制构造函数是一种特殊的构造函数,其形参为本类的对象的引用。作用是用一个已经存在的对象去初始化同类型的新对象。
class 类名{
public:
类名(形参) //构造函数
类名(const 类名 &对象名) //复制构造函数
… …
};
类名::类(const 类名 &对象名) //复制构造函数的实现
{函数体}
复制构造函数被调用的三种情况:
1、定义一个对象时,用本类的另一个对象作为初始值;
2、如果函数的形参是类的对象,调用函数时,将使用实参对象初始化形参对象,发生复制构造;
3、如果函数返回值类型是类的对象,函数执行完成返回主调函数时,将使用return语句中的对象初始化一个临时无名对象,传递给主调函数,此时发生复制构造。(可通过移动构造避免不必要的复制,详见后续章节)
例:
#include <iostream>
using namespace std;
class Point{
public:
Point(int xx,int yy):x(xx),y(yy){} //构造函数
Point():Point(0,0){} //委托构造函数
Point(const Point &p); //复制构造函数
~Point(){}; //析构函数
int getX(){return x;}
int getY(){return y;}
private:
int x,y;//私有成员
};
Point::Point(const Point &p)
{
x = p.x;
y = p.y;
cout << "调用复制构造函数" <<endl;
}
//形参作为Point类对象的函数
void fun1(Point p)
{
cout<< p.getX()<<endl;
}
//返回类的对象
Point fun2()
{
Point a(1,2);
return a;
}
int main()
{
Point a; //第一个对象a,调用默认构造函数(委托构造函数)
Point b(a); //此时调用复制构造函数:用a初始化b,第一次调用复制构造函数
cout << b.getX()<<endl;
fun1(b); //此时调用复制构造函数:类的对象在函数中为实参,第二次调用复制构造函数
b = fun2();//此时调用复制构造函数:函数返回值为类的对象,第三次调用复制构造函数
cout << b.getX()<<endl;
return 0;
}
隐含的复制构造函数:
若未定义一个复制构造函数,编译器会自动生成一个隐含的复制构造函数。其功能是:用初始值对象的每个数据成员,初始化将要建立的对象的对应数据成员。
析构函数
在对象的生存期结束的时刻,系统会自动调用析构函数。
如果程序中未声明析构函数,编译器将自动产生一个默认的析构函数,其函数体为空。
析构函数原型: ~类名();
析构函数没有参数表,也没有返回值类型及return语句。
注:上面复制函数的例子中就有析构函数。
组合类
组合类的构造函数: 不仅要负责对本类的基本数据类型成员初始化,也要对对象成员初始化。
格式:
类名::类名(对象成员所需的形参,本类成员形参):
对象1(参数),对象2(参数),… …
{
//函数体其他语句
}
构造组合类对象时的初始化次序:
1、首先对构造函数初始化列表中列出的成员(包括基本类型成员和对象成员)进行初始化,初始化次序是成员在类体中定义的次序:
成员构造函数调用的顺序:按对象成员的声明顺序,先声明者先构造。
初始化列表中未出现的成员对象调用默认构造函数(无形参的)初始化。
2、处理完初始化列表后,再执行构造函数的函数体
例:
#include <iostream>
#include <cmath>
using namespace std;
class Point{
public:
Point(int a=0,int b=0); //构造函数
Point(Point &p); //复制构造函数
int getX(){return x;}
int getY(){return y;}
private:
int x,y;
};
Point::Point(int a,int b) //构造函数实现
{
x=a;
y=b;
cout<<"using constructor of Point"<<endl;
}
Point::Point(Point &p) //复制构造函数实现
{
x=p.x;
y=p.y;
cout<<"using copy constructor of Point"<<endl;
}
//类的组合
class Line{
public:
Line(Point xp1,Point xp2);
Line(Line &l);
double getLen(){return len;}
private:
Point p1,p2;
double len;
};
//组合类的构造函数
Line::Line(Point xp1,Point xp2) : //形参实参结合执行两次Point的复制构造函数,从后往前执行
p1(xp1),p2(xp2) //用形参将组合类初始化,执行两次Point的复制构造函数,按组合类Line定义的顺序:p1在前p2在后,则先初始化p1,再初始化p2
{
cout<<"using constructor of Line"<<endl;
double x=(p1.getX()-p2.getX());
double y=(p1.getY()-p2.getY());
len=sqrt(x*x+y*y);
}
//组合类的复制函数
Line::Line(Line &l):
p1(l.p1),p2(l.p2) //此处再调用两次Point的复制构造函数
{
cout<<"using copy constructor of Line"<<endl;
len=l.len;
}
int main()
{
Point myp1(1,1),myp2(4,5); //执行两次Point的构造函数
Line line(myp1,myp2); //执行四次Point的复制构造函数,一次Line组合类的构造函数
Line line2(line); //利用复制构造函数建立一个新对象,执行两次Point的复制构造函数,一次Line组合类的复制构造函数
cout<<"The length of the line is:";
cout<<line.getLen()<<endl;
cout<<"The length of the line2 is:";
cout<<line2.getLen()<<endl;
return 0;
}
结果:
前向引用声明
1、类应先声明后引用;
2、如果需要在某个类的声明之前引用该类(两个类互相引用),则应进行前向引用声明;
3、前向引用声明只为程序引用一个标识符,具体声明在其他地方。
例:
class B; //前向引用声明
class A{
public:
void f(B b);
};
class B{
public:
void g(A a);
};
注:
1、在提供一个完整的类声明之前,不能声明该类的对象,也不能在内联成员函数中使用该类的对象;
2、只能使用被声明的符号,而不能涉及类的任何细节。
例:
class B;
class A{
B b; //错误:类B的声明尚不完善
};
class B{
A a;
};
结构体
c++中结构体是一种特殊形态的类。
与类的唯一区别:类的缺省访问权限是private;结构体的缺省访问权限是public。
什么时候用结构体而不用类:
1、定义主要用来保存数据,而没什么操作的类型;
2、人们习惯将结构体的数据成员设为公有,因此这时用结构体更方便。
结构体的定义:
struct 结构体名称{
公有成员
protected:
保护型成员
private:
私有成员
};
与c结构体区别: c++中结构体成员既可以是数据成员,也可以是函数成员;c中的结构体成员只能为数据成员。
结构体的初始化:
如果:
1、一个结构体的所有数据成员都为公有成员;
2、没有用户定义的构造函数;
3、没有基类和虚函数(以后介绍)。
这个结构体的变量可以用下面的语法形式进行初始化:
类型名 变量名={数据成员1初始值,数据成员2初始值… …};
例:
#include <iostream>
#include <string>
using namespace std;
struct Student{
string num;
string name;
char sex;
int age;
}; //结构体
int main()
{
Student stu = {"201924100724","John",'M',19};
cout<<stu.name<<endl;
cout<<stu.num<<endl;
cout<<stu.sex<<endl;
cout<<stu.age<<endl;
}
联合体
与类和结构体差别:存储空间的共用。
定义形式:
union 联合体名称{
公有成员
protected:
保护型成员
private:
私有成员
};
特点:
1、成员共用同一组内存单元;
2、任何两个成员不会同时有效。
例:
普通联合体;
union Mark{
char grade;
bool pass;
int percent;
//int为四个字节,占用的最大,则此联合体共占用四个字节
};
每使用一个数据成员,就会把其他的刷掉。
无名联合体
union{
int i;
float f;
};
在程序中可以这样使用:
i=20;
f=2.2;
则i的值会被f刷掉,只存储了f的值。
例:
#include <iostream>
#include <string>
using namespace std;
class ExamInfo{
private:
string name;
enum {GRADE,PASS,PERCENTAGE} mode; //定义一个枚举类型的mode
union{
char grade;
bool pass;
int percent;
};
public:
ExamInfo(string name,char grade):
name(name),mode(GRADE),grade(grade){}
ExamInfo(string name,bool pass):
name(name),mode(PASS),pass(pass){}
ExamInfo(string name,int percent):
name(name),mode(PERCENTAGE),percent(percent){}
void show();
};
void ExamInfo::show(){
cout<<name<<":";
switch(mode){
case GRADE: cout<<grade;break;
case PASS: cout<<(pass?"PASS":"FAIL");break;
case PERCENTAGE: cout<<percent;break;
}
cout<<endl;
}
int main()
{
ExamInfo course1("English",'B');
ExamInfo course2("Math",true);
ExamInfo course3("C++ Programming",85);
course1.show();
course2.show();
course3.show();
return 0;
}
结果:
枚举类
注:与枚举类型不同!
语法形式:
enum class 枚举类型名:底层类型名{枚举值列表};
(枚举类型是枚举类中底层类型名为 int 的情况。)
枚举类优势:
1、强作用域,其作用域限制在枚举类中。
例:enum class Type:char{General,Light,Medium};
使用Type的枚举值General时,要Type::General
(避免不同的枚举类中枚举值重名问题)
2、转换限制,枚举类对象不可以与整型隐式地相互转换;
3、可以指定底层类型。