c++学习笔记(三、类和对象)

类是c++中特有的,c是没有类的概念的。

3.1 类

这个类还真没啥好写的,c++的类脱胎已c的结构体,只不过结构体中不能定义函数(c++可以在结构体中定义函数了),c语言用结构体模拟类只能用函数指针。

在C++中, 用 “类” 来描述 “对象”, 所谓的"对象"是指现实世界中的一切事物。那么类就可以看做是对相似事物的抽象, 找到这些不同事物间的共同点, 如自行车和摩托车, 首先他们都属于"对象", 并且具有一定得相同点, 和一些不同点, 相同点如他们都有质量、都有两个轮子, 都是属于交通工具等。“都有质量”、"两个轮子"属于这个对象的属性, 而"都能够当做交通工具"属于该对象具有的行为, 也称方法。

类是属于用户自定义的数据类型, 并且该类型的数据具有一定的行为能力, 也就是类中说描述的方法。通常来说, 一个类的定义包含两部分的内容, 一是该类的属性, 另一部分是它所拥有的方法。以 “人类” 这个类来说, 每个人都有自己的姓名、年龄、出生日期、体重等, 为 人类 的属性部分, 此外, 人能够吃饭、睡觉、行走、说话等属于人类所具有的行为。

上面举例中所描述的 “人” 类仅仅是具有人这种对象的最基础的一些属性和行为, 可以称之为人的"基类"。 再说说一些具有一些职业的人, 例如学生, 一个学生还具有"基类"中所没有的属性, 如学校、班级、学号; 也可以具有基类所不具有的行为, 如每天需要去上课, 需要考试等。

学生类可以看做是基类的一个扩展, 因为他具有基类的所有属性和行为, 并且在此基础上增加了一些基类所没有的属性和行为, 像"学生"这样的类称为"人类"这个基类的"派生类"或者"子类"。在学生的基础上海可以进一步的扩展出其他更高级的类, 如"研究生"类。
参考这篇博客https://blog.csdn.net/xulingxin/article/details/81335030

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;
 }

是不是很简单,类中的变量是可以赋初值的,但是不能用变量的表达式的值,因为类加载的过程中,申请的变量是随机的,所以得出的值也会是随机的,不过可以赋值常数,这个不会被改变。

扫描二维码关注公众号,回复: 8985351 查看本文章

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一个类的时候,只有普通的成员变量才算大小。

发布了32 篇原创文章 · 获赞 26 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/C1033177205/article/details/104074761