带你初步了解 类和对象 的基本机制

目录

类的初步了解:

访问限定符:

类的作用域:

计算类的大小:

内存对齐? 

this指针:

this指针可以为0嘛?


类的初步了解:

我们在c语言中,结构体里面只能定义变量,在c++中结构体里面现在可以定义函数

struct stu
{
    void test1()
    {  
        ;      
    }
    
    int test2()
    {
        ;
    }

    int a;
    int b;
    int* c;
};

所以在c++中,c++兼容c的用法同时c++也对struct进行了升级,让其变成了类,另外可以直接用结构体的名称来定义变量(也就是可以用上面代码中的 stu 来定义变量) 我们类里面的函数叫做 成员函数 变量叫做 成员变量

 给大家穿插一个知识点,我们的成员变量在类里面只是一个声明,就像上面的a,b,c 只是声明,我们是在创建对象的时候才会有它的定义,后面的构造函数大家学后,就明白了。那你可能会说“int a =10;”这样写是不是定义呢?

当然也不是定义,这里的10是一个缺省值,用于初始化列表中使用,大家学到后面就知道了

成员变量的名字不要和成员函数中形参的名字相同,因为在使用的时候我们会采用就近原则它里形参的定义近就用形参: 

struct stu
{
    void test1(int a)
    {  
        a = a;      
    }
    
    int test2()
    {
        ;
    }

    int a;
    int b;
    int* c;
};

这里的test1就是这种情况,所以是不能这样写的,对此,我们对成员变量的命名就可以自己加一些符号,既方便自己写的时候不容易搞混,也不会出现上面那种情况,我个人习惯是在变量的前面加上 _ 这个下划线,每个公司也都是有自己的标准来定义成员变量的

虽说我们的c++是兼容c语言的 我们可以用struct定义 但我们在c++就更喜欢使用 class 来定义我们写出来的类,可以用来定义变量,但现在这些变量我们都叫做 对象 (用类来定义了一个对象)

既然我们可以用class和struct 来定义类,那他们两个有什么区别呢? 

访问限定符:

这里与这个相关的就是 封装 ,我们自己写的东西有的我不想让你随便访问我们就会用一个访问限定符把他封装起来(private)这个意思是 私有的 还有两个是 public 和 protect 

struct stu
{
    void test1(int a)
    {  
        _a = a;      
    }
    
    int test2()
    {
        ;
    }

    int _a;
    int _b;
    int* _c;
};

int main()
{
    stu tmp;
    tmp.test1(10);
    tmp._b=10;

}

这里我们的 _a 和 _b都被我们赋值成了 10 一个是通过函数 一个在类外面直接赋值 大家想想如果这里我把struct stu 换成 class stu 又会怎样呢?它会不会报错呢?

class stu
{
    void test1(int a)
    {  
        _a = a;      
    }
    
    int test2()
    {
        ;
    }

    int _a;
    int _b;
    int* _c;
};

int main()
{
    stu tmp;
    tmp.test1(10);
    tmp._b=10;

}

答案肯定是会报错的,不知道大家看出来他们两个之间的差别了嘛?

我们的struct定义的类,它里面都是默认为共有的,是可以随便访问的

class定义出来的类,它里面都是默认为私有的,是不可以在类外访问的

class stu
{
public:
    void test1(int a)
    {  
        _a = a;      
    }
    
    int test2()
    {
        ;
    }

    int _a;
    int _b;
    int* _c;
};

int main()
{
    stu tmp;
    tmp.test1(10);
    tmp._b=10;

}

  改成这样就是正确的了(当然我们的struct也是可以加访问限定符的哦),我们通常的写法是把成员变量用private封装起来,不让你随便访问改变我们的值(因为如果改变了是很有可能影响到我们写的函数的功能的),但如果说有些变量是我们常常访问的,也是可以把它放到 public 里面去的

  我们的一个访问限定符限制的范围是从当前的位置到下一个访问限定符截至或者是到末尾截至,至于protect大家学习到继承(面向对象的三大特性:封装、继承、多态)就知道了

在了解class和struct的区别中,我们也了解了一下封装,封装大家可以理解为本质是一种管理 封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现的细节,仅对外公开接口来和对象进行交互

类的作用域:

   我们定义出来的类,也相当于是定义了一个新的作用域,注意这里是一个作用域,作用域的特点有什么,我们在不同的作用域内是可以有同名函数同名变量的哦,我们一般都叫类定义出来的域叫 类域 

   既然有了一个类域,当我们想要在这个域的外面,定义函数的时候该怎么办呢?这个时候 :: 这个域作用限定符就可以了

class stu
{
public:
	void test1(int a);
	int test2(char ch);

private:
	int _a;
	char _b;
};

void stu::test1(int a)
{
	_a = a;
}

int stu::test2(char ch)
{
	_b = ch;
}

    我们这里使用了域作用限定符,就不属于是在类外访问变量的错误做法,我们这里已经表明了我们是这个域里面的,所以可以这样在类外定义函数,像上面我们写得在类外操作私有成员就是不行的

  注意:我们在类里面定义的函数都是 内联函数,注意是类里面定义的才是哦,所以根据内联函数的特性,我们一般,短小的函数可以直接在类里面定义,长一点的函数就可以选择在类外定义,当然这个不是标准看个人习惯

 

注意:我们在类里面定义的函数满足内联函数的特性,内联只是给编译器的一个建议,如果函数篇幅较长,就不会去展开它,而是去调用

