目录

  • 前言
  • 从结构体到类
  • 类的声明与使用
    • 基础声明
    • 继承声明
    • 数据与函数声明与调用
    • 声明
      • 调用
  • 类的访问修饰符
  • 类对象的内存分布
  • 类内数据相关
    • 静态变量
    • 非静态变量
  • 类成员函数相关
    • 普通成员函数
    • 友元函数
    • 构造与析构函数
      • 构造函数
      • 析构函数
    • 拷贝构造函数
      • 总结

前言

 面向对象编程有三大特性,分别是封装继承多态。这些特性在类的语法使用中都得到了充分的体现,我也预计写几篇文章来介绍一下C++的类语法。这是第一篇:类基础。
 类基础主要目的是介绍一下类的基础使用,重点在于数据函数的封装。

从结构体到类

 对于新手来说 这个概念可能比较陌生,但是提到结构体(struct)对于有C语言基础的人应该比较熟悉。在C++中结构体和类的底层实现几乎完全一致,结构体不仅可以封装函数,甚至还可以继承类。

#include <iostream>
#include <cstring>
using namespace std;    class Base_Class{
protected:int id; //占用大小为4字节的内存空间
public:Base_Class(int i=0){id = i;cout << "Base_Class constructor called." << endl;cout << "id = " << id << endl;}
};struct Derived_struct : public Base_Class
{Derived_struct(int i) : Base_Class(i) {cout << "Derived_Struct constructor called." << endl;cout << "id = " << id << endl;}
};int main() {Derived_struct obj(10);return 0;
}

输出结果为:

Base_Class constructor called.
id = 10
Derived_Struct constructor called.
id = 10

 是不是对类的感觉亲切了许多?当然在C语言中结构体里是不能封装函数的,也没有继承这个说法。数据和函数不封装到一起,那么当我处理数据的时候(比如结构体)就要去其它地方找函数(公共声明函数的地方),而不是通过对象直接可以调用处理的函数。
 打个比方,就像用勺子吃西瓜:C语言买了西瓜后要到厨房去找勺子才能吃西瓜,而C++封装的特性就是让买西瓜时,西瓜和勺子绑定在一起出售。因而C语言是面向过程编程的,C++是面向对象编程
 当然了,结构体和类底层实现相同,这不意味着在C++中结构体和类能混用。从规范上来说,结构体用于数据聚合,基本上不封装方法,就像C那样(例如坐标点封装,颜色封装)而类则用于‌对象进行封装‌,包括数据以及相关的方法

类的声明与使用

基础声明

类的基础语法声明如下:
来源菜鸟教程
举例说明:

class Box
{public:double length;   // 盒子的长度double width;  // 盒子的宽度double height;   // 盒子的高度int get_volume(){ //获得盒子的体积return length*width*height;}
};

 类里面就两样东西:数据+方法

继承声明

继承的语法如下:

class {derived_class} : {access_specifier} {base_class}

 其中,derived_class是派生类的名称,base_class是基类名称,二者之间通过: + 访问修饰符连接。访问修饰符 access-specifier 是 public、protected 或 private 其中的一个,如果未使用访问修饰符 access-specifier,则默认为 private继承。

// 基类
class Animal {
xxxx
};
//派生类
class Dog : public Animal {
xxxx
};

数据与函数声明与调用

声明

 类内变量与成员函数的声明语法类外一致,值得注意的是成员函数的具体实现可以放在类外。具体方法如下:

  1. 首先在类内声明这个函数:
class Dog {
public:void bark(); //类内声明
};
  1. 然后在类外以范围解析运算符(::) + 函数名的形式进行定义:
//类外实现
void Dog::bark(){cout << "汪汪汪" <<endl;
}

调用

 类的变量和函数的调用方法与结构体类似:

class Dog{
public: int age=10;//数据void bark() //方法{cout << "汪汪汪" <<endl;}
};
  1. 如果是类对象实例,使用.运算符访问成员变量和函数方法:
Dog wangcai;
wangcai.age; //访问变量
wangcai.bark(); //访问函数
  1. 如果是类指针,使用->运算符访问成员变量和函数方法:
Dog* wangcai;
wangcai->age; //访问变量
wangcai->bark(); //访问方法

