当前位置: 首页 > news >正文

C++ 类与对象

类的构成

类与结构

类由结构体演变而来 两者之间虽然相似 但是也有很大的差异,其中结构是C语言中一种自定义的数据类型,它把相关联的数据元素组成一个单独的统一体

#include <iostream.h>
struct Date {
    int year;
    int month;
    int day;
};
int main()
{
    Date date1;
    date1.year=2003;
    date1.month=8;
    date1.day=25;
    cout<<date1.year<<"."<<date1.month<<"."
    <<date1.day<<endl;
    return 0;
}

类class与结构体struct的异同
1.类是C++对C中结构的扩展。
2.C语言中的struct是数据成员集合,而C++中的类,则是数据成员成员函数的集合。
3.struct是用户定义的数据类型,是一种构造数据类型。类和struct一样,也是一种用户定义的数据类型,是一种构造数据类型。
4.结构无法对数据进行保护和权限控制 ,所以结构中的数据是不安全的,C++中的类将数据和与之相关联的数据封装在一起,形成一个整体,具有良好的外部接口可以防止数据未经授权的访问,提供了模块间的独立性


类的构成

什么是类:

定义一个类,本质上是定义一个数据类型的蓝图,这实际上并没有定义任何数据,但它定义了类的名称意味着什么,也就是说,它定义了类的对象包括了什么,,以及可以在这个对象上执行哪些操作.


类的成员分两部分 :一部分对应数据的状态,称为数据成员,另一部分为作用于该数据状态的函数,称为成员函数

类定义是以关键字 class 开头,后跟类的名称.类的主体是包含在一对花括号中.类定义后必须跟着一个分号或一个声明列表


类声明:

class 类名;

我们可以仅仅声明类而暂时不定义它,这种声明被称为前向声明,在它声明之后定义之前该类是个不完全类型

不完全类型只能在非常有限的情况下使用:可以定义指向这种类型的指针或引用(可以被类指针指向),也可以作为一个已经声明(但没有定义)的函数的参数或返回类型,但不能进行类对象声明一旦一个类的名字出现后,它就被认为是声明过了(但尚未定义),因此类允许包含指向它自身类型的引用或指针


注意
1.每个类定义了唯一的类型,即使两个类的成员完全一样,只要类名不同,它们也是不同类型.
2.类内初始值要么放在等号右边,要么放在花括号内,记住不能使用圆括号


定义一个类的的一般格式如下:

class name //类名 类头 类声明
{//类体
public:
    公有数据成员;
    公有成员函数;
protected:
    保护数据成员;
    保护成员函数;                      //类体 类定义部分
private:
    私有数据成员;
    私有成员函数;
};//类定义结束标志

class为类关键字 类中又有三个关键字:privateprotectedpublic,称为访问权限关键字,而每个关键字下面又都可以有数据成员和成员函数


关于类中的三个关键字

public:
public部分称为类的公有部分,这部分的数据成员和成员函数称为类的公有成员公有成员可以由程序中的函数访问,它对外是完全开放的

protected:
protected部分称为类的保护部分,这部分的数据成员和成员函数称为类的保护成员。保护成员可以由本类的成员函数访问,也可以由本类的派生类的成员函数访问,而类外的任何访问都是非法的

private:
private部分称为类的私有部分,这一部分的数据成员和成员函数称为类的私有成员私有成员只能由本类的成员函数访问,而类外部的任何访问都是非法的


注意:

1.类的三个访问权限关键字分并非一定要全有,但至少要有其中的一个部分(一般一个类的数据成员应该声明为私有成员,成员函数声明为公有成员)

2.private处于类体中第一部分时,关键字private可以省略

3. 数据成员可以是任何数据类型,但不能用自动(auto),寄存器(register)外部(extern)关键字进行声明一个类的成员类型不能是该类自己(static静态成员除外)

4.不能在类声明中给数据成员赋值, C++规定,只有在类对象定义之后才能给数据成员赋初值

5.类声明中的private,protected,public 三个关键字可以按任意顺序出现任意次.但是,如果把所有的私有成员,保护成员和公有成员归类放在一起,程序将更加清晰

用一个类来描述日期,其形式如下:

class Date
{
public:
    void setDate(int y,int m,int d);
    void showDate();
private:
    int m_year;
    int m_month;
    int m_day;
};

成员函数的声明

在类内声明类的成员函数

在类的声明中只给出成员函数的原型,而成员函数体写在类的外部

类中声明的格式一般是:返回类型 成员函数 (参数列表);,例如:

class Car //类名的第一个字母习惯上最好采用大写 
{
public:
void setcolor(string col);	//如下声明一个给车选颜色的方法
}

