类是c++中特有的,c是没有类的概念的。
3.1 类
这个类还真没啥好写的,c++的类脱胎已c的结构体,只不过结构体中不能定义函数(c++可以在结构体中定义函数了),c语言用结构体模拟类只能用函数指针。
在C++中, 用 “类” 来描述 “对象”, 所谓的"对象"是指现实世界中的一切事物。那么类就可以看做是对相似事物的抽象, 找到这些不同事物间的共同点, 如自行车和摩托车, 首先他们都属于"对象", 并且具有一定得相同点, 和一些不同点, 相同点如他们都有质量、都有两个轮子, 都是属于交通工具等。“都有质量”、"两个轮子"属于这个对象的属性, 而"都能够当做交通工具"属于该对象具有的行为, 也称方法。
类是属于用户自定义的数据类型, 并且该类型的数据具有一定的行为能力, 也就是类中说描述的方法。通常来说, 一个类的定义包含两部分的内容, 一是该类的属性, 另一部分是它所拥有的方法。以 “人类” 这个类来说, 每个人都有自己的姓名、年龄、出生日期、体重等, 为 人类 的属性部分, 此外, 人能够吃饭、睡觉、行走、说话等属于人类所具有的行为。
上面举例中所描述的 “人” 类仅仅是具有人这种对象的最基础的一些属性和行为, 可以称之为人的"基类"。 再说说一些具有一些职业的人, 例如学生, 一个学生还具有"基类"中所没有的属性, 如学校、班级、学号; 也可以具有基类所不具有的行为, 如每天需要去上课, 需要考试等。
学生类可以看做是基类的一个扩展, 因为他具有基类的所有属性和行为, 并且在此基础上增加了一些基类所没有的属性和行为, 像"学生"这样的类称为"人类"这个基类的"派生类"或者"子类"。在学生的基础上海可以进一步的扩展出其他更高级的类, 如"研究生"类。
参考这篇博客javascript:void(0)
3.2 类的访问权限
结构体中默认的访问权限是public,c++对这个访问权限做了升级,一共有3个:
public: 这个是公有的,其他类可以访问到有public的变量或方法
protected: 这个是保护的,其他类不能访问到有protected修饰的变量或方法
private: 这个也是不能访问
protected和private不同点就是在继承的时候,protected能继承给子类。
类的默认访问权限(不写权限的时候)是private
有一些变量是private的,需要访问的话,要利用间接访问 get和set方法。
3.3 面向过程和面向对象
不知道哪位大神总结的这么牛逼,好好理解理解。
3.4 面向对象求园的面积和周长
class Circle
{
public:
void setR(float r) {
m_r = r;
}
float perimeter() {
return 2 * m_r * 3.14;
}
float area() {
return m_r * m_r * 3.14;
}
private:
float m_r; //私有变量可以在前面加m_
//float m_area = m_r * m_r * 3.14; //不行
};
int main(int argc, char **argv)
{
cout << "hello c++ " << my_spaceA::my_spaceB::haha << endl;
Circle c;
c.setR(10);
printf("周长 %f\n", c.perimeter() );
return 0;
}
是不是很简单,类中的变量是可以赋初值的,但是不能用变量的表达式的值,因为类加载的过程中,申请的变量是随机的,所以得出的值也会是随机的,不过可以赋值常数,这个不会被改变。
3.5 构造函数和析构函数
构造函数:
类的构造函数是类的一种特殊的成员函数,它会在每次创建类的新对象时执行。
构造函数的名称与类的名称是完全相同的,并且不会返回任何类型,也不会返回 void。构造函数可用于为某些成员变量设置初始值。
class Circle
{
public:
Circle() { //无参构造函数
m_r = 10;
}
Circle(float r){ //有参构造函数
m_r = r;
}
void setR(float r) {
m_r = r;
}
float perimeter() {
return 2 * m_r * 3.14;
}
float area() {
return m_r * m_r * 3.14;
}
private:
float m_r; //私有变量可以在前面加m_
//float m_area = m_r * m_r * 3.14;
};
int main(int argc, char **argv)
{
cout << "hello c++ " << my_spaceA::my_spaceB::haha << endl;
Circle c(10);
//c.setR(10);
printf("周长 %f\n", c.perimeter() );
Circle cd; //无参构造函数要这样写
printf("周长 dd %f\n", cd.perimeter() );
return 0;
}
构造函数有无参构造函数和有参构造函数,申请一个类的对象的时候都不一样。
析构函数
类的析构函数是类的一种特殊的成员函数,它会在每次删除所创建的对象时执行。
析构函数的名称与类的名称是完全相同的,只是在前面加了个波浪号(~)作为前缀,它不会返回任何值,也不能带有任何参数。析构函数有助于在跳出程序(比如关闭文件、释放内存等)前释放资源。
class Circle
{
public:
Circle() { //无参构造函数
m_r = 10;
}
Circle(float r){ //有参构造函数
m_r = r;
}
~Circle() { //析构函数,是无参数的,也不支持重载
//为了释放类中的资源
printf("释放类中资源\n");
}
void setR(float r) {
m_r = r;
}
float perimeter() {
return 2 * m_r * 3.14;
}
float area() {
return m_r * m_r * 3.14;
}
private:
float m_r; //私有变量可以在前面加m_
//float m_area = m_r * m_r * 3.14;
};
析构函数调用顺序,跟构造相反,谁先构造的,谁后析构。
3.6 默认无参构造函数和析构函数
有没有发现我们之间没有写构造函数和析构函数的时候,直接使用了类都没有问题,这是因为c++的类中,默认提供了一个无参的构造函数:
Circle() { //无参构造函数
}
就是长这样,如果有显示定义了其他构造函数,比如有参的,这个默认无参的构造函数就不复存在,类中就只会有显示的构造函数
同理析构函数,也会提供一个默认的析构函数:
~Circle() { //析构函数
}
这个默认的析构函数也是空的,当有显示定义了析构函数之后,这个默认的析构函数也不复存在。
3.7 拷贝构造函数
拷贝构造函数是一种特殊的构造函数,它在创建对象时,是使用同一类中之前创建的对象来初始化新创建的对象。拷贝构造函数通常用于:
- 通过使用另一个同类型的对象来初始化新创建的对象。
- 复制对象把它作为参数传递给函数。
- 复制对象,并从函数返回这个对象。
如果类中没有显示定义拷贝构造函数,就默认会有一个:
Circle (const Circle &c) { //拷贝构造函数
//拷贝复制类中所有变量的值
}
如果我们显示定义了拷贝构造函数,就会调用我们显示定义的:
Circle (const Circle &c) { //拷贝构造函数
//拷贝复制类中所有变量的值
printf("拷贝构造函数\n");
m_r = c.m_r;
}
下面是初始化时调用了拷贝构造函数的例子:
Circle c2(c);
Circle c3 = c;
上面两种都调用了拷贝构造函数,下面这种就不是,
Circle c4;
c4 = c;
这种是调用了=好操作符
void operator=(const Circle &c)
{
printf("=赋值操作\n");
m_r = c.m_r;
}
函数返回值是一个对象的时候:
Circle haha()
{
Circle c;
return c;
}
在return c;的时候,会把对象c赋值给一个匿名对象
//匿名对象 = c; 这个时候也会调用拷贝构造函数
当一个函数返回匿名对象的时候,函数外部没有任何变量去接收它,这个匿名对象将不会再被使用,(找不到了),编译器会直接将这个匿名对象回收掉,而不是等待整个函数执行完再回收。
如果这时候有一个对象接收这个函数的返回值:
Circle c2 = haha();
这次不会触发t1拷贝,而是将匿名对象转正c2,把这个匿名对象,起了一个名字叫c2.
3.8 深拷贝和浅拷贝
按上面的拷贝构造函数是一个浅拷贝
Circle c2©;
把c对象作为模板把类中所有的变量的值(包括指针)都赋值给c2。
这种就是浅拷贝,指针的值也拷贝的话,就会两个指针指向同一个内存空间,这样在析构的时候会出现问题,
所以当类中的变量有指针的时候,拷贝构造函数一定要重写,然后给指针申请一个内存空间,这个就是深拷贝。
3.9 构造函数初始化列表
构造函数初始化列表以一个冒号开始,接着是以逗号分隔的数据成员列表,每个数据成员后面跟一个放在括号中的初始化式。
Circle(Point &p) : m_p(p)
{
}
Point(int x, int y) : m_x(x), m_y(y)
{
}
第一种是初始化类的,m_p§这样调用会按对象p的结构初始化m_p,也就是调用Point的拷贝构造函数。如果m_p()这样,就会调用point的无参构造函数。
第二种初始化普通变量也是可以的,省的在函数里面赋值,这种用法很多。
构造对象成员的顺序跟初始化列表的顺序无关,而是跟成员对象的定义顺序有关。
如果对象中有const的常量,一定要在初始化列表中初始化,因为常量只能在初始化中赋值,不能再做修改。
Circle(p); //这样是申请一次匿名对象,一申请就被析构
Circle c = Circle(p); //这个是用对象c接收到匿名对象,匿名对象转正了,用完了才析构
注意:
Circle(Point &p) : m_p(p)
{
Circle(10);
}
c++的构造函数调用构造函数的时候,好像跟Java不一样,c++在构造函数调用构造函数会创建一个新的匿名对象,这个匿名对象初始化都是初始化自己匿名对象的参数,跟你之前的那个对象没有关系,并且这个匿名对象创建完了之后,就会立马被析构,因为没有被使用。
3.10 new和delete
我们上面举的例子对象都是申请在栈中,堆中的内存用的少,c语言中有两个函数是负责申请堆内存和释放堆内存的,分别是malloc和free,c++中对新提供了两个关键字,对堆内存的申请和释放,分别是new和delete,这样会提高效率,但是还是对c语言的malloc和free函数做了兼容,这两个函数在c++中也可以使用。
c语言malloc申请内存的代码我就不写了,这个熟悉c语言的都写过,c的面向对象,下面即就写new和delete的
int *a = new int; //申请一个a的int型变量
int *a = new int(100); //申请一个a的int型变量,值为100
int *a = new int[100]; //申请一个a的int型数组,大小为100个
Circle a = new Circle(); //申请一个Circle类的对象,调用无参构造函数
Circle a = new Circle(10); //申请一个Circle类的对象,调用有参构造函数
//delete
delete[] a; //释放a的数组
delete a; //释放a的变量或a的对象,如果是对象,会触发调用析构函数(free就直接释放,不会调用析构)
3.11 静态成员变量和静态成员函数
类的静态成员有两种:静态成员变量和静态成员函数。静态成员变量就是在定义时前面加了 static 关键字的成员变量;静态成员函数就是在声明时前面加了 static 关键字的成员函数。
class Haha
{
public:
static int m_o; //静态变量
static int set_o(int o) { //静态函数,只有静态函数才能设置静态变量
m_o = o;
}
}
//静态成员变量的初始化,一定要在类的外面
int Haha::m_o = 10;
普通变量成员是每一个类一份,存储的位置可能是堆也可能是栈,而静态变量是共同一个类才有一份,都是共享的,数据存储在静态区。
静态成员的访问可以直接用类名访问,因为这个静态成员是有类了之后,就有了,对象是要申请了才有,不过也可以跟普通成员一样,用对象调用。
如果静态变量私有化之后,只能静态函数才能访问,静态函数使用的变量必须是静态变量,不能有其他的普通变量,因为这时候还没有对象,用了普通变量是不行。
因为静态变量存储在静态区,所有sizeof一个类,是不包含静态变量的,函数也不会存在类中的(不是c语言中的指针,具体存哪不知道,等待以后解锁)。所以sizeof一个类的时候,只有普通的成员变量才算大小。