类的访问修饰符

 访问修饰符用来控制外界对类内变量以及成员函数(类成员)的访问权限。关键字 public、private、protected 称为访问修饰符。

  1. public(公有)
     该成员变量/方法可以被外部的函数直接访问。这是权限最低的一种,类内、派生类以及类外都能访问往往用于对外接口上
  2. protected(保护)
     保护的类成员只能被类内以及派生类访问,类外无法访问。
  3. private(私有)
     如果没有声明访问权限,私有是默认的访问修饰符。是保护程度最高的一种,只能类内函数访问,派生类和类外都是不能访问的。往往只用于给内部成员函数数据交换

上面的内容总结来说就是:类内成员函数访问类内的变量/方法时没有任何限制派生类能访问public和protected类外的普通函数就只能访问public下的变量/方法(友元函数除外)

#include <iostream>
using namespace std;class Example {
public:    // 公有成员int publicVar;void publicMethod() {cout << "Public method" << endl;}private:   // 私有成员int privateVar;void privateMethod() {cout << "Private method" << endl;}protected: // 保护成员int protectedVar;void protectedMethod() {cout << "Protected method" << endl;}
};

类对象的内存分布

 首先要明确一下,类本身是一个抽象的概念,是不占用物理内存的。例如:

class Dog{
public: int age=10;//数据void bark() //方法{cout << "汪汪汪" <<endl;}
};

 如果不实例化这个类(Dog wangcai)是不会有内存占用的。提这个点是为了解答后面学习多态时,有的教材会说虚函数表存储在类里面,这样容易引起误解,类就像int 、float那样,如果不实例化是不会创造出内存空间的。

类对象实例的内存分布如下:

  1. 最开头: 虚函数表指针。
  2. 非静态成员变量。(按照声明顺序依次排布)

此外,

  • 对于静态成员变量:存储在全局数据区,不占用类实例内存空间。
  • 对于成员函数:代码存储在代码段,所有对象共享同一份函数代码。
  • 对于虚函数:虚函数实现也和成员函数一样存储在代码段。
  • 对于虚函数表:虚函数表在可执行文件中位于.rodata段(Linux/Unix)或.rdata段(Windows),程序加载后存于内存的‌常量区‌,具有只读属性。

类内数据相关

静态变量

 静态变量是类内以static为修饰符定义的变量。要点如下:

  1. 对于类中的静态变量来说,所有对象共享同一个静态变量,修改会影响所有实例。(即存储位置在全局数据区)
  2. 静态变量的生命周期‌在程序启动时初始化,程序结束时销毁。
  3. 静态变量在类内声明在类外定义
  4. 推荐使用类名访问(推荐):ClassName::staticVar

示例代码:

class Person {
public:static int count;  // 静态变量声明
};
int Person::count = 0;  // 必须在类外定义Person p1;
Person::count++;  // 推荐:通过类名访问
p1.count++;       // 不推荐:通过对象访问(合法但不清晰)

非静态变量

 又被称为实例变量,是指类内定义的普通成员变量。有以下特性:

  1. 每个对象独立存储‌,不同对象的实例变量互不影响。(即存储在实例的内存区域)
  2. 生命周期‌:随对象创建而分配,随对象销毁而释放。
  3. 必须通过对象实例进行访问。

举例:

class Person {
public:std::string name;  // 实例变量int age;           // 实例变量
};
Person p1;
p1.name = "Alice";  // 访问实例变量

类成员函数相关

普通成员函数

 要点:

  1. 普通成员函数暗含this指针,可以无需声明使用成员变量
  2. 成员函数的调用要通过对象实例进行,不能单独拎出来使用。

友元函数

 友元函数本质就是类外声明定义的函数,不与类对象绑定,即在类内用friend声明的外部函数,能突破封装性直接访问类的私有和保护成员,使其数据访问权限等同于成员函数(也就是说可以访问类的私有变量与函数)。
要点:

  1. 必须在类内使用friend修饰符进行声明。
  2. 具体的实现放在类外。

示例:

class MyClass {
private:int secret;
public:friend void peek(MyClass& obj); // 声明友元函数
};
void peek(MyClass& obj) { obj.secret = 42; } // 实现可访问私有成员

构造与析构函数

