【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)

简介: 本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。

前言

       c++是一种支持面向对象编程(OOP)的语言,而在面向对象编程当中,类和对象是核心概念,理解类和对象是学习面向对象编程的基石。面向对象编程是一种编程范式,它使用“类”来定义对象的属性和方法,完成对软件的设计。掌握类和对象的概念及其相互关系,对于深入理解并有效应用面向对象编程至关重要。


一、类的概念及定义

       类的本质是一种自定义类型,是定义对象模板的蓝图或者结构。它制定了对象可以包含的数据以及该对象可以执行的操作。在某种程度上,c++中的类可以认为是c语言结构体的升级版,不仅可以在其中定义成员变量,也可以定义成员函数,用于对成员变量进行访问或操作。


1. 类的定义格式

     接下来我们尝试定义一个类:

class MyClass
{
    void fun1()
    {
        //...
    }
    void fun2()
    {
        //...
    }
    int _x;
    float _y;
};

以上代码当中:


1. class 是定义类的关键字,Myclass 是类名,{} 中的内容是类的主体,fun1和fun2是类的成员函数,_x 和_y 是类的成员变量。

2. 注意定义最后的分号不能省略。


注意事项:


1. 为了防止命名冲突,类的成员变量在创建时一般会在变量名前加上一个特殊的标识符,例如_x,_y。

2. 定义在类中的成员函数默认具有标签inline。


       在c++当中,struct也可以用于定义类,与c语言不同的是,strcut当中可以定义函数,并且类的类型名不需要再带struct。


2. 访问限定符

       c++中有三种访问限定符,用于对类成员的访问权限进行限制,它们分别是:


public(公有):使得被修饰的成员可以在类的外部被访问和修改。

protected(保护):被修饰的成员不可在类外部进行访问,但可以在子类当中访问。

private(私有):对于被修饰的成员,无论是在类外还是在子类中,都无法访问及修改。


一般情况下,我们会对类中的成员变量修饰为private,防止它被外部修改;成员函数修饰为public,便于外部进行方法的调用。


注意:class定义的类当中,如果成员没有被这三种访问限定符修饰,则这些成员默认被private修饰;而struct中的成员默认被public修饰。


接下来,我们简单使用一下这些访问限定符:

#include <iostream>
using namespace std;
 
class MyClass
{
public:
    void fun1()
    {
        //...
    }
 
    void fun2()
    {
        //...
    }
private:
    int _x;
    float _y;
};
 
 
int main()
{
    //创建一个MyClass类对象
    MyClass a;
    a.fun1();//公有的,可以在外部访问
    a._x = 10;//私有成员,不可访问,报错
    return 0;
}

       我们在使用访问限定符时:在其后加上一个冒号,表示从此处开始到下一个访问限定符或者类结束的位置之间的所有成员都被修饰。例如,这里的fun1、fun2函数被修饰为public;_x、_y被修饰为private


       访问限定符不仅是面向对象编程的特性之一——封装的具体实现,也促进了软件设计的质量、规范性、可维护性和安全性。它们是面向对象编程中不可或缺的一部分。


二、类域

       既然学到了类,那就不得不提及类域了。我们都知道,c++一共有四大域:函数局部域、全局域、命名空间域和类域。而我们之前在类中定义的成员函数和成员变量,就属于类域。当我们在类外对类中的成员进行定义时,就需要用域限定运算符“ : : ”。举个例子:

class MyClass
{
public:
    void fun();//方法的声明
 
private:
    int _m;
};
 
void MyClass::fun()//方法的定义,要使用域限定运算符表明该方法所在的类域
{
    //...
}

那么,为什么要使用域限定运算符来表明类域呢?因为类域影响的是编译器的查找规则。如果fun函数没有声明类域,那么编译器就会从全局域去查找该函数的声明。此时如果fun函数有涉及对成员变量_m的操作,编译器从全局域找不到_m,就会发生报错。


三、类的实例化--对象

1. 实例化的概念

        与结构体的定义和创建类似,当我们定义了一个类以后,就可以用这个类在内存中创建出一个对象。所谓对象,指的就是根据类创建出的“变量”。而根据类创建对象的过程,叫做类的实例化,我们在内存中创建出的每一个对象都是类的实例。



我们写一段代码体现类的实例化:

#include <iostream>
using namespace std;
 
//类的定义
class MyClass
{
public:
    void fun() 
    {
        //...
    }
private:
    int _x;
};
 
int main()
{
    MyClass a;//类的实例化,创建一个对象叫a
    return 0;
}

在上述代码中,我们对类进行定义时,编译器并没有为其开辟内存空间,就像是造房子的图纸,类就是一个模板,而对象则是根据这个模板建造出的“房子”,创建对象时才会分配内存空间。


