C++类和对象(上): 封装与this指针

简介: C++类和对象(上): 封装与this指针

目录

一.前言

二. 类的引入和定义

1.C和C++结构体的区别

2.C++类的定义

3.类的成员方法的声明和定义是可分离的

三.面向对象之封装特性

1.封装思想的介绍

2.类封装编程模式的优点

四. 类实例(对象)的内存模型

五.this指针

章节导图:

一.前言
面向过程和面向对象初步认识:

1.C语言是面向过程的,关注的是过程,分析出求解问题的步骤,通过函数调用逐步解决问题;

2.C++是基于面向对象的,关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交互完成

对于面向对象的编程思想,我们还需要长时间的学习和积累才可能对其有更深刻的理解。

二. 类的引入和定义
1.C和C++结构体的区别
C语言结构体中只能定义变量,在C++中,结构体内不仅可以定义变量,也可以定义函数。(可在其中定义函数的自定义类型为面向对象编程提供了可能)

比如:

代码段1:

include

using std::cout;
using std::cin;
using std::endl;

include <assert.h>

include

struct STU
{

int _data;                             为了区分结构中成员变量和结构函数的形参,习惯性地在成员变量前标记_
char _name[20];
void init(int data, const char* name)  //结构体初始化函数
{
    assert(name);
    _data = data;                      结构体中的函数可以直接访问结构体的成员变量
    strcpy(_name, name);
}
 
void Prin()                            //结构体打印函数
{
    cout << _data << endl;
    cout << _name << endl;
}

};

int main()
{

STU a;                                  创建一个结构体变量(C++中类型名无需加struct关键字)
a.init(23, "张三");
a.Prin();
return 0;

}

结构体中的函数可以直接访问结构体的成员变量

注意:

为了区分结构(类)中成员变量 和 结构(类)成员函数的形参,习惯性地在成员变量标识名前标记_(具体标记什么无所谓)。

2.C++类的定义
C++的类也是一种类似于C++结构体的自定义类型(两者的区别后续学习中再进一步探究),不仅可以定义变量,也可以定义函数(类中的函数亦称为方法)

C++类定义:

class className
{ // 类体:由成员方法和成员变量组成

public:         // 公有域和私有域可以在类中随意划分
//公有域的成员
private:
//私有域的成员
public:
//公有域的成员
......

}; // 一定要注意后面的分号
相关关键字和公私有域介绍:

(1)class为定义类的关键字,ClassName为类的名字,{}中为类的主体。

(2)类的私有域(由关键字private标识)中的成员只能被类中的方法访问,不能在类作用域(由类的花括号限定)外被访问。

(3)类的公有域(由关键字public标识)的成员既可以被类中的方法访问,也可以在类作用域(由类的花括号限定)外被访问。

C++类和结构体的其中一个区别在于成员变量和方法所在默认公私有域不同:

(class的默认外部访问权限为private,struct为public(因为struct要兼容C))

(1)结构体的成员变量和方法在用户不指定公私有域的情况下,默认属于public公有域。

(2)类的成员变量和方法在用户不指定公私有域的情况下,默认属于private私有域.

(编程中最好不要使用默认的公私属性,要明确地划分好公有域和私有域)

但是在C++中,构建某类对象时我们一般习惯使用class而不是struct(并且class和struct在后续的深入学习中还有一些其他方面的差别)

3.类的成员方法的声明和定义是可分离的
类的成员方法的声明和定义是可以分离的,我们可以在类的作用域中只写成员方法的声明,然后成员方法的定义可以放在类的作用域外;

比如:

注意:在类的作用域外实现的成员方法标识名前一定要用类名加作用域限定符:: 来标识成员方法属于某个类的作用域。(类似于命名空间,类实质上定义了一个新的作用域)

这种类的实现方式在工程项目中十分有用,可以大大提高代码的可读性和可维护性。

补充:成员方法如果在类的作用域中完成定义,编译器可能会将其当成内联函数处理。内联函数参见:http://t.csdn.cn/r8m2N

三.面向对象之封装特性
1.封装思想的介绍
面向对象的封装思想:将数据和操作数据的方法进行有机结合,通过访问权限的选择限定隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。(类的语法特性就是为封装而设计的)

封装本质上是一种管理,让用户更便捷地使用类。

(打个比方:计算机出厂时,在外部套上壳子,将内部的电路,芯片等等细节隐藏起来,仅仅对外提供开关机、鼠标以及键盘插孔等操作接口,让用户可以边界地与计算机进行交互)

2.类封装编程模式的优点
相比于C语言的编程模式(将函数模块暴露在全局作用域中) ,C++的类封装模式具有很多的优越性.

比如现在我们用类来实现一个栈对象。

using std::cin;
using std::cout;
using std::endl;