#include <iostream>
using namespace std;class MyClass {
public:int value;// 构造函数MyClass(int v) {value = v;cout << "构造函数被调用,value=" << value << endl;}// 析构函数~MyClass() {cout << "析构函数被调用,value=" << value << endl;}
};int main() {MyClass obj1(10);  // 构造函数调用{MyClass obj2(20);  // 构造函数调用}  // obj2离开作用域,析构函数调用return 0;
}  // obj1离开作用域,析构函数调用

构造函数

 构造函数是一种特殊的成员函数,有以下要点:

  1. 无任何返回值(连void也没有),可以传入参数
  2. 函数名与类名相同。
  3. 在类对象创建时执行一次,主要用来初始化类对象
  4. 不被继承,即子类的构造函数中必须要先手动调用父类的构造函数(无参构造时编译器会自动调用)。

 构造函数还有一个初始化列表的语法,可以用来初始化字段。例如上面的构造函数可以改为:

MyClass(int v):value(v){cout << "构造函数被调用,value=" << value << endl;
}

如果MyClass继承自BaseClass,初始化列表的语法也可以用来初始化构造函数,可以写成这样下面这样,以符合第四点的要求:

MyClass(int v):BaseClass(Base_args),value(v){cout << "构造函数被调用,value=" << value << endl;
};

(初始化列表时,用逗号,隔开各个参数)

析构函数

 析构函数与构造函数相呼应,就是类对象在销毁时自动调用的函数。(例如,程序结束时、局部对象离开作用域时)。要点如下:

  1. 无任何返回值(void也没有)以及不传入任何参数!
  2. 函数名为~+类名
  3. 对象销毁时自动执行,用来释放类对象手动创建的内存空间,避免内存泄漏
  4. 同样不被继承,但是自动调用(因为是无参的,编译器会自动调用)。顺序为先调用派生类的析构,再调用基类的析构

拷贝构造函数

 为什么需要额外的拷贝构造函数?其实想想就能清楚,一个类对象的创建只能有两种途径:

  1. 自定义。使用默认的构造函数进行初始化。
  2. 从其它对象中复制。这时就要调用拷贝构造函数而不是构造函数进行初始化了

why?拷贝构造函数必须要写吗?为什么之前写的类没有拷贝构造函数也能编译通过?
回答这个问题首先要了解一下浅拷贝和深拷贝。
 其实C++中数据无非被分为两种:普通变量和指针变量,对于浅拷贝来说,执行的就是简单的赋值操作,不区分指针和值,这样也就会造成一种情况:对象A里有个指针p,p指向某个地址。如果此时简单的使用Class B = A这样初始化对象B就是浅拷贝。那么对象B内的指针p的值和A当中指针p的值是一样的(浅拷贝只是简单赋值),这样就会造成对象A和B的指针p指向同一块内存空间,如果B中的p改动了指向的变量(*p),那么A中p指向的值也会随之改变,在某些情况下这是很危险的。(即浅拷贝会造成多个指针指向同一个内存地址的情况)。
 对于深拷贝来说,就会区分普通变量和指针变量。对于普通变量,如int,float等就直接把值复制过去就行了。对于指针变量则会为指针成员分配新内存并复制内容,但是这一过程必须要手动实现,这就依赖我们的拷贝构造函数了!
 下面从使用场合、语法规则两方面总结拷贝构造函数:

  1. 使用场合:需要以一个已经实例化的类对象为基础,创建一个新的类对象(所谓构造),并且类的成员变量中有指针类型时,需要自定义拷贝构造函数为指针成员分配新内存,并将值赋值到新内存空间中(所谓深拷贝)。
     若未显式定义拷贝构造函数,编译器会生成默认拷贝构造函数执行‌浅拷贝‌(逐成员复制值)。拷贝构造二者缺一不可,如果仅仅是普通的赋值,例如B = A,那么默认触发浅拷贝,此时如果需要深拷贝应该要在类内重载运算符=

  2. 语法规则:
    定义:

Class_name(const Class_name& obj)

 简单来说就是构造函数的基础上,固定传入的参数必须为同类对象的‌常量引用‌(ClassName(const ClassName& obj)),若使用值传递(ClassName(const ClassName obj))会导致无限递归调用。

使用:

MyClass obj2 = obj1; // 隐式调用,此时=表示初始化而非赋值
MyClass obj3(obj1); // 显式调用

示例:

