C++类与对象详解
什么是类和对象
类和对象的概念
类是对象的抽象,对象是对客观事物的抽象。
用通俗的话来说:
类是类别的意思,是数据类型。
对象是类别下的具体事物。
也就是说:
类是数据类型,对象是变量。
比如:
水果是类。(水果不是真实存在的)
一个苹果是具体的对象。(一个苹果是真实存在的,它有大小,有颜色)
类的定义
定义一个类:盒子Box
class Box{
public:
double length; // 盒子的长度
double breadth; // 盒子的宽度
double height; // 盒子的高度
};
解释1:
class是一个专门用于定义类的关键字。
Box为类名
public下面会讲到,这里不必在意。
double length是类的数据成员,这里一共定义了三个数据成员。分别代表Box的长宽高。
将类的成员使用大括号{}包裹,记得最后使用分号结束。
解释2:
盒子是有自己的属性的,比如说盒子的长宽高,盒子的颜色和材质(上面的代码没有定义)。
我们称这些属性为类的数据成员。尽管有数据成员,类是没有存储空间的,只是一段代码存储在内存中。(因为类是抽象的,不是真实存在的)
只有实例化为对象,才会创建内存空间供数据成员存储。
也就是说,每一个盒子都会有自己的大小。
比如,int是没有存储空间的,但是int i=0;就会为变量i申请4字节的内存空间。
对象的创建
class Box{
public:
double length; // 盒子的长度
double breadth; // 盒子的宽度
double height; // 盒子的高度
};
Box box1; //使用类创建对象box1
box1.breadth = 20; //将对象box1的数据成员breadth的值修改为20
box1.height = 20;
box1.length = 20;
//得到box1的数据成员的值,再得到盒子box1的体积
cout << "盒子的体积为" << box1.breadth* box1.height*box1.length << endl;
解释:
Box为类名,是数据类型。
box1为变量名,是对象。它是通过类Box实例化的一个对象。
可以看到类就像是一个模板,对象是根据这个模板创建的。
box1.breadth表示访问对象中的数据成员breadth。
访问数据成员的语法:对象名.成员名。
思考:如果我们使用动态内存分配来创建对象,那么如果访问数据成员。
分析:我们一般是通过【对象名.成员名】。如果使用动态内存分配,那么我们就没有对象名了,只有指针名。
class Box{
public:
double length; // 盒子的长度
double breadth; // 盒子的宽度
double height; // 盒子的高度
};
Box *p = new Box; //使用动态内存分配的方式创建对象
p->breadth = 20; //使用指针修改对象的数据成员
p->height = 20;
p->length = 20;
//使用指针得到对象的数据成员的值,在得到盒子的体积
cout << "盒子的体积为" << p->breadth * p->height * p->length << endl
成员函数
成员函数的定义
class Box{
public:
double length; // 长度
double breadth; // 宽度
double height; // 高度
double getVolume(){ //成员函数
return length * breadth * height;
}
};
解释:
成员函数定义在类中,可以访问对象中的所有成员(包括数据成员和成员函数)。
(有些数据成员是不能被随便访问的,public的作用就是限制访问的,后面会讲到)
成员函数没有内存空间:
数据成员在每个对象中都有内存空间,但是成员函数在存在类中,在内存中只是一段代码,需要被对象使用时,则被调用。
成员函数的使用
class Box{
public:
double length; // 长度
double breadth; // 宽度
double height; // 高度
double getVolume(){
return length * breadth * height;
}
};
Box box1;
box1.breadth = 20;
box1.height = 20;
box1.length = 20;
//调用成员函数得到对象的体积
cout << "盒子的体积为" << box1.getVolume() << endl;
解释:
成员函数的调用方法与数据成员一致,通过【对象名.函数名(参数)】调用。
函数可访问此对象的所有成员。
成员函数声明
普通函数中,可以先声明再调用最后定义。
在类的成员函数中,同样可以进行函数声明。
class Box{
public:
double length; // 长度
double breadth; // 宽度
double height; // 高度
double getVolume(); //成员函数声明
};
double Box::getVolume(){ //在类的外面进行函数定义
return length * breadth * height;
}
解释:
使用【类名::】来修饰函数,表示此函数是Box的成员函数。(其他类可能也有同名的函数)
在调用时,是使用【对象名.函数名(参数)】调用的。
为什么要这么做?
因为函数具有封装性,我们只要关心函数的使用,不必关心函数是如何实现的。
类的访问修饰符
数据封装是面向对象编程的一个重要特点,类的访问修饰符防止直接访问类的内部成员。
类成员的访问限制是通过标记public、private、protected来指定的。
关键字public、private、protected称为访问修饰符。
比如:
class Base {
public:
// 这里是公有成员
protected:
// 这里是受保护成员
private:
// 这里是私有成员
};
1.公有成员-public
公有成员在程序中类的外部是可访问的。也就是说,谁都可以访问这些公有成员,没有任何限制。
class Box{
public:
double length; // 盒子的长度
double breadth; // 盒子的宽度
double height; // 盒子的高度
};
void main(){
Box box1;
box1.breadth = 20;
}
我们发现:可以直接通过【对象名.成员名】访问对象的成员。
相对的,我们可以看一下private和protected是都能通过这种方式直接访问对象的成员。
2.私有成员-private
私有成员在类的外部是不可访问的,甚至是不可查看的。
也就是说,在类的外部是不可以通过【对象名.成员名】访问对象的成员的。
类的私有成员只能在类的内部被直接调用。
class Box
{
private:
double length; // 盒子的长度
double breadth; // 盒子的宽度
double height; // 盒子的高度
};
void main(){
Box box1;
// box1.breadth = 20; //这行代码会报错!
}
解释:
在以上的代码中,由于breadth是私有成员,所以直接通过【对象名.数据成员】的形式访问会报错。
类的私有成员是默认的成员,如果没有写访问修饰符,则默认为private。
比如:
//下面的数据成员都是private
class Box{
double length;
double breadth;
double height;
};
通过函数间接访问private:
如果我们在类的外部需要访问私有成员,我们可以通过函数间接访问。
原理:1.private不能被直接访问.
2.public可以被直接访问。
3.类的内部可以随意调用任意成员。
得到结论:通过public成员间接调用private成员。
xclass Box
{
private:
double length; // 盒子的长度
double breadth; // 盒子的宽度
double height; // 盒子的高度
public:
void setBreadth(double B){
Breadth = B;
}
double getBreadth(){
return breadth;
}
};
void main(){
Box box1;
box1.setBreadth(12.5);
cout <<"盒子的宽度为: "<< box1.getBreadth() << endl;
}
解释:
length、breadth和height都是私有成员。
setBreadth(double B)
和getBreadth()
是公有成员。
我们通过这两个set和get函数修改和得到breadth的值。
通过这样的方式,我们就可以访问私有成员了。
3.受保护成员-protected
保护成员访问控制与私有成员十分相似。在类的外部不可被直接访问。
但有一点不同,保护成员在派生类(即子类)中是可访问(以后再说)。
构造函数和析构函数
1.构造函数
什么是构造函数?
类的构造函数是类的一种特殊的成员函数,它会在每次创建类的新对象时执行。
而且这个构造函数时自动执行的,无需手动调用。
构造函数有什么作用?
构造函数可用于初始化,例如:为某些成员变量设置初始值。
如何定义构造函数?
- 构造函数的名称与类的名称是完全相同。
- 不会返回任何类型,也不会返回 void。
class Box{
private:
double length; // 盒子的长度
double breadth; // 盒子的宽度
double height; // 盒子的高度
public:
Box(double L,double B,double H){ //构造函数
length = L; breadth = B; height = H;
}
double getBreadth(){
return breadth;
}
};
void main(){
Box box1 = Box(12, 12, 12); //定义的时候初始化
cout << "盒子的宽度为: " << box1.getBreadth() << endl;
}
2.默认构造函数
- 类中带有默认构造函数,若没有定义构造函数,则会采用默认构造函数创建对象。
- 默认构造函数是没有参数的,不会初始化数据成员。(就是空的函数,什么事也不做)。
- 若自定义了构造函数,则C++不会为我们创建默认构造函数。
我们可以进行构造函数重载,使得初始化多样化。
例1:
Box(){ //默认构造函数,由程序自动创建
}
//如果我们自定义了其他函数,则程序不会为我们自动创建构造函数
例2:
#include<iostream>
using namespace std;
class Box{
private:
double length; // 盒子的长度
double breadth; // 盒子的宽度
double height; // 盒子的高度
};
void main(){
Box box1=Box(); //此处不会报错,因为自动创建了构造函数Box(){}
}
例3:
#include<iostream>
using namespace std;
class Box{
private:
double length; // 盒子的长度
double breadth; // 盒子的宽度
double height; // 盒子的高度
public:
Box(double L, double B, double H){ //构造函数
length = L; breadth = B; height = H;
}
};
void main(){
Box box1; //报错!!!!不存在默认构造函数
}
/*
解释:
因为我们自己创建了一个构造函数,所以程序不会为我们创建默认的构造函数Box(){}
如果想要有默认的构造函数:可以使用函数重载,自己创建Box(){}
*/
例4:
#include<iostream>
using namespace std;
class Box
{
private:
double length; // 盒子的长度
double breadth; // 盒子的宽度
double height; // 盒子的高度
public:
Box(double L, double B, double H){ //构造函数
length = L; breadth = B; height = H;
}
Box(){ //构造函数
}
};
void main(){
Box box1 = Box(11,12,14);
Box box2; //相当于Box box2=Box();
}
3.析构函数
什么是析构函数?
类的析构函数是类的一种特殊的成员函数,它会在每次删除对象时执行。
如何定义析构函数?
1.析构函数的名称与类的名称是完全相同的,只是在前面加了个波浪号(~)作为前缀。
2.它不会返回任何值,也不能带有任何参数。
析构函数有什么作用?
析构函数有助于在跳出程序(比如关闭文件、释放内存等)前释放资源。
比如:在构造函数中使用了动态内存分配,那么系统并不会自动回收这些内存。所以需要析构函数。
如何调用析构函数?
无需手动调用,在程序运行完后自动调用析构函数,完成内存释放和文件关闭等功能。(所以只要关心如何定义即可)
class Arr{
public:
int *p;
Arr(){ //构造函数
p = new int[5];
}
~Arr(){ //析构函数
delete[] p;
}
};
/*
解释:
析构函数作用其中之一就是回收动态内存分配的空间。
new和delete要成对出现。
将delete写在析构函数中,就可以在销毁对象时自动回收内存了。
析构函数还有其他的作用:比如关闭文件。
*/
4.拷贝构造函数
什么是拷贝构造函数?
拷贝构造函数是一种特殊的构造函数,它在创建对象时,可以用对象给对象赋值。
拷贝构造函数有什么作用?
如果对象中有指针,那么使用对象给对象赋值后,它们的指针将会指向同一块内存。这样它们就会产生关联,而不是两个不同的对象。
如何定义拷贝构造函数?
类名 (const 类名 &obj) {
//省略构造函数的主体
}
例1:
/*
这个例子没有使用拷贝构造函数,但是运行结果是正常的。
例子中:使用了对象给对象赋值。
对象给对象赋值的过程中,会将所有成员变量的值复制一遍。
*/
#include<iostream>
using namespace std;
class Box
{
public:
double length; // 盒子的长度
double breadth; // 盒子的宽度
double height; // 盒子的高度
Box(double L, double B, double H){ //构造函数
length = L; breadth = B; height = H;
}
};
void main(){
Box box1 = Box(1, 1, 1);
Box box2 = box1;
cout << box2.length << endl; //输出1
}
/*
解释:
这个例子中:将box1的值复制一份给box2。
*/
例2:
#include<iostream>
using namespace std;
class Arr{
public:
int *p;
Arr(){ //构造函数
p = new int[5]; //动态内存分配5个int的空间
}
~Arr(){ //析构函数
delete[] p; //回收动态内存分配的空间
}
};
void main(){
Arr a;
a.p[1] = 1; //将a中的p[1]修改为1
Arr b = a;
b.p[1] = 2; //将b中的p[1]修改为2
cout << a.p[1] << endl; //输出2!!!
cout << b.p[1] << endl; //输出2!!!
getchar(); getchar(); getchar(); getchar(); getchar();
}
/*
现象:
我们将b中的p[1]修改为2,但是a中的p[1]也被修改为了2.
我们并没有修改a中的p[1],为什么也变成了2?
解释:
Arr b = a;将a的值赋值给了b。
那么,a的值就和b一样了,即他们的数据成员p指向的内存空间是同一个。
所以,修改b的值,a的值也会跟着变化。
*/
产生的问题:
我们想要创建的是两个不同的对象,但是上面这个例子:两个对象相互关联了。
如何解决:
拷贝构造函数
#include<iostream>
using namespace std;
class Arr{
public:
int *p;
Arr(){
p = new int[5];
}
~Arr(){
delete[] p;
}
Arr(const Arr&obj){ //拷贝构造函数
p = new int[5];
for (int i = 0; i < 5; i++)
p[i] = obj.p[i];
}
};
void main(){
Arr a;
a.p[1] = 1;
Arr b = a;
b.p[1] = 2;
cout << a.p[1] << endl; //输出1
cout << b.p[1] << endl; //输出2
getchar(); getchar(); getchar(); getchar(); getchar();
}
/*
解释:
有了拷贝构造函数,对象给对象赋值的时候就不会只复制地址了,而是复制地址所在的内存空间的值。
*/
友元
类的友元函数是定义在类外部,但有权访问类的所有私有(private)成员和保护(protected)成员。
尽管友元函数的原型有在类的定义中出现过,但是友元函数并不是成员函数。
友元可以是一个函数,该函数被称为友元函数;
友元也可以是一个类,该类被称为友元类,在这种情况下,整个类及其所有成员都是友元。
使用关键字friend来定义友元类和友元函数
友元函数举例
class Box
{
double width;
public:
friend void printWidth(Box box);
void setWidth(double wid){
width = wid;
}
};
void printWidth(Box box){
cout << box.width << endl;
}
void main(){
Box b;
b.setWidth(10);
printWidth(b); //外部函数访问private成员
}
友元类举例
class Box
{
double width;
public:
friend class friendBox;
void setWidth(double wid){
width = wid;
}
};
class friendBox{
public:
void testFunc(Box box){
cout << "box.width = " << box.width << endl;
}
};
void main(){
Box b;
b.setWidth(10);
friendBox f;
f.testFunc(b); //友元类中的所有函数都是友元函数
}
内联函数
背景:
函数在调用的时候会发生转移,会在栈中保存现场(比如原来的变量值),这样的操作会花费一定的内存空间和时间。
内联函数:
在调用时不发生控制转移,而是在编译时将函数体嵌入在每一个调用处。
适用于功能简单,规模较小又使用频繁的函数。
好处:加快程序运行效率
注意:
- 递归函数无法内联处理,内联函数不能有循环体,switch语句,不能进行异常接口声明。
- 即便将函数声明为内联函数,程序在编译时若发现不适合作为内敛函数,则按普通函数处理。
- 类中的所有成员函数都是内联函数。
inline int Max(int x, int y){ //将函数声明为内联函数
return (x > y)? x : y;
}
int main(){
cout << "Max (20,10): " << Max(20,10) << endl;
return 0;
}
this指针
每一个对象都有自己的地址,this指向自己。
可以通过this来访问本对象的成员。
class Box{
public:
double length; // 长度
double breadth; // 宽度
double height; // 高度
double setLength(double length){
this->length=length; //通过this指针访问自身的数据成员
}
};
int main(){
Box box1;
box1.setLength(10); //长度被修改为10
cout<<box1.length<<endl;
return 0;
}
解释:
细心发现,函数
double setLength(double length)
中的length和数据成员中的额length冲突了,函数参数length比数据成员length的作用域更小,所以默认访问的是函数参数length。
那么我们如何去访问数据成员length呢?
因为this指针永远指向自己(本对象),所以可以通过this->length来访问。
静态成员
静态成员在类的所有对象中是共享的。无论创建多少对象,静态成员始终只有一个。
我们使用static关键字将成员声明为静态。
静态成员分为静态数据成员和静态成员函数。
1.静态数据成员
静态数据成员需要在类中定义,在类外初始化。
访问方式:
类名::变量名(推荐)
对象名.变量名。
例1-静态成员是共享的:
class A{
public:
static int i; //静态数据成员定义
};
int A::i=0; //静态数据成员初始化
int main(){
A a1,a2;
a1.i=1;
cout<<a2.i<<endl; //输出结果为1
return 0;
}
解释:
我们将a1的i修改为1,可以发现a2的i也变成了1,所以静态成员是共享的。
例2-静态数据成员的作用举例:
class A{
public:
static int ObjectCount; //静态成员
A(){
ObjectCount++; //每创建一个对象,此变量加1(用于记录对象的数量)
}
~A(){
ObjectCount--; //每回收一个对象,此变量减1
}
};
int A::ObjectCount=0; //静态成员初始化
void main(){
A a1;
A a2;
cout<<A::ObjectCount<<endl;
}
解释:
静态数据成员是共享的。在构造函数中,将此变量+1;在析构函数中,将此变量-1。
那么,这个成员就是用来记录对象的数量。(当前存在多少个A的对象)
1.静态数据成员
静态成员函数只能访问静态成员(包括数据成员和静态成员函数)和类外部其他函数。
静态成员函数没有this指针。
解释:
因为静态成员函数只有一份,那么如果他要访问对象中的数据成员,该访问哪一个对象呢?所以,不能让静态成员函数访问对象中的非静态成员。
this指针是每个对象都有的,指向本对象。静态成员是类拥有的,而不是对象,所以类没有this指针,静态成员函数也就不能使用this指针了。
访问方式:
类名::静态函数名(推荐)
对象名.静态函数名
举例:
class A{
public:
static int ObjectCount; //静态成员
A(){
ObjectCount++;
}
~A(){
ObjectCount--; //每回收一个对象,此变量减1
}
static void print(){ //静态成员函数
cout<<ObjectCount<<endl; //访问静态成员
}
};
int A::ObjectCount=0; //静态成员初始化
void main(){
A a;
A::print();
}
C++类与对象详解的更多相关文章
- Java类和对象详解,以及相关知识点
了解类和对象前,简单提及面向对象程序设计.面向对象程序设计就是通过对象来进行程序设计,对象表示一个可以明确标识的实体.例如:一个人.一本书.一个学校或一台电脑等等.每个对象都有自己独特的标识.状态和行 ...
- java类和对象详解
类和对象 java 是面向对象的语言 即 万物皆对象c语言是面向过程语言 一.怎么去描述一个对象? (1)..静态的(短时间内不会改变的东西) 例如:外观,颜色,品牌 (2).动态的(动作) 可以干什 ...
- Java类和对象 详解(一)---写的很好通俗易懂---https://blog.csdn.net/wei_zhi/article/details/52745268
https://blog.csdn.net/wei_zhi/article/details/52745268
- 【转】UML类图与类的关系详解
UML类图与类的关系详解 2011-04-21 来源:网络 在画类图的时候,理清类和类之间的关系是重点.类的关系有泛化(Generalization).实现(Realization).依赖(D ...
- [转]c++类的构造函数详解
c++构造函数的知识在各种c++教材上已有介绍,不过初学者往往不太注意观察和总结其中各种构造函数的特点和用法,故在此我根据自己的c++编程经验总结了一下c++中各种构造函数的特点,并附上例子,希望对初 ...
- mvc-servlet---ServletConfig与ServletContext对象详解(转载)
ServletConfig与ServletContext对象详解 一.ServletConfig对象 在Servlet的配置文件中,可以使用一个或多个<init-param>标签为s ...
- JavaWeb学习----JSP内置对象详解
[声明] 欢迎转载,但请保留文章原始出处→_→ 生命壹号:http://www.cnblogs.com/smyhvae/ 文章来源:http://www.cnblogs.com/smyhvae/p/4 ...
- django中request对象详解(转载)
django中的request对象详解 Request 我们知道当URLconf文件匹配到用户输入的路径后,会调用对应的view函数,并将 HttpRequest对象 作为第一个参数传入该函数. ...
- UML类图与类的关系详解
摘自:http://www.uml.org.cn/oobject/201104212.asp UML类图与类的关系详解 2011-04-21 来源:网络 在画类图的时候,理清类和类之间的关系是重点.类 ...
- jQuery的deferred对象详解(一)
最近一段时间,都在研究jquery里面的$.Deffered对象,几天都搞不明白,其中源码的运行机制,网上查找了相关的资料,<jQuery的deferred对象详解>阮一峰老师的文章,里面 ...
随机推荐
- dotnet 6 引用 NAudio 的旧版本构建不通过
本文告诉大家在使用 NAudio 的旧版本导致构建不通过问题,解决方法是升级到 1.10 或以上版本 在更新 dotnet 6 项目时,使用了 NAudio 的旧版本,构建失败,提示 MC1000 如 ...
- WPF 解决 Skia 因为找不到字体而绘制不出中文字符
在 WPF 使用 Skia 做渲染工具,如果绘制的中文都是方块,也许是字体的问题.字体的问题是 Skia 没有找到字体,本文告诉大家如何修复 在 Skia 使用特定字体,可以使用 SkiaSharp ...
- vue全国省市选择vue组件
没用懂checkbox,干脆自己定义布尔值,方便数据页面响应. 可以再原始省市数据 下载address.js文件 1.初始化数据格式: 2页面样式: 3.对应输出的数据格式: 4.源码: <!D ...
- vue项目hbuilder打包-微信登录调取手机微信登录权限
这个笔记得做好. 1.vue页面的点击事件 import {login,loginy,wxLog,wxLogin,logout} from '../network/login' wxloginBtn( ...
- Intel Pentium III CPU(Coppermine, Tualatin) L2 Cache Latency, Hardware Prefetch特性调查
这几天,偶然的机会想到了困扰自己和其他网友多年的Intel Pentium III系列处理器缓存延迟(L2 Cache Latency),以及图拉丁核心版本是否支持硬件预取(Hardware Pref ...
- Linux-0.11操作系统源码调试
学习操作系统有比较好的两种方式,第一种是跟着别人写一个操作系统出来,<操作系统真相还原>.<Orange's:一个操作系统的实现>等书就是教学这个的:另一种方式就是调试操作系统 ...
- 十三、.net core(.NET 6)搭建ElasticSearch(ES)系列之dotnet操作ElasticSearch进行存取的方法
.net core操作ES进行读写数据操作 在Package包项目下,新增NEST包.注意,包版本需要和使用的ES的版本保持一致,可以避免因为不兼容所导致的一些问题.例如我本机使用的ES版本是7.13 ...
- radmin远程控制软件怎么样,有没有替代品
Radmin 是流行的.屡获殊荣的安全远程控制软件,它使您能够在远程计算机上实时工作,就像使用它自己的键盘和鼠标一样. 您可以从多个地方远程访问同一台计算机,是网络和管理类别中流行的远程桌面工具. R ...
- Teamviewer 再次涨价,太贵了,有没有平替软件?
今天打开 Teamviewer 网站,吓一跳,商业版基础款价格直接翻倍. 作为行业龙头,又是德国产品,Teamviewer 一直保持着高价格的特色.这两年 Teamviewer 的价格还逐年上涨,从每 ...
- C 语言编程 — 高级数据类型 — 枚举
目录 文章目录 目录 前文列表 声明枚举类型 定义枚举类型的变量 枚举类型变量的枚举值 枚举在 switch 语句中的使用 将整型转换为枚举类型 前文列表 <程序编译流程与 GCC 编译器> ...