typedef int DataType;
class Stack
{
public:

void Init()
{
    _array = (DataType*)malloc(sizeof(DataType) * 3);
    if (NULL == _array)
    {
        perror("malloc申请空间失败!!!");
        return;
    }
    _capacity = 3;
    _size = 0;
}


void Push(DataType data)
{
    CheckCapacity();
    _array[_size] = data;
    _size++;
}



void Pop()
{
    if (Empty())
    return;
    _size--;
}


DataType Top()
{ 
    return _array[_size - 1];
}



int Empty() 
{ 
    return 0 == _size;
}



int Size()
{ 
    return _size;
}

void Destroy()
{
    if (_array)
    {
        free(_array);
        _array = NULL;
        _capacity = 0;
        _size = 0;
    }
}

private:

void CheckCapacity()
{
    if (_size == _capacity)
    {
        int newcapacity = _capacity * 2;
        DataType* temp = (DataType*)realloc(_array, newcapacity *
        sizeof(DataType));
        if (temp == NULL)
        {
            perror("realloc申请空间失败!!!");
            return;
        }
        _array = temp;
        _capacity = newcapacity;
    }
}

private:

DataType* _array;
int _capacity;
int _size;

};

int main()
{

Stack s;
s.Init();
s.Push(1);
s.Push(2);
s.Push(3);
s.Push(4);
printf("%d\n", s.Top());
printf("%d\n", s.Size());
s.Pop();
s.Pop();
printf("%d\n", s.Top());
printf("%d\n", s.Size());
s.Destroy();
return 0;

}

如果用C语言来实现栈:

typedef int DataType;
typedef struct Stack
{

DataType* array;
int capacity;
int size;

}Stack;

void StackInit(Stack* ps)
{

assert(ps);
ps->array = (DataType*)malloc(sizeof(DataType) * 3);
if (NULL == ps->array)
{
    assert(0);
    return;
}
ps->capacity = 3;
ps->size = 0;

}

void StackDestroy(Stack* ps)
{

assert(ps);
if (ps->array)
{
    free(ps->array);
    ps->array = NULL;
    ps->capacity = 0;
    ps->size = 0;
}

}

void CheckCapacity(Stack* ps)
{

if (ps->size == ps->capacity)
{
    int newcapacity = ps->capacity * 2;
    DataType* temp = (DataType*)realloc(ps->array,newcapacity*sizeof(DataType));
    
    if (temp == NULL)
    {
        perror("realloc申请空间失败!!!");
        return;
    }
    ps->array = temp;
    ps->capacity = newcapacity;
}

}

void StackPush(Stack* ps, DataType data)
{

assert(ps);
CheckCapacity(ps);
ps->array[ps->size] = data;
ps->size++;

}

int StackEmpty(Stack* ps)
{

assert(ps);
return 0 == ps->size;

}

void StackPop(Stack* ps)
{

if (StackEmpty(ps))
return;
ps->size--;

}

DataType StackTop(Stack* ps)
{

assert(!StackEmpty(ps));
return ps->array[ps->size - 1];

}

int StackSize(Stack* ps)
{

assert(ps);
return ps->size;

}

int main()
{

Stack s;
StackInit(&s);
StackPush(&s, 1);
StackPush(&s, 2);
StackPush(&s, 3);
StackPush(&s, 4);
printf("%d\n", StackTop(&s));
printf("%d\n", StackSize(&s));
StackPop(&s);
StackPop(&s);
printf("%d\n", StackTop(&s));
printf("%d\n", StackSize(&s));
StackDestroy(&s);
return 0;

}

C++的类封装写法相比于C语言将栈操作函数放置在全局区域而言的好处:

(1).C++中通过类可以将数据以及操作数据的方法进行完美结合,将所有栈操作函数封装在类中便于代码的维护和管理。

(2).由于类存在公私有访问权限的限定,所以外部用户无法修改栈对象的 _array ,_capacity,_size这三个维护栈的关键信息,而C语言在主函数中是可以随意对维护栈的指针和栈容量等信息进行修改的,因此C++的栈对象使用起来更加安全。

(3).将所有栈操作函数封装在类中,使用时统一通过类成员访问的方式去调用,代码的可读性更高(尤其是当同类型类对象很多的时候)

(4)C语言实现的栈涉及到大量指针操作,稍不注意可能就会出错。

(5)编程思维的转变:从编写功能模块去操作对象的编程思维转变成构造对象去使用自身成员方法的编程思维。

四. 类实例(对象)的内存模型
用定义好的类去创建类对象实例,对象实例在自身的内存区块中只存放了成员变量,类的成员方法(函数体指令段)存放在只读常量区被所有类实例对象所共用。

类对象实例的内存区块中,只存有成员变量,而不存放函数体

有两个例子可以验证这一点:

例一:

include

using std::cout;
using std::cin;
using std::endl;