class DeepString {
public:char* data;DeepString(const DeepString& other) {data = new char[strlen(other.data) + 1]; //重新分配地址strcpy(data, other.data);  // 复制内容而非指针}
};

总结

 拷贝和构造缺一不可:

  1. 少了拷贝就会导致多个指针指向同一个地址。
  2. 少了构造就是普通的赋值操作,是在已存在对象间进行赋值(a = b)不属于根据一个已有对对象初始化一个新对象。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/87538.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/87538.shtml
英文地址,请注明出处:http://en.pswp.cn/bicheng/87538.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

黑神话悟空游戏舆情分析

完整项目包点击文末名片 黑神话悟空上线初期舆情分析 背景 《黑神话&#xff1a;悟空》在上线首日便创下了全球游戏行业的多项新纪录&#xff0c;包括Steam同时在线人数超过222万&#xff0c;全渠道总销量超过450万份&#xff0c;总销售额超过15亿元。本项目旨在对 3A 游戏《黑…

python的or-tools算法踩坑

debug模式代码好的,然后正常运行不行(用的PyCharm) 不知道为什么debug模式这个可以的,但是正常模式不行 用or-tools算路径的时候 因为要多次到达同一个点,但是or-tools不支持,所以弄了虚拟点和真实点的距离是0,但是实际上如果虚拟点到真实点为0的话or-tools结果秒出,但是实…

docker-compose一键部署全栈项目。springboot后端,react前端

部署总览前端打包: 我们将配置 package.json&#xff0c;使用 npm run build (内部调用 vite build) 来打包。这个过程将完全在 Docker 构建镜像的过程中自动完成&#xff0c;你的主机上甚至不需要安装 Node.js。后端打包: 我们将配置 pom.xml&#xff0c;使用 mvn clean packa…

MCMC:高维概率采样的“随机游走”艺术

MCMC&#xff08;马尔可夫链蒙特卡洛&#xff09; 是一种从复杂概率分布中高效采样的核心算法&#xff0c;它解决了传统采样方法在高维空间中的“维度灾难”问题。以下是其技术本质、关键算法及实践的深度解析&#xff1a; 本文由「大千AI助手」原创发布&#xff0c;专注用真话…

HarmonyOS免密认证方案 助力应用登录安全升级

6月21日&#xff0c;2025年华为开发者大会"安全与隐私分论坛"在松山湖顺利举办。本论坛聚焦App治理与监管、星盾安全2.0的核心能力等进行深度分享与探讨。其中&#xff0c;HarmonyOS Passkey免密认证方案作为安全技术创新成果备受瞩目。该方案基于FIDO协议实现&#…

flutter flutter_vlc_player播放视频设置循环播放失效、初始化后获取不到视频宽高

插件&#xff1a;flutter_vlc_player: ^7.4.3 问题1&#xff1a;设置循环播放_controller.setLooping(true);无用。 解决方法&#xff1a; //vlcPlayer设置循环播放失效&#xff0c;以这种方式失效循环播放 _setLoopListener() async {if (_videoController!.value.hasError…

React与Vue的主要区别

React和Vue都是当今最流行、最强大的前端Javascript框架&#xff0c;它们都能构建出色的单页面应用。 以下是React和Vue的主要区别&#xff1a; React&#xff1a; React官方自称是一个用于构建用户界面的JavaScript库&#xff08;尤其是UI组件&#xff09;。它专注于视图层。…

浏览器原生控件上传PDF导致hash值不同

用户要求对上传的pdf计算hash排重&#xff0c;上线后发现排重失败 1、postman直接调用接口没有发现问题&#xff0c;每次获取的hash值是一样的 2、apifox网页版&#xff0c;调用接口发现问题&#xff0c;清除缓存后&#xff08;将选择的文件删除重新选择&#xff09;&#xf…

.net 的依赖注入

依赖注入(Dependency Injection,简称 DI)是一种软件设计模式,旨在将对象之间的依赖关系从代码内部解耦出来,通过外部提供的方式来建立依赖关系,从而提高软件的可维护性、可测试性和可扩展性。以下从概念、工作原理、常见类型、在不同框架中的应用等方面进行详细解释: 概…

【unitrix】 4.17 类型级别右移运算实现解析(shr.rs)

一、源码 这段代码实现了类型级别的右移运算&#xff08;>>&#xff09;&#xff0c;包括对整数和定点数的支持。 /*类型级别的右移运算实现* 编制人: $ource* 修改版次:0版完成版* 本版次创建时间: 2025年6月27日* 最后修改时间: 2025年7月2日 Sub1修改* 待完善问题&a…

Java 并发核心:AQS(AbstractQueuedSynchronizer) 详解

一、什么是 AQS&#xff1f; AbstractQueuedSynchronizer&#xff08;简称 AQS&#xff09;是 Java 并发包 java.util.concurrent.locks 中的一个核心同步框架&#xff0c;用于构建锁和同步器&#xff0c;如&#xff1a; ReentrantLockReentrantReadWriteLockCountDownLatchS…

LoRA 实战指南:NLP 与 CV 场景的高效微调方法全解析

大模型已成 AI 应用的“标配”&#xff0c;但高昂的训练和部署成本让很多企业望而却步。LoRA&#xff08;Low-Rank Adaptation&#xff09;作为一种轻量级微调方案&#xff0c;正成为 NLP 与 CV 场景中低成本定制的利器。本文详细通过详细介绍 LoRA 的核心原理、在文本与图像任…

centos指令

一、python 1、查询运行中脚本 ps aux | grep python2、关闭脚本进程 kill -15 <PID> # 优雅终止&#xff08;SIGTERM&#xff09; kill -9 <PID> # 强制终止&#xff08;SIGKILL&#xff09;

用c语言写一个找两张图片不同处的游戏

回答 思考完成 我们计划开发一个用C语言实现的查找两张图片不同之处的游戏。这个程序需要加载两张图片&#xff0c;然后允许用户通过点击等方式标记出差异的位置。我们将使用一个简单的图形库&#xff08;例如SDL&#xff09;来处理图像和用户交互。步骤&#xff1a;1.加载两…

【深度学习:进阶篇】--4.3.seq2seq与Attention机制

学习目标 目标 掌握seq2seq模型特点掌握集束搜索方式掌握BLEU评估方法掌握Attention机制 应用 应用Keras实现seq2seq对日期格式的翻译 目录 学习目标 1.seq2seq 1.1.定义 1.2.条件语言模型理解 1.3.应用场景 2.注意力机制 2.1.长句子问题 2.2.定义 2.3.公式 3.机器…

MYSQL与PostgreSQL的差异

一、架构设计的根本差异 进程模型 vs 线程模型 ​PostgreSQL​&#xff1a;采用多进程架构&#xff08;每个连接独立进程&#xff09;&#xff0c;通过共享内存通信。优势在于进程隔离性强&#xff0c;单连接崩溃不影响整体服务&#xff0c;但资源消耗较高。 ​MySQL​&…

Wpf布局之StackPanel!

文章目录 前言一、引言二、使用步骤 前言 Wpf布局之StackPanel&#xff01; 一、引言 StackPanel面板在水平或垂直的堆栈中放置元素。这个布局容器通常用于更大、更复杂窗口中的一些区域。 二、使用步骤 StackPanel默认是垂直堆叠 <Grid><StackPanel><Butt…

【MySQL】 内置函数

目录 1.时间函数2.字符串函数3.数学函数4.其他函数 1.时间函数 函数名称描述current_date()当前日期current_time()当前时间current_timestamp()当前时间戳date(datetime)返回datetime参数的日期部分date_add(date,interval d_value_type)在date中添加日期/时间&#xff0c;in…

【RK3568+PG2L50H开发板实验例程】Linux部分/FAN 检测案例

本原创文章由深圳市小眼睛科技有限公司创作&#xff0c;版权归本公司所有&#xff0c;如需转载&#xff0c;需授权并注明出处&#xff08;www.meyesemi.com) 1.案例简介 本案例旨在介绍如何测试开发板上风扇接口控制风扇启停与调速功能 2. FAN接口介绍 开发板上 FAN接口是一个…

Spring AI ETL Pipeline使用指南

前言&#xff08;Introduction&#xff09; 版本声明&#xff1a;本文基于 Spring AI 1.0.0 版本编写。由于 Spring AI 目前仍处于活跃开发阶段&#xff0c;API 和组件可能在后续版本中发生变化&#xff0c;请注意及时关注官方文档更新以保持兼容性。 在当今大数据和人工智能快…