计算类的大小:

大家觉得我们以下的代码,计算stu的大小是多大呢?

class stu
{
public:
    void test1(int a)
    {  
        _a = a;      
    }
    
    int test2()
    {
        ;
    }

    int _a;
    char _c;
    int _b;
};

int main()
{
    stu tmp;
    tmp.test1(10);
    tmp._b=10;

}

内存对齐? 

 答案当然是 12 至于为什么大家可以去看这篇博客力的内存对齐:

C语言进阶学习日志:自定义类型 (细中细)_luck++的博客-优快云博客    

    可是我们这里不是还写得有函数嘛 为什么只算了成员变量的大小呢

    首先大家这样想:如果我们每个对象都保存一份代码,相同的代码被保存多次,这又是何必呢?

    所以有了只保存成员变量,成员函数放在公共的代码段(操作系统的角度来看,也就是内存中的代码段)调用的时候,也就是去公共代码段去找

    如果是这样的类,大家觉得类的大小又会是多大呢?

class stu
{
public:
    void test() {}
};

class s1
{
};

他们最终的答案都是 1 ,首先这里不可能是0,我们打印他们定义出来的对象的地址,是可以打印出来的,这里的 1 也不是用来存储有效地址的,这个是为了表示我们的对象存在过

this指针:

我们的成员函数中是存在又一个隐含的this指针的  

class stu
{
public:
    stu(int a,int c,int b)
    {    
        _a=a;
        _c=c;
        _b=b;
    }

    void test1()
    {  
        cout<<_a<<"-"<<_c<<"-"<<_b<<endl;
    }
    
    
private:
    int _a;
    char _c;
    int _b;
};

int main()
{
    stu s1(10,'A',20);
    stu s2(30,'B',40);
    
    s1.test1();
    s2.test1();

}

这里的stu是一个构造函数,通过这样的调用来完成 s1和s2 的定义和初始化,无论是这里的构造函数,还是它下面的test1函数,他们都是存放在公共代码段的,我们调用都是同样的函数,那他是怎么样来区分到底是 s1 调用还是 s2调用呢

 

 大家看这张图就可以看到,我们的函数都是调用的一样的,我们的上面方括号里面的内容确是不一样的,其实我们调用的时候,函数本样是这样的:

class stu
{
public:
    stu(stu* this,int a,int c,int b)
    {    
        this->_a=a;
        this->_c=c;
        this->_b=b;
    }

    void test1(stu* this)
    {  
        cout<<this->_a<<"-"<<this->_c<<"-"<<this->_b<<endl;
    }
   
    
private:
    int _a;
    char _c;
    int _b;
};

int main()
{
    stu s1(&s1,10,'A',20);
    stu s2(&s2,30,'B',40);
    
    s1.test1(&s1);
    s2.test1(&s2);

}

   我们是通过传一个对象的地址,来通过地址来完成对象内容的改变与访问

   不知道大家会不会有个这样的疑问,之前我学习这里就是这样理解错误了,那个时候我觉得我去改变为什么一定要有一个this指针呢,我这里不是有那些成员变量嘛,这些成员变量不都是对象自己的嘛,直接去访问赋值不就好了嘛

  当然这样肯定是不正确的,我们是不能这样去实现对象的赋值与访问的,我们那些成员变量都只是声明,声明我们这个类里面是有这个变量的,真正要访问它,是必须通过定义对象,通过一个对象去访问它的,所以我们这个隐含的this指针是必不可少的

还有一个点大家需要注意:我们实参、形参的位置是不能这样显示去写的(因为这个是隐含的,是编译器去实现的),但是我们在函数的内部是可以显示的写的 我们的this指针也是被const修饰的,自身不能被改变,指向的对象是可以改变的

this指针可以为0嘛?

就这个问题大家看了这段代码就知道了: 

class stu
{
public:
    stu(int a,int c,int b)
    {    
        _a=a;
        _c=c;
        _b=b;
    }

    void test1()
    {  
        cout<<_a<<"-"<<_c<<"-"<<_b<<endl;
    }
    
    void test2()
    {
        cout<<"Yes"<<endl;
    }
    
private:
    int _a;
    char _c;
    int _b;
};

int main()
{
    stu* p =nullptr;
    p->test2();
    
}

大家觉得这段代码运行有问题嘛?

    如果拿去试过就知道了,这段代码是没有问题的,可我这里的p不是空嘛,去访问空指针里面的内容,不是会运行崩溃嘛,那我这样问:我的成员函数是在对象里面的嘛,他们不是存放在公共代码段嘛,我们在编译的时候,如果函数的篇幅较长,这里的调用就会转换成 去 call test2 的指令了,较短的话,就是直接展开了(因为内联) 就不存在访问空指针的问题了

   当然如果这里的成员函数是公有的,访问他们就不行了,这里的 test2函数 依旧也是存在隐含的this指针的,这里的this指针就是空了(我们这里也是把p的地址传过去了的)

 最后给大家提一个东西,我们的this指针按理来讲是存在栈上面的,但通常编译器优化后会放到寄存器里面,上面那图,call指令上面的指令就是,把 p 存到了 ecx 寄存器里面去了


谢谢大家看到这里,预祝大家都能收到自己心仪大厂的offer!!! 

重点:C++ :类 和 对象 ② ※重点※_luck++的博客-优快云博客 这一篇涉及更多类和对象中的细节

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值