声明的作用是让函数进行具体定义功能的时候能根据声明能确认函数所在哪个类中


在类内定义类的成员函数

类外定义的一般格式是:

void Car::setcolor(string col)
{
	color = col;
}

函数定义是给类中声明的函数(模板)的具体功能进行实现


例如, 以下是表示坐标点的类Coord的声明

class Coord {
public:
    void setCoord (int,int); // 设置坐标点
    int getx(); // 取x坐标点
    int gety(); // 取y坐标点
private:
    int x,y;
};
void Coord∷setCoord(int a,int b)
{ x=a; y=b;}
int Coord::getx()
{ return x;}
int Coord::gety()
{ return y;}

类的内联函数和外联函数

内联函数外联函数

1.内联函数是指那些定义在类体内的成员函数,即该函数的函数体放在类体内。而说明其函数在类体内,定义在类体外的成员函数叫外联函数,且外联函数的函数体在类的实现部分

2.内联函数在调用时不是像一般函数那样要转去执行被调用函数的函数体,执行完成后再转回调用函数中,而是直接在调用函数处用内联函数体的代码来替换,这样将会节省调用开销,提高运行速度

3.内联函数与带参数的宏定义进行一下比较,它们的代码效率是一样的,但内联函数要优于宏定义,因为内联函数遵循函数的类型和作用域规则,它与一般函数更相近,在一些编译器中,一旦关上内联扩展,将与一般函数一样进行调用,调试比较方便

4.外联函数变成内联函数的方法很简单,只要在函数头前面加上关键字inline就可以了

注: 外联函数就是普通函数(仅在类中声明 在类外部进行函数定义)


将成员函数以内联函数的形式进行说明

有两种格式将成员函数声明为类的内联函数:

隐式声明直接将函数声明在类内部

class Coord{
public:
    void setCoord(int a,int b)
    { x=a; y=b;}
    int getx()
    { return x;}
    int gety()
    { retrun y;}
private:
    int x,y;
};

显式声明
在类声明中只给出成员函数的原型(即只在类内进行内联函数的声明),而成员函数体写在类的外部.为了使它起内联函数的作用,在成员函数返回类型前加上关键字inline即可

显示声明一般格式

inline 返回类型 类名::成员函数名(参数表)
{
函数体
}

例如以下程序:

class Coord{
public:
    void setCoord(int,int);
    int getx();
    int gety();
private:
    int x,y;
};
inline void Coord::setCoord(int a,int b)
{ x=a; y=b;}
inline int Coord::getx(){ return x;}
inline int Coord::gety(){ return y; }

注意:

1.使用inline关键字修饰一个函数为内联函数时,必须使函数体和inline说明结合在一起,否则编译器将它作为普通函数处理
2.通常只有较短的成员函数才定义为内联函数,而较长的成员函数最好作为一般函数处理

总结:

内联函数的代码编译后会直接放在调用点的函数体内, 从而使得代码增大, 但是效率提高了(减少了跳转, 参数传递以及保存调用函数寄存器状态的过程).

对外联函数的调用会在调用点生成一个调用指令(在X86中是call), 函数本身不会被放在调用者的函数体内, 所以代码减小, 但效率较低.


对象的定义和使用

类与对象的关系

通常我们把具有共同属性和行为的事物所构成的集合叫做 类
在C++中,可以把相同数据结构相同操作集对象看成属于同一类

在C++中,类也是一种用户自定义数据类型,类的对象可以看成是该类类型的一个实例,定义一个对象和定义一个变量相似 (就像定义结构体时定义的结构体变量名实例一样),类与对象间的关系,可以用数据类型int和整型变量i之间的关系类比

C++把类的变量叫做类的对象对象也称为类的实例


对象的定义

对象的定义,也称对象的创建和类的实例化,在C++中可以用以下两种方法定义对象:

在声明类的同时,直接定义对象

class Coord {
public:
    void setCoord(int,int);
    int getx();
    int gety();
private:
    int x,y;
} op1,op2;//此处为在声明类的同时 被直接定义的对象(和定义结构体时 在结构体定义尾部进行结构体变量名定义的方法一致)

声明了类之后,在使用时再定义对象

class Coord
{
    //…
};

// …

main()
{
    Coord op1,op2;//此处为声明完类之后 在需要进行对象定义的地方进行的类对象的定义(和定义结构体变量名方法一致)
    // …
}

注意:

在声明类的同时定义的对象(定义在类声明的尾部)是一种全局对象 ,在它的生存期内任何函数都可以使用它
声明了一个类便声明了一种类型,它并不接收和存储具体的值,只作为生成具体对象的一种“样板”,只有定义了对象后,系统才为对象分配存储空间