2. 对象的内存大小

       既然创建对象时才分配内存空间,那么对象所占内存空间的大小是多少呢?


首先我们写一段程序,用sizeof来计算对象的内存大小:

#include <iostream>
using namespace std;
 
class X
{
public:
    void fun() 
    {
        //...
    }
private:
    int _x;
};
 
int main()
{
    X x;
    cout << sizeof(x) << endl;
    return 0;
}

运行结果:



可以看到,对象x所占空间是4个字节。从代码当中得知,这个类中包含一个函数fun和一个整形变量_x,而整形的大小是4个字节,所以说对于对象而言,成员变量的内存是包含在其中的,而成员函数不在对象当中存储,而是在代码段当中。其次,c++规定,对象的成员变量才存储时要符合结构体的内存对齐规则。


规则如下:


1.结构体的第一个成员对齐到和结构体的起始地址的偏移量为0的地址处,也就是说第一个成员的偏移量记为0。


2.其他的成员要对齐到该成员的对齐数整数倍的地址处。


(对齐数:编译器默认对齐数与该成员内存大小的较小值;在VS环境中,默认对齐数是8;linux系统中,没有默认对齐数,对齐数就是该成员内存大小)


3.结构体的总大小为结构成员中最大的对齐数的整数倍。


当类中仅有成员函数或者空类的情况:

#include <iostream>
using namespace std;
 
class A
{
    void fun()
    {
        //...
    }
};
 
class B
{
 
};
 
int main()
{
    cout << sizeof(A) << endl;
    cout << sizeof(B) << endl;
    return 0;
}

运行结果:



当类中只有成员函数或者类为空类时,其所创建的对象大小为1字节,纯属占位作用。


四、this指针

       首先来看一段代码:

#include <iostream>
using namespace std;
 
class MyClass
{
public:
    MyClass(int a = 0, float b = 0, char c = 0)//构造函数,用于初始化对象的成员变量,后续会给大家介绍
    {
        _a = a;
        _b = b;
        _c = c;
    }
    void Print()
    {
        cout << _a << endl;
        cout << _b << endl;
        cout << _c << endl;
    }
private:
    int _a;
    float _b;
    char _c;
};
 
int main()
{
    MyClass m = { 1,5.5,'w' };
    m.Print();
}

运行结果:



以上程序中,我们首先用MyClass创建了一个对象m,并且对其进行了初始化。之后,我们打印了一下其中三个成员变量的值。这里不难发现,Print函数是没有参数的。那既然没有参数,那么编译器是怎么知道要打印的是哪个对象的成员变量呢?

       实际上,这里的Print函数的参数的第一个位置,存在一个隐含的this指针



当我们调用对象的成员函数时,本质是将该对象的地址赋值给this指针,隐含的this指针总是指向该对象,不可改变。也就是说,该函数调用当中的this指针指向的是对象m。所以它的本质是通过隐含的this指针,就访问到了对象m的成员。


要注意:

1. 在函数的实参和形参中,这个this指针会自动在参数第一个位置生成,我们不能显示地写出来;但是在函数体内我们可以使用this指针。

2. this指针只能在成员函数内部使用。

3. this指针只是一个形参,并不存储在对象当中。


       this指针的用处:


1. 当我们需要使成员函数返回该对象的地址,就可以return this;

2. 当函数内的局部变量与类的成员变量名发生冲突时,就可以在类成员前加上this->,便于区分。


小练习:


1. 以下代码的运行结果是?

#include <iostream>
using namespace std;
 
class MyClass
{
public:
    void Print()
    {
        cout << "hehe" << endl;
    }
private:
    int _a;
};
 
int main()
{
    MyClass* a = nullptr;
    a->Print();
}

答案:


正常运行,打印“hehe”。原因是:这里创建类指针a,并且调用函数Print。可以看到程序中虽然使用了“->”,但是并没有对空指针a进行解引用,本质是将a传递给了形参this指针。而函数当中并没有访问成员变量,只是打印了“hehe”,所以不会发生问题,程序正常运行。


2. 以下代码的运行结果是:

#include <iostream>
using namespace std;
 
class MyClass
{
public:
    void Print()
    {
        cout << _a << endl;
    }
private:
    int _a;
};
 
int main()
{
    MyClass* a = nullptr;
    a->Print();
}

答案:


运行崩溃。和上一道题相同,本质也是将a传给了形参this,但是函数内部却访问了成员变量_a,我们都知道_a本质是由this指针解引用访问到的,但是此时的形参this是空指针,所以就出现了对空指针解引用的问题,运行崩溃。