include <assert.h>

include

static class STU
{

                           
int _data;                             //为了区分结构中成员变量和结构函数的形参,习惯性地在成员变量前标记_
char _name[20];

public:

void init(int data, const char* name)  //结构体初始化函数
{
    assert(name);
    _data = data;                      //结构体中的函数可以直接访问结构体的成员变量
    strcpy(_name, name);
}
 
void Prin()                            //结构体打印函数
{
    cout << _data << endl;
    cout << _name << endl;
}

void Prin2()
{
    cout << "Prin2" << endl;
}

};

int main()
{

STU a;                                  //创建一个类对象a
STU b;                                  //创建一个类对象b
a.init(23, "张三");
b.init(24, "李四");
return 0;

}

创建两个STU对象a,b,分别调用它们方法init,转到汇编代码观察两次调用init时call指令访问到的函数体地址:

这充分说明了多个类对象公用的是同一个函数体代码段。

例二:

代码段(1)

class A
{
public:

void PrintA()
{
    cout<<_a<<endl;
}

private:

int _a;

};

int main()
{

A* p = nullptr;        将空指针赋给类指针p
p->PrintA(); 
return 0;

}

主函数中没有创建类对象实例,p是空指针(值为0),而PrintA函数中访问了类成员_a,所以调用PrintA方法会导致非法访问内存空间

代码段(2)

class A
{
public:

void Print()
{
    cout << "Print()" << endl;
}

private:

int _a;

};

int main()
{

A* p = nullptr;
p->Print();
return 0;

}

代码段(2)的不同之处在于Print方法中没有访问类的成员变量,又因为类的方法(函数体)是存放在只读常量区的,并没有存放在任何类对象实例的内存空间中,所以该代码段可以正常运行输出。这也充分说明了类函数体的代码段是独立存放在内存中的。

因此,类对象实例的所占的内存空间根据其成员变量来计算即可,各成员变量的内存分布遵循结构体内存对齐原则.

结构体内存对齐参见:http://t.csdn.cn/eTtYF

五.this指针

1.C++编译器在编译阶段会给类的每个非静态(非static修饰)的成员函数(方法)的形参表中增加一个隐藏的指针参数(形参).(指针参数名为this,this在C++中被作为关键字)。(编译器添加this指针的操作不会在源码层面改变代码,仅仅只是在编译阶段临时添加而已)

2.类对象调用成员函数时,编译器会自动向成员函数传入当前对象的地址作为this指针的实参使this指针指向当前对象

3.在成员函数体(成员方法)中所有访问成员变量的操作,都是通过this指针实现的。

4.this指针的类型为 : (类名)* const; this 指针被const保护无法被修改

比如:

using std::cout;
using std::cin;
using std::endl;

include <assert.h>

include

class Date
{
public:

void Init(int year, int month, int day)
{
    _year = year;
    _month = month;
    _day = day;
}
void Print()
{
    cout << _year << "-" << _month << "-" << _day << endl;
}

private:

int _year; // 年
int _month; // 月
int _day; // 日

};

int main()
{

Date d1, d2;
d1.Init(2022, 1, 11);
d2.Init(2022, 1, 12);
d1.Print();
d2.Print();
return 0;

}

编译时:

​​

注意:this形参指针在各“成员函数”形参表的第一个位置
我们可以通过汇编来观察一下:

继续往下调试通过call指令跳转到函数体指令段中:

this指针的添加和使用的操作在源码层面是不可见的,都是编译器在编译阶段自动完成的。
但是通过汇编可以很清楚地看到this指针的存在。

this指针的存在使类成员方法可以返回类对象的地址或类对象本身的引用

比如:

include

using std::cout;
using std::cin;
using std::endl;

include <assert.h>

include

class Date
{
public:

Date& Init(int year, int month, int day)
{
    _year = year;
    _month = month;
    _day = day;
    return *this;
}
Date* Print()
{
    cout << _year << "-" << _month << "-" << _day << endl;
    return this;
}

private:

int _year; // 年
int _month; // 月
int _day; // 日

};

int main()
{

Date d1;
(d1.Init(2022, 1, 11)).Print();
return 0;

}

这种用法在后续的学习中会经常见到,也十分地重要。