对象中成员的访问

当定义了一个类的对象后,就可以访问对象的成员了.类的外部 可以通过 类的对象公有成员 进行访问,访问对象成员要使用 .点操作符

访问的一般形式是:

对象名.数据成员名
对象名.成员函数名(参数表);

使用类Coord的完整程序

#include<iostream.h>
class Coord {
publicvoid setCoord(int a,int b)//在类声明中被定义的 内联类成员函数
    { x=a; y=b; }
    int getx()
    { return x; }
    int gety()
    { return y; }
privateint x,y;
};
void main()
{
    Coord op1,op2;//建立 Coord类 的类对象op1,op2
    int i,j;
   
    op1.setCoord(5,6); // 调用 Coord类 的类对象 op1的setCoord()内联类成员函数,初始化对象op1
    op2.setCoord(7,8); // 调用 Coord类 的类对象 op2的setCoord()内联类成员函数,初始化对象op2
   
    i=op1.getx(); // 调用 Coord类 的类对象 op1的getx() 数据成员,取op1的x值
    j=op1.gety(); // 调用 Coord类 的类对象 op2的gety() 数据成员,取op2的y值
   
    cout<<"op1 i= "<<i<<" op1 j= "<<j<<endl;
   
    i=op2.getx(); // 调用op2的getx(),取op2的x值
    j=op2.gety(); // 调用op2的gety(),取op2的y值
   
    cout<<"op2 i= "<<i<<" op2 j= "<<j<<endl;
}

注意:
1.对象名.成员名实际上是一种缩写形式,它表达的意义是对象名.类名::成员名

void main( )
{
    Coord op1;
    op1.setCoord(5,6);
    //……
}

2.在类的内部所有成员之间都可以通过成员函数直接访问,但是类的外部不能访问对象的私有成员

3.在定义对象时,若定义的是指向对象的指针,则访问此对象的成员时,要用->操作符

void main( )
{
    Coord *op1;
    op1->setCoord(5,6);
    //……
}

总结:
类对象的成员的访问和结构体的成员的访问方式很相似都是以.->进行成员的访问


类成员的访问属性

类成员有三种访问属性:公有(public)、 私有(private) 和保护(protected)

1.说明为公有的成员不但可以被类中成员函数访问;还可在类的外部,通过类的对象进行访问
2.说明为私有的成员只能被类中成员函数访问,不能在类的外部,通过类的对象进行访问
3.说明为保护的成员除了类本身的成员函数可以访问外,该类的派生类的成员也可以访问,但不能在类的外部,通过类的对象进行访问

类的成员对类对象的可见性和对类的成员函数的可见性是不同的,类的成员函数可以访问类的所有成员 (也就是说在定义成员函数时 成员函数可访问任何访问属性的类数据成员以及类函数成员) ,而类的对象对类的成员的访问是受类成员的访问属性的制约的

注意:
如果在类中未对类成员的访问属性进行声明,则此类中的成员的访问属性默认为私有访问属性 (不可被外部访问及为其建立类对象)


类中成员的访问方式

类中成员互访直接使用成员名

类外访问使用对象名.成员名方式访问public属性的成员

class Sample{
public:
    int k;
    int geti(){return i;}
    int getj(){return j;}int getk(){return k;}
private:
    int i;
protected:
    int j;
};
//……
Sample a; //定义类Sample的对象a
a.i; //非法,类Sample的对象a不能访问类的私有成员i
a.j; //非法,类Sample的对象a不能访问类的保护成员j
a.k; //合法,类Sample的对象a能访问类的公有成员k
//……

一般来说,公有成员是类的对外接口,而私有成员和保护成员是类的内部数据和内部实现,是不希望外界访问的,将类的成员划分为不同的访问级别的个好处是信息隐蔽实现封装和数据保护,即将类的重要信息保护起来,以免被其它程序不恰当地进行修改,造成程序漏洞


对象赋值语句

两个同类型的变量之间可以相互赋值,同样,同类型的对象间也可以进行赋值,当一个对象赋值给另一个对象时,所有的数据成员都会逐位拷贝

#include<iostream.h>
class abc{
public:
    void init(int i,int j) { a=i; b=j; }
    void show(){ cout<<a<<" "<<b<<endl; }
private:
    int a,b;
};
main()
{
    abc o1,o2;
    o1.init(12,34);
    o2=o1; // 将对象o1数据成员的值赋给对象o2
    o1.show();
    o2.show();
    return 0;
}