总结

       今天,我们初入了c++类和对象的大门,学习了类的概念及定义、类实例化出对象,以及this指针的概念及作用。学习并理解这些知识,对于我们理解面向对象编程的特性之一--封装有很大帮助。如果你觉得博主讲的还不错,就请留下一个小小的赞在走哦,感谢大家的支持❤❤❤

相关文章
|
8月前
使用指针访问数组元素
【10月更文挑战第30天】使用指针访问数组元素。
98 3
|
4月前
|
安全 C++
【c++】继承(继承的定义格式、赋值兼容转换、多继承、派生类默认成员函数规则、继承与友元、继承与静态成员)
本文深入探讨了C++中的继承机制,作为面向对象编程(OOP)的核心特性之一。继承通过允许派生类扩展基类的属性和方法,极大促进了代码复用,增强了代码的可维护性和可扩展性。文章详细介绍了继承的基本概念、定义格式、继承方式(public、protected、private)、赋值兼容转换、作用域问题、默认成员函数规则、继承与友元、静态成员、多继承及菱形继承问题,并对比了继承与组合的优缺点。最后总结指出,虽然继承提高了代码灵活性和复用率,但也带来了耦合度高的问题,建议在“has-a”和“is-a”关系同时存在时优先使用组合。
234 6
|
7月前
|
存储 程序员 C++
深入解析C++中的函数指针与`typedef`的妙用
本文深入解析了C++中的函数指针及其与`typedef`的结合使用。通过图示和代码示例,详细介绍了函数指针的基本概念、声明和使用方法,并展示了如何利用`typedef`简化复杂的函数指针声明,提升代码的可读性和可维护性。
216 1
|
8月前
使用指针访问数组元素
【10月更文挑战第31天】使用指针访问数组元素。
98 2
|
9月前
|
存储 安全 编译器
在 C++中,引用和指针的区别
在C++中,引用和指针都是用于间接访问对象的工具,但它们有显著区别。引用是对象的别名,必须在定义时初始化且不可重新绑定;指针是一个变量,可以指向不同对象,也可为空。引用更安全,指针更灵活。
|
5月前
|
编译器 C++ 开发者
【C++篇】深度解析类与对象(下)
在上一篇博客中,我们学习了C++的基础类与对象概念,包括类的定义、对象的使用和构造函数的作用。在这一篇,我们将深入探讨C++类的一些重要特性,如构造函数的高级用法、类型转换、static成员、友元、内部类、匿名对象,以及对象拷贝优化等。这些内容可以帮助你更好地理解和应用面向对象编程的核心理念,提升代码的健壮性、灵活性和可维护性。
|
1月前
|
人工智能 机器人 编译器
c++模板初阶----函数模板与类模板
class 类模板名private://类内成员声明class Apublic:A(T val):a(val){}private:T a;return 0;运行结果:注意:类模板中的成员函数若是放在类外定义时,需要加模板参数列表。return 0;
43 0
|
1月前
|
存储 编译器 程序员
c++的类(附含explicit关键字,友元,内部类)
本文介绍了C++中类的核心概念与用法,涵盖封装、继承、多态三大特性。重点讲解了类的定义(`class`与`struct`)、访问限定符(`private`、`public`、`protected`)、类的作用域及成员函数的声明与定义分离。同时深入探讨了类的大小计算、`this`指针、默认成员函数(构造函数、析构函数、拷贝构造、赋值重载)以及运算符重载等内容。 文章还详细分析了`explicit`关键字的作用、静态成员(变量与函数)、友元(友元函数与友元类)的概念及其使用场景,并简要介绍了内部类的特性。
110 0
|
3月前
|
编译器 C++ 容器
【c++11】c++11新特性(上)(列表初始化、右值引用和移动语义、类的新默认成员函数、lambda表达式)
C++11为C++带来了革命性变化,引入了列表初始化、右值引用、移动语义、类的新默认成员函数和lambda表达式等特性。列表初始化统一了对象初始化方式,initializer_list简化了容器多元素初始化;右值引用和移动语义优化了资源管理,减少拷贝开销;类新增移动构造和移动赋值函数提升性能;lambda表达式提供匿名函数对象,增强代码简洁性和灵活性。这些特性共同推动了现代C++编程的发展,提升了开发效率与程序性能。
112 12
|
4月前
|
设计模式 安全 C++
【C++进阶】特殊类设计 && 单例模式
通过对特殊类设计和单例模式的深入探讨,我们可以更好地设计和实现复杂的C++程序。特殊类设计提高了代码的安全性和可维护性,而单例模式则确保类的唯一实例性和全局访问性。理解并掌握这些高级设计技巧,对于提升C++编程水平至关重要。
96 16