面向对象和面向过程的区别
面向过程:是以函数驱动,通过函数之间的相互调用次序来完成事情,重视完成该件事情的步骤;
面向对象:关注的不在是完成事情的步骤,关注的是如何通过对象之间的交互将事情完成。
封装
1.概念:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。
2.C++实现封装如何实现封装特性?
通过类将数据和操作数据的方法进行有机结合,通过访问限定符控制那些接口需要暴露给外部使用。
3.访问限定符:public(公有)、protected(保护)、private(私有)
4.访问限定符说明:
a.public修饰的类成员在类外可以直接被访问;
b. protected和private修饰的成员在类外不能直接被访问(此处protected和private是类似的) ;
c.访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止 ;
d. class的默认访问权限为private,struct为public(因为struct要兼容C) .
this指针
1.为什么有this指针呢?
//定义一个日期类Date
class Date
{
public :
void Display ()
{
cout <<_year<< "-" <<_month << "-"<< _day <<endl;
}
void SetDate(int year , int month , int day)
{
_year = year;
_month = month;
_day = day;
}
private :
int _year ;
int _month ;
int _day ;
};
int main()
{
Date d1, d2;
d1.SetDate(2021,1,1);
d2.SetDate(2021,1,11);
d1.Display();
d2.Display();
return 0;
}
//Date类中有SetDate与Display两个成员函数,函数体中没有关于不同对象的区分
//那当s1调用SetDate函数 时,该函数是如何知道应该设置s1对象,而不是设置s2对象呢?
所以,C++编译器给每个“非静态的成员函数“增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有成员变量的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。
2.this指针的特性
a.this指针的类型:T* const;
b.只能在类非静态成员函数中使用;
c.this指针本质上其实是一个成员函数的形参,是对象调用成员函数时,将对象地址作为实参传递给this 形参。所以对象中不存储this指针;
d.this指针是成员函数第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递(在vs编译器中),不需要用户传递;
3.this指针能否为空?
场景:
对象.成员函数();this指针一定不会为空
指针->成员函数();this指针指向对象,则不为空;指针指向空,那么就是空
4.this指针是存储在栈上
类中默认的成员函数
默认成员函数:用户在实现类时,如果没有显式定义该成员函数,则编译器会自动生成一份成员函数,这个成员函数就是默认成员函数;
注意:编译器可能没有严格按照语法来实现,即有些情况不会生成(vs编译器)
1.构造函数
概念:构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,保证每个数据成员都有 一个合适的初始值,并且在对象的生命周期内只调用一次。
注意:构造函数的主要任务并不是开空间创建对象,而是初始化对象。
特性:
a.函数名与类型相同;
b.没有返回值;
c.对象实例化时编译器自动调用对应的构造函数;
d.构造函数可以重载;
class Date
{
public:
// 1.无参构造函数
Date()
{}
// 2.带参构造函数
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
void TestDate()
{
Date d1; // 调用无参构造函数
Date d2(2015, 1, 1); // 调用带参的构造函数
// 注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明
// 以下代码的函数:声明了d3函数,该函数无参,返回一个日期类型的对象
Date d3();
}
e.构造函数具有初始化列表;
class Date
{
public:
Date (int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
{
_year = year;
_month = month;
_day = day;
}//初始化列表才是真正对成员非静态成员变量进行初始化;
//构造函数体中是对成员变量进行赋值
//当类中包含:引用、const以及类类型对象且该类具有带有参数构造函数时必须在构造函数初始化列表的位置进行初始化
private:
int _year;
int _month;
int _day;
};
//注意:1.初始化列表用户可以不用显式给出,但是不给出不代表没有初始化列表,编译器会自动补全,只不过在补全时给成员变量中放置的是随机值
// 2.成员的初始化次序并不是按照其在初始化列表中出现的先后次序来进行初始化,而是与成员变量在类中声明的先后次序有关
f.如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成;
g.无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。
// 默认构造函数
class Date
{
public:
Date()
{
_year = 2020;
_month = 1;
_day = 1;
}
Date(int year = 2020, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
void Test()
{
Date d;
}// 函数不能通过编译
调用场景:当用户在创建对象时,编译器会自动调用构造函数,对对象中的成员进行初始化
注意:与拷贝构造函数的调用场景进行区分
什么情况下会生成默认构造函数?
a.类和对象阶段:类A中如果包含有类B的对象,并且类A没有显式定义任何构造函数,而类B显式定义了无参或全缺省构造函数 ,编译器一定会给类A生成一份默认的无参构造函数
b.继承位置
c.虚拟继承下
d.类中带有虚函数
2.析构函数
概念:与构造函数功能相反,在对象销毁时,由编译器自动调用,完成对象中资源的清理工作。
注意:不负责回收对象的空间,只负责将对象空间中的资源清理干净
特性:
a.析构函数名是在类名前加上字符~;
b.没有参数返回值
c.一个类只有一个析构函数
d.在对象销毁时,由编译器自动调用,完成对象中资源的清理工作;
typedef int DataType;
class SeqList
{
public:
SeqList(int capacity = 10)
{
_pData = (DataType*)malloc(capacity * sizeof(DataType));
assert(_pData);
_size = 0;
_capacity = capacity;
}
~SeqList()
{
if (_pData)
{
free(_pData); // 释放堆上的空间
_pData = NULL; // 将指针置为空
_capacity = 0;
_size = 0;
}
}
private:
int* _pData;
size_t _size;
size_t _capacity;
};
e.用户没有显式实现时,编译器会生成一份默认的析构函数
class String
{
public:
String(const char* str = "jack")
{
_str = (char*)malloc(strlen(str) + 1);
strcpy(_str, str);
}
~String()
{
cout << "~String()" << endl;
free(_str);
}
private:
char* _str;
};
class Person
{
private:
String _name;
int _age;
};
int main()
{
Person p;
return 0;
}
注意:类中如果没有涉及到资源管理时,析构函数是否给出都可以;当类中涉及到资源管理时,用户必须要显式给出析构函数,在析构函数中清理对象的资源
调用场景:
当对象被销毁时,有编译器进行调用,完成对象中资源的清理工作
3.拷贝构造函数
概念:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。
特性
a.拷贝构造函数是构造函数的一个重载形式
b.拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用
class Date
{
public:
Date(int year = 2020, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
Date d2(d1);
return 0;
}
编译器生成的默认函数
如果用户没有显式定义拷贝构造函数时,则编译器会生成一份默认的拷贝构造函数;
class Date
{
public:
Date(int year = 2020, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
// 这里d2调用的默认拷贝构造完成拷贝,d2和d1的值也是一样的。
Date d2(d1);
return 0;
}
注意:对于一般的类例如Date类(_year,_month,_day)编译器并没有真正给其生成一份拷贝构造函数,但是发现编译器完成了拷贝的工作
如果类中没有涉及到资源管理时,用户是否显式提供拷贝构造都可以;当类中涉及到资源管理时,用户必须要显式给出拷贝构造函数,因为编译器默认的拷贝方式是将一个对象中的内容原封不动的拷贝到另一个对象中,最后就会导致多个对象共享同一份资源,当对象在被销毁时,会导致一份资源被释放多次而引起代码崩溃。
调用场景
当使用已经存在的对象创建新对象时,则调用拷贝构造函数
4.运算符重载
概念:运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似;
函数名:关键字operator后面接需要重载的运算符符号
函数原型:返回值类型 operator操作符(参数列表)
注意:
a.不能通过连接其他符号来创建新的操作符;
b.用于内置类型的操作符,其含义不能改变;
c.作为类成员的重载函数时,其形参看起来比操作数数目少1成员函数的操作符有一个默认的形参this,限定为第一个形参 ;
d. sizeof、.* 、:: 、?: 、. 注意以上5个运算符不能重载
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
// bool operator==(Date* this, const Date& d2)
// 这里需要注意的是,左操作数是this指向的调用函数的对象
bool operator==(const Date& d2)
{
return _year == d2._year
&& _month == d2._month
&& _day == d2._day;
}
private:
int _year;
int _month;
int _day;
};
void Test()
{
Date d1(2020, 1, 1);
Date d2(2020, 1, 2);
cout << (d1 == d2) << endl;
}
编译器生成的默认赋值运算符重载是浅拷贝,如果类中涉及到资源管理时,默认的赋值运算符重载会导致浅拷贝+内存泄漏,所以类中涉及到资源管理时,则用户必须要显式提供赋值运算符重载,一般使用深拷贝;
类和对象的区分
类 | 对象 |
---|---|
用来描述对象,说明对象有哪些属性,有哪些功能 | 是类具体产生的实体 |
一个自定义类型 | 真正存在的 |
新的作用域 | 是类类型创建的变量 |
不能存储数据 | 对象才是真正存储数据 |