相关文章
|
5月前
|
人工智能 机器人 编译器
c++模板初阶----函数模板与类模板
class 类模板名private://类内成员声明class Apublic:A(T val):a(val){}private:T a;return 0;运行结果:注意:类模板中的成员函数若是放在类外定义时,需要加模板参数列表。return 0;
149 0
|
5月前
|
存储 编译器 程序员
c++的类(附含explicit关键字,友元,内部类)
本文介绍了C++中类的核心概念与用法,涵盖封装、继承、多态三大特性。重点讲解了类的定义(`class`与`struct`)、访问限定符(`private`、`public`、`protected`)、类的作用域及成员函数的声明与定义分离。同时深入探讨了类的大小计算、`this`指针、默认成员函数(构造函数、析构函数、拷贝构造、赋值重载)以及运算符重载等内容。 文章还详细分析了`explicit`关键字的作用、静态成员(变量与函数)、友元(友元函数与友元类)的概念及其使用场景,并简要介绍了内部类的特性。
227 0
|
7月前
|
编译器 C++ 容器
【c++11】c++11新特性(上)(列表初始化、右值引用和移动语义、类的新默认成员函数、lambda表达式)
C++11为C++带来了革命性变化,引入了列表初始化、右值引用、移动语义、类的新默认成员函数和lambda表达式等特性。列表初始化统一了对象初始化方式,initializer_list简化了容器多元素初始化;右值引用和移动语义优化了资源管理,减少拷贝开销;类新增移动构造和移动赋值函数提升性能;lambda表达式提供匿名函数对象,增强代码简洁性和灵活性。这些特性共同推动了现代C++编程的发展,提升了开发效率与程序性能。
278 12
|
8月前
|
编译器 C++
类和对象(下)C++
本内容主要讲解C++中的初始化列表、类型转换、静态成员、友元、内部类、匿名对象及对象拷贝时的编译器优化。初始化列表用于成员变量定义初始化,尤其对引用、const及无默认构造函数的类类型变量至关重要。类型转换中,`explicit`可禁用隐式转换。静态成员属类而非对象,受访问限定符约束。内部类是独立类,可增强封装性。匿名对象生命周期短,常用于临时场景。编译器会优化对象拷贝以提高效率。最后,鼓励大家通过重复练习提升技能!
|
9月前
|
编译器 C++ 开发者
【C++篇】深度解析类与对象(下)
在上一篇博客中,我们学习了C++的基础类与对象概念,包括类的定义、对象的使用和构造函数的作用。在这一篇,我们将深入探讨C++类的一些重要特性,如构造函数的高级用法、类型转换、static成员、友元、内部类、匿名对象,以及对象拷贝优化等。这些内容可以帮助你更好地理解和应用面向对象编程的核心理念,提升代码的健壮性、灵活性和可维护性。
|
8月前
|
设计模式 安全 C++
【C++进阶】特殊类设计 && 单例模式
通过对特殊类设计和单例模式的深入探讨,我们可以更好地设计和实现复杂的C++程序。特殊类设计提高了代码的安全性和可维护性,而单例模式则确保类的唯一实例性和全局访问性。理解并掌握这些高级设计技巧,对于提升C++编程水平至关重要。
165 16
|
9月前
|
编译器 C语言 C++
类和对象的简述(c++篇)
类和对象的简述(c++篇)
|
8月前
|
编译器 C++
类和对象(中 )C++
本文详细讲解了C++中的默认成员函数,包括构造函数、析构函数、拷贝构造函数、赋值运算符重载和取地址运算符重载等内容。重点分析了各函数的特点、使用场景及相互关系,如构造函数的主要任务是初始化对象,而非创建空间;析构函数用于清理资源;拷贝构造与赋值运算符的区别在于前者用于创建新对象,后者用于已存在的对象赋值。同时,文章还探讨了运算符重载的规则及其应用场景,并通过实例加深理解。最后强调,若类中存在资源管理,需显式定义拷贝构造和赋值运算符以避免浅拷贝问题。
|
8月前
|
存储 编译器 C++
类和对象(上)(C++)
本篇内容主要讲解了C++中类的相关知识,包括类的定义、实例化及this指针的作用。详细说明了类的定义格式、成员函数默认为inline、访问限定符(public、protected、private)的使用规则,以及class与struct的区别。同时分析了类实例化的概念,对象大小的计算规则和内存对齐原则。最后介绍了this指针的工作机制,解释了成员函数如何通过隐含的this指针区分不同对象的数据。这些知识点帮助我们更好地理解C++中类的封装性和对象的实现原理。
|
8月前
|
安全 C++
【c++】继承(继承的定义格式、赋值兼容转换、多继承、派生类默认成员函数规则、继承与友元、继承与静态成员)
本文深入探讨了C++中的继承机制,作为面向对象编程(OOP)的核心特性之一。继承通过允许派生类扩展基类的属性和方法,极大促进了代码复用,增强了代码的可维护性和可扩展性。文章详细介绍了继承的基本概念、定义格式、继承方式(public、protected、private)、赋值兼容转换、作用域问题、默认成员函数规则、继承与友元、静态成员、多继承及菱形继承问题,并对比了继承与组合的优缺点。最后总结指出,虽然继承提高了代码灵活性和复用率,但也带来了耦合度高的问题,建议在“has-a”和“is-a”关系同时存在时优先使用组合。
452 6