注意:
1.在使用对象赋值语句进行对象赋值时,两个对象的类型必须相同,如果对象的类型不同,编译时将出错
2.两个对象之间的赋值,仅仅使这些对象中数据成员相同,而两个对象仍是分离的
3.上方代码的对象赋值是通过缺省(默认)的赋值运算符函数实现的当类中存在指针时,使用默认的赋值运算符进行对象赋值,可能会产生错误,详见副本构造器案例🚀


类的作用域

类的作用域就是指在类声明中的一对花括号所形成的作用域

一个类的所有成员都在该类的作用域内,一个类的任何成员可以访问该类的其他成员
一个类的成员函数可以不受限制地访问类的成员,而在类的外部,对该类的数据成员和成员函数的访问则要受到一定的限制,有时甚至是不允许的,这体现了类的封装功能

例如以下程序:

class myclass{
public:
    int i;
    void init(int);
    void show(){ cout<<“i=<<i<<endl;} // 可以访问类中的数据成员i
};
void myclass::init(int si){ i=si;} // 可以访问类中的数据成员i
int fun(){ return i; } // 非法,不能直接访问类中的i
void main()
{
    myclass ob;
    ob.init(5); // 给数据成员i赋初值5
    ob.show();
    i=8; // 非法,不能直接访问类中的i,可改写成ob.i=8
    ob.show();
}

案例 使用类和对象编写一个汽车行驶信息的程序

#include <iostream>
#include<windows.h>
#include <conio.h>//todo使用_kbhit()非阻塞键盘流检测函数和_getch()直接返回键盘字符函数需要包含此头文件

#define FULLGAS 85

using namespace std;

//todo1.如何声明一个 类

//类:由变量 和 函数组成  对象将使用变量存储信息 调用函数完成操作 (类中的变量成为属性 函数成为方法) 

class Car //类名的第一个字母习惯上最好采用大写 
{

public:
	//建立一个类 :存放汽车数据 车漆色 引擎类型 油量 和 几个轮子

	string color;
	string engine;
	float gas;
	unsigned int wheel;

	//todoC++允许在类里声明常量 但不允许对它进行赋值(绕开这一限制的方法 - 创建一个静态常量)

	//此时我们已经声明了车的简单属性 接下来我们应该为类定义一些方法 (也就是函数) 

	//创建函数的原型(声明) 再描述函数本身实现过程(先在类的声明中创建一个方法原型 再实现这个方法)


	//todo定义类中的普通成员函数(类中 声明 部分)
	//如下声明一个给车选颜色的方法
	void setcolor(string col);

	//如下声明一个给车选引擎的方法
	void setengine(string eng);

	//如下声明一个给车选轮毂的方法
	void setwheel(unsigned int whe);

	//如下声明一个给车加油的方法
	void fillgas(float Fgas);

	//再定义一个车辆运行的方法
	int running(void);

	//如下声明故障警报方法
	void warning(void);

	//至此我们已经有了加油和车辆运行方法的原型(声明) 如果我们想要使用它 我们还需对这个函数进行正式的定义

};//和结构体相似 类声明的末尾 必须有一个分号


//todo具体定义类中的普通成员函数(类外 定义 部分)
//成员函数可以访问类的所有成员(也就是说在定义成员函数时 成员函数可访问任何访问属性的类数据成员以及类函数成员) ,而类的对象对类的成员的访问是受类成员的访问属性的制约的
void Car::setcolor(string col)
{
	color = col;
}

void Car::setengine(string eng)
{
	engine = eng;
}

void Car::setwheel(unsigned int whe)
{
	wheel = whe;
}

void Car::fillgas(float Fgas)
{

	cout << endl;
	while(gas < Fgas)
	{
		gas += 3;
		Sleep(520);
		cout << "正在加油中!! 目前油量" << gas << "% \r";
		if (_kbhit())//非阻塞的响应键盘输入时间 (检测是否有按键按下,有按下返回非0值,一般是1没有按下返回0 需要包含 <conio.h>文头件)
		{
			if(_getche() == 'A')//从标准输入流(键盘)读取ESC键 (相较于getchar()接收按键后还需按下回车才能返回 _getch()不用按下回车就能返回 返回值为输入的Ascii码  需要包含 <conio.h>头文件)
			{
				cout << "暂停加油!! 当前油量 :" << gas << endl;//显示程序跳出提示并换行
				cout << endl;//再次换行
				break;//加油环节被手动停止 继续上路
			}
		}
	}
}

//todo_kbhit()函数和_getch()函数的配合使用
//由于_getch() 是阻塞函数(不按指定键不返回值 会导致程序 如果直接拿 if(_getch() == 27) 作为键盘输入暂停的判断条件 无论是否按下esc键 程序会在通过此函数时被阻塞 无法下行 )
//这时就需要在getchar()外层包裹_kbhit()函数来进行非阻塞型的(即未按下任何键时允许程序通过此判断条件)是否有按键输入判断就可以顺利实现按键跳出功能

//todo_getch()不显示输入字符 _getche()显示输入字符

int Car::running(void)
{
	cout << "目前正以时速120km向前移动";
	gas--;
	cout << "当前还剩余" << 100 * gas / FULLGAS << "%" << "的油量\r";
	return gas;
}

// " :: " 作用域操作符  作用是告诉编译器这个fillgas的方法存在于何处(属于哪一个类)
void Car::warning(void)
{
	cout << "\n警告!油量不足" << "还剩" << 100 * gas / FULLGAS << "%的燃油";
}


//todo案例 汽车加油程序
int main()
{
	
	char i;
	Car mycar;//建立Car类的对象mycar
	mycar.setcolor("WHITE");//类对象内的数据成员的具体定义
	mycar.setengine("V8");
	mycar.setwheel(4);

	mycar.gas = FULLGAS;

	while (mycar.running())
	{
		Sleep(50);
		if (mycar.running() < 10)
		{
			mycar.warning();
			cout << " \n油量过低 是否进行加油(Y/N) \n";
			cin >> i;
			if ('Y' == i || 'y' == i)
			{
				mycar.fillgas(FULLGAS);
			}
		}
	}

	return 0;
}

效果
在这里插入图片描述


来自:
关于循环程序跳出(_kbhit()、getch() 、getchar())🔍
while循环与中断语句break、continue、return以及goto的使用🔍
网站维护中🔍
C++中的类——类的定义和声明🔍

相关文章:

  • (转) Android中ViewStub组件使用
  • C++ 构造函数与析构函数
  • MFC应用程序中处理消息的顺序,创建窗口的过程关闭窗口的顺序(非模态窗口),打开模式对话框的函数调用顺序...
  • C++ this指针 与 类的继承
  • OpenGL_Qt学习笔记之_07(闪烁的星星)
  • C++ 继承机制中的构造器与析构器
  • C#数组
  • C++ 访问控制
  • ASA842配置内网DNS欺骗测试
  • C++ 覆盖方法与重载方法
  • Ubuntu 12.04 ping 响应慢
  • C++ 静态属性和静态方法
  • C++ this指针与静态属性的关系
  • .NET I/O 学习笔记:对文件和目录进行解压缩操作
  • C++ new和delete动态分配和释放内存
  • $translatePartialLoader加载失败及解决方式
  • 《Java编程思想》读书笔记-对象导论
  • 【162天】黑马程序员27天视频学习笔记【Day02-上】
  • co模块的前端实现
  • CSS 三角实现
  • C学习-枚举(九)
  • ECMAScript入门(七)--Module语法
  • ERLANG 网工修炼笔记 ---- UDP
  • flask接收请求并推入栈
  • Git 使用集
  • JS题目及答案整理
  • miaov-React 最佳入门
  • oschina
  • PermissionScope Swift4 兼容问题
  • Spring Cloud Feign的两种使用姿势
  • 从重复到重用
  • 前端存储 - localStorage
  • 设计模式走一遍---观察者模式
  • 首页查询功能的一次实现过程
  • 异常机制详解
  • 与 ConTeXt MkIV 官方文档的接驳
  • 在Unity中实现一个简单的消息管理器
  • 转载:[译] 内容加速黑科技趣谈
  • 阿里云服务器如何修改远程端口?
  • # 数论-逆元
  • #QT(串口助手-界面)
  • #ubuntu# #git# repository git config --global --add safe.directory
  • $redis-setphp_redis Set命令,php操作Redis Set函数介绍
  • (echarts)echarts使用时重新加载数据之前的数据存留在图上的问题
  • (动手学习深度学习)第13章 计算机视觉---图像增广与微调
  • (七)c52学习之旅-中断
  • (全注解开发)学习Spring-MVC的第三天
  • (五)Python 垃圾回收机制
  • (轉)JSON.stringify 语法实例讲解
  • .NET Core 成都线下面基会拉开序幕
  • .NET Core实战项目之CMS 第十二章 开发篇-Dapper封装CURD及仓储代码生成器实现
  • .NET 分布式技术比较
  • .NET/C# 编译期间能确定的相同字符串,在运行期间是相同的实例
  • .NetCore Flurl.Http 升级到4.0后 https 无法建立SSL连接
  • .NET分布式缓存Memcached从入门到实战