C++入门06 数据的共享与保护

news2024/11/16 3:38:02

 

图源:文心一言

听课笔记简单整理,供小伙伴们参考,内容包含“🐋5.2 变量的生存期与可见性、🐋5.5 静态成员与静态函数、🐋5.6 友元函数与友元类、🐋5.7 共享数据的保护 / const关键字、🐋5.10 多文件工程”~🥝🥝

  • 第1版:听课的记录代码~🧩🧩

编辑:梅头脑🌸

审核:文心一言


目录

🐳课程来源

🐳数据的共享与保护

🐋5.2 变量的生存期与可见性

🐋5.5 静态成员与静态函数

🐋5.6 友元函数与友元类

🐋5.7 共享数据的保护 / const关键字

🐋5.10 多文件工程

🔚结语


🐳课程来源

  • 郑莉老师的公开课:🌸C++语言程序设计基础 - 清华大学 - 学堂在线 (xuetangx.com)

🐳数据的共享与保护

🐋5.2 变量的生存期与可见性

⌨️算法代码

#include <iostream>
using namespace std;

int i = 1; // i 为全局变量,具有静态生存期。
void other() {
    static int a = 2;   // a 和 b 为静态变量,具有全局寿命,局部可见,只第一次进入函数被初始化
    static int b;
    int c = 10;         // c 为局部变量,具有动态生存期,每次进入函数时都初始化

    a += 2; i += 32; c += 5;

    cout << "---OTHER---\n";
    cout << "i:" << i << " a:" << a << " b:" << b << " c:" << c << endl;
    b = a;
}

int main()
{
    static int a;
    int b = -10;
    int c = 0;
    cout << "---MAIN---\n";
    cout << "i:" << i << " a:" << a << " b:" << b << " c:" << c << endl;
    c += 8;  other();
    cout << "---MAIN---\n";
    cout << "i:" << i << " a:" << a << " b:" << b << " c:" << c << endl;
    i += 10; other();
    return 0;
}

📇执行结果

​​

代码执行流程
iabc
main10-100
全局变量

 main局部静态变量

main局部变量main局部变量
other1 + 32 = 332 + 2 = 4010 + 5 = 15
全局变量other局部静态变量other局部静态变量other局部变量
main330-100 + 8 = 8
全局变量

 main局部静态变量

main局部变量main局部变量
other33 + 32 + 10 = 754 + 2 = 64 (上一轮a值)10 + 5 = 15
全局变量other局部静态变量other局部静态变量other局部变量

📇相关概念

局部变量、全局变量、静态变量、动态变量
作用域存储期存储位置未显示初始化

 局部变量

局部于定义它的函数或代码块。从变量进入作用域到作用域结束。通常存储在栈(stack)上。未定义。
全局变量整个程序。从程序开始执行到程序结束。通常存储在全局/静态存储区。

数据:0;

指针:空。

静态变量可以是局部的或全局的。从程序开始执行到程序结束。通常存储在全局/静态存储区。

数据:0;

指针:空。

动态变量程序员的显式管理。程序员的显式管理。通常存储在堆(heap)上。

malloc:未定义;

calloc数据:0;

calloc指针:空;

new 基本数据类型:未定义;

new 对象:默认构造函数。

  • 动态变量
    •  通常,“动态变量”这个词并不是用来描述变量作用域或存储期的标准术语。然而,在编程中,动态内存分配通常指的是使用malloccallocrealloc(在C中)或new(在C++中)等函数在堆(heap)上分配的内存。
    • 这种内存的生命周期不是由作用域或存储期决定的,而是由程序员的显式管理(如freedelete)决定的,程序员需要负责分配和释放这些变量所占用的内存。
    • 需要注意的是,动态分配的内存如果未被正确释放,会导致内存泄漏(memory leak),这是一种常见的编程错误。因此,在使用动态内存分配时,必须格外小心以确保内存的正确管理。

🐋5.5 静态成员与静态函数

🧩题目

构造点,并统计已构造的点个数。

📇算法思路

使用静态成员 count 统计已有点个数。

⌨️算法代码

#include <iostream>
using namespace std;

class Point {
public:
    Point(int x = 0, int y = 0) :x(x), y(y) {
        count++;            // 在构造函数中对count累加,所有对象共同维护同一个count
    }
    Point(Point& p) {
        x = p.x;  y = p.y; count++;
    }
    ~Point() { count--; }   // 每一个点消亡时,使用析构函数--,一般构造函数与析构函数执行的功能是相对的
    int getX() { return x; }
    int getY() { return y; }
    static void showCount() {    // 静态函数
        cout << "Object count = " << count << endl;
    }
private:
    int x, y;
    static int count;            // 静态成员
};
int Point::count = 0;

int main()
{
    Point::showCount();
    Point a(4, 5);
    cout << "Point A:" << a.getX() << "," << a.getY() << "\t";
    a.showCount(); // 或 Point::showCount();

    Point b(a);
    cout << "Point B:" << b.getX() << "," << b.getY() << "\t";
    b.showCount(); // 或 Point::showCount();
    return 0;
}

📇执行结果

📇代码解释

在创建Point的对象时,静态成员 count 的数量自增,相应的,调用析构函数时 count 的数量自减。

值得注意的是,如果showcount()是静态函数,它可以直接通过类名调用,而不需要类的实例。在编译时,编译器会为静态函数分配存储区,并且该函数可以在没有创建类实例的情况下执行。

相反,如果showcount()不是静态函数,那么它必须通过类的实例来调用。尝试在没有实例的情况下调用非静态函数(例如,当count = 0时,如果没有任何Point对象存在),编译器会报错,因为它违反了C++的调用规则。

然而,一旦创建对象后调用了非静态函数showcount(),无论count的值是多少(包括0),它都能够正确地显示count的值。

📇相关知识

静态函数和非静态函数
静态函数非静态函数
可见性和生命周期只在定义它的文件内部可见,不能被其他文件调用,且在程序运行期间一直存在,不会被释放。可以被当前文件以外的其他文件调用,具有全局可见性,且在被调用时动态地创建,在函数返回时被释放,生命周期相对较短。
内存分配在程序开始时分配内存,直到程序结束时才释放内存,函数调用期间不再进行内存分配。在每次被调用时都会从栈中分配内存,在调用结束后释放内存。
访问权限和对象状态只能访问静态成员变量或其他静态函数,不能直接访问类的非静态成员变量和非静态成员函数,因为它们没有与特定对象相关联。可以访问并修改对象的状态,包括非静态成员变量和非静态成员函数,具有访问特定对象的上下文。
调用方式可以通过类名直接调用,无需创建类的实例。必须通过类的实例来调用。
适用性

(1)限制函数可见性;

(2)节省内存空间。

(1)访问和修改对象状态的情况。

🐋5.6 友元函数与友元类

📇相关概念

友元函数(Friend Function)和友元类(Friend Class)是C++中提供的一种访问控制机制,允许特定的非成员函数或非同类的成员函数访问一个类的私有(private)和保护(protected)成员。

友元函数

友元函数是一个在类定义中被声明为 friend 的非成员函数。尽管它不是类的成员函数,但它被赋予了访问类私有和保护成员的权限。友元函数通常用于操作类的内部数据,而这些操作不适合作为类的成员函数来实现。

#include <iostream>
#include <cmath>
using namespace std;

class Point {
public:
    Point(int x = 0, int y = 0):x(x), y(y) {};
    int getX() { return x; }
    int getY() { return y; }
    friend float dist(Point& a, Point& b);  // 友元函数
private:
    int x, y;
};

float dist( Point& a, Point& b) {
    double x = a.x - b.x;
    double y = a.y - b.y;
    return static_cast<float>(sqrt(x * x + y * y));
}

int main()
{
    Point p1(1,1), p2(4,5);
    cout << "The distance is:";
    cout << dist(p1, p2) << endl;
    return 0;
}

友元类

友元类是指一个类被另一个类声明为 friend。当一个类被声明为另一个类的友元时,它的所有成员函数都可以访问另一个类的私有和保护成员。这种关系不是相互的,除非双方都明确声明对方为友元。

#include <iostream>
#include <cmath>
using namespace std;

class A {
    friend class B;     // 友元类
public:
    void display(){
        cout << x << endl;
    }
private:
    int x;
};

class B {
public:
    void set(int i);
    void display();
private:
    A a;
};

void B::set(int i) {
    a.x = i;  // 由于B是A的友元类,所以可以直接访问A的私有成员x  
}

void B::display() {
    a.display();  // 调用A类的display方法  
}

int main() {
    B b;  // 创建B类的实例  
    b.set(10);  // 调用B类的set方法,设置A类实例a的私有成员x为10  
    b.display();  // 调用B类的display方法,这将间接调用A类的display方法显示x的值  
    return 0;
}

注意:

  • 友元函数和友元类都提供了对类私有成员的访问权限。
  • 友元关系不是相互的,也不是继承的。如果一个类是另一个类的友元,这并不意味着反之亦然。
  • 友元关系破坏了封装性,应谨慎使用。它破坏了数据隐藏的原则,可能会导致代码维护困难和错误的出现。通常,应优先考虑使用类的成员函数和公共接口来实现功能,而不是直接使用友元函数或友元类。

🐋5.7 共享数据的保护 / const关键字

📇相关概念

常数据成员

常数据成员是指在类中声明的常量成员变量。它们必须在类构造函数的初始化列表中进行初始化,并且一旦初始化后,它们的值就不能再被修改

 
#include <iostream>
using namespace std;

class A {
public:
    A(int i);
    void print();
private:
    const int a;
    static const int b;
};

const int A::b = 10;    // 常数据成员,必须初始化
A::A(int i) : a(i) {}
void A::print() {
    cout << a << ":" << b << endl;
}

int main()
{
    A a1(100), a2(0);
    a1.print();
    a2.print();
    return 0;
}

常成员函数

常成员函数是指在函数声明后面添加了const关键字的成员函数。常成员函数不能修改它所属对象的任何数据成员(除了被声明为mutable的数据成员)。这样的函数可以在常对象上被调用(普通对象也可以被调用)

 
#include <iostream>
using namespace std;

class R {
public:
    R(int r1, int r2):r1(r1),r2(r2){}
    void print();
    void print() const;
private:
    int r1, r2;
};

void R::print() {
    cout << r1 << ":" << r2 << endl;
}
void R::print() const { // 常函数,编译器会特别审查是否改变了函数的状态;普通对象也可以调用常函数。
    cout << r1 << ":" << r2 << endl;
}

int main()
{
    R a(5, 4);
    a.print();
    const R b(20, 52);  // 常对象
    b.print();
    return 0;
}

常量表达式和constexpr

除了上面提到的常成员函数外,C++11还引入了constexpr成员函数,这些函数在编译时或运行时都能被计算,但它们在编译时必须产生常量表达式。然而,由于constexpr函数的限制,它们通常只用于非常简单的计算和操作。

constexpr int z = 5; // z是一个编译时常量
#include <iostream>  
  
class Point {  
public:  
    constexpr Point(int x, int y) : x_(x), y_(y) {}  
  
    constexpr int getX() const {  
        return x_;  
    }  
  
    constexpr int getY() const {  
        return y_;  
    }  
  
    constexpr int sum() const {  
        return x_ + y_;  
    }  
  
private:  
    int x_, y_;  
};  
  
int main() {  
    constexpr Point p(3, 4); // 在编译时构造Point对象  
    constexpr int sum = p.sum(); // 在编译时计算sum  
    std::cout << "Sum of coordinates: " << sum << std::endl; // 输出7  
  
    // 下面的代码在编译时会出错,因为p不是constexpr对象  
    // Point q(5, 6);  
    // constexpr int sum2 = q.sum(); // 错误:q不是constexpr对象  
  
    return 0;  
}

常引用作形参

常引用作形参意味着在函数内部不能通过这个引用来修改所引用的对象。这保证了函数不会意外地修改传递给它的参数。

 
#include <iostream>
#include <cmath>
using namespace std;

class Point {
public:
    Point(int x = 0, int y = 0):x(x), y(y){}
    int getX() { return x; }
    int getY() { return y; }
    friend float dist(const Point& p1, const Point& p2);    //  常引用,可以读取p1与p2,但是不能重新赋值
private:
    int x, y;
};

float dist(const Point& p1, const Point& p2) {
    double x = p1.x - p2.x;
    double y = p1.y - p2.y;
    return static_cast<float>(sqrt(x * x + y * y));
}

int main()
{
    const Point myp1(1, 1), myp2(4, 5);
    cout << "The distance is:";
    cout << dist(myp1, myp2) << endl;
    return 0;
}

函数返回常引用

确保函数返回的对象不会被修改。这通常用于返回类内部的私有成员变量,同时保证这些变量不会被外部修改。

class MyClass {  
private:  
    int value;  
public:  
    const int& getValue() const { // 返回常引用  
        return value;  
    }  
};

常指针

  • 指向常量的指针:指针指向的内容不能被修改。

    const int x = 10;       // 常量x  
    const int y = 20;       // 常量y  
    const int *p = &x;      // 指向常量的指针p,指向x  
      
    // 下面的赋值是合法的,因为改变的是指针p所指向的地址,而不是它所指向的内容  
    p = &y;                 // 现在p指向y  
      
    // 下面的赋值是不合法的,因为p指向的内容是常量,不能被修改  
    // *p = 30;             // 编译错误:表达式必须是可修改的左值

  • 常量指针:指针本身的值(即它所存储的地址)不能被修改,但它指向的内容可以修改。

  • 指向常量的常量指针:指针本身和它指向的内容都不能被修改。

    int a = 10;  
    int b = 20;  
      
    // 常量指针,指向的地址不能被修改,但指向的内容可以修改  
    int *const q = &a;  
    // q = &b;  // 编译错误:q是一个常量指针,它的值不能被修改  
    *q = 30;    // 合法:修改q所指向的内容  
      
    // 指向常量的常量指针,既不能修改指向的地址,也不能修改指向的内容  
    const int *const r = &a;  
    // r = &b;  // 编译错误:r是一个常量指针,它的值不能被修改  
    // *r = 40; // 编译错误:r指向的内容是常量,不能被修改

🐋5.10 多文件工程

🧩题目

构造点,并统计已构造的点个数,要求使用多文件完成。

📇算法思路

类的定义放入point 点中。

⌨️算法代码

Point.h

class Point {
public:
    Point(int x = 0, int y = 0) :x(x), y(y) {}
    Point(const Point& p);
    int getX() { return x; }
    int getY() { return y; }
    static void showCount();   // 静态函数成员
private:
    int x, y;
    static int count;   // 静态数据成员
};

main 

#include "Point.h"
#include <iostream>
using namespace std;

int Point::count = 0;

Point::Point(const Point& p) :x(p.x), y(p.y) {
    count++;
}

void Point::showCount() {
    cout << "Object count = " << count << endl;
}

int main()
{
    Point a(4, 5);
    cout << "Point A:" << a.getX() << "," << a.getY();
    Point::showCount();
    Point b(a);
    cout << "Point B:" << b.getX() << "," << b.getY();
    Point::showCount();
    return 0;
}

📇执行结果

📇相关概念

头文件#include "Point.h",用“”时编译器会优先搜寻在当前的源文件目录;

头文件#include <iostream>,用<>是编译器会搜索标准库的安装路径。


🔚结语

博文到此结束,写得模糊或者有误之处,期待小伙伴留言讨论与批评,督促博主优化内容{例如有错误、难理解、不简洁、缺功能}等,博主会顶锅前来修改~~😶‍🌫️😶‍🌫️

我是梅头脑,本片博文若有帮助,欢迎小伙伴动动可爱的小手默默给个赞支持一下,感谢点赞小伙伴对于博主的支持~~🌟🌟

同系列的博文:🌸数据结构_梅头脑_的博客-CSDN博客

同博主的博文:🌸随笔03 笔记整理-CSDN博客

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

使用Fragments(片段)提升你的Vue.js开发体验

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

JS进阶——深入对象

版权声明 本文章来源于B站上的某马课程&#xff0c;由本人整理&#xff0c;仅供学习交流使用。如涉及侵权问题&#xff0c;请立即与本人联系&#xff0c;本人将积极配合删除相关内容。感谢理解和支持&#xff0c;本人致力于维护原创作品的权益&#xff0c;共同营造一个尊重知识…

docker安装单机版canal和使用

说明&#xff1a;我安装的组件架构如下&#xff1a; 1、准备一台虚拟机&#xff0c;192.168.2.223&#xff0c;我安装的时候&#xff0c;docker只支持canal1.1.6版本&#xff0c;1.1.7无法使用docker安装.还有一点要补充&#xff0c;就是1.1.6好像不支持es8.0以上版本&#x…

基于springboot实现的牙科诊所系统

一、系统架构 前端&#xff1a;html | layui | js | css 后端&#xff1a;springboot | mybatis 环境&#xff1a;jdk1.8 | mysql | maven 二、 代码及数据库 三、功能介绍 01. web端-首页 02. web端-医生介绍 03. web端-新闻资讯 04. web端-关于我们 05. web…

mini-spring|关于Bean对象作用域以及FactoryBean的实现和使用

需求 FactoryBean 直接配置FactoryBean 获取FactoryBean中的Bean对象 FactoryBean的getObject方法通过反射获取Bean对象 由此省去对实体Dao类的定义 解决方法 对外提供一个可以二次从 FactoryBean 的 getObject 方法中获取对象的功能即可 整体架构 整个的实现过程包括了两部…

Spark Bloom Filter Join

1 综述 1.1 目的 Bloom Filter Join&#xff0c;或者说Row-level Runtime Filtering&#xff08;还额外有一条Semi-Join分支&#xff09;&#xff0c;是Spark 3.3对运行时过滤的一个最新补充   之前运行时过滤主要有两个&#xff1a;动态分区裁剪DPP&#xff08;开源实现&am…

electron-release-server部署electron自动更新服务器记录

目录 一、前言 环境 二、步骤 1、下载上传electron-release-server到服务器 2、宝塔新建node项目网站 3、安装依赖 ①npm install ②安装并配置postgres数据库 ③修改项目配置文件 ④启动项目 ⑤修改postgres的认证方式 ⑥Cannot find where you keep your Bower p…

Unity(第十四部)光照

原始的有默认灯光、除了默认的你还可以创建 1、定向光源&#xff08;类似太阳、从无限远的地方射向地面的光&#xff0c;光源位置并不影响照射角度等&#xff0c;不同方向的旋转影响角度和明亮&#xff09; 1. 颜色&#xff1a;调整光的颜色2. 模式&#xff1a;混合是实时加烘…

【InternLM 实战营笔记】LMDeploy 的量化和部署

环境配置 vgpu-smi 查看显卡资源使用情况 新开一个终端执行下面的命令实时观察 GPU 资源的使用情况。 watch vgpu-smi复制环境到我们自己的 conda 环境 /root/share/install_conda_env_internlm_base.sh lmdeploy激活环境 conda activate lmdeploy安装依赖库 # 解决 Modu…

SpringBoot项目中如何结合Mybatis进行数据库查询

在Spring Boot项目中使用Mybatis进行数据库操作是一种常见的实现方式。下面我将展示如何在Spring Boot项目中整合Mybatis。这个示例将包括几个主要部分&#xff1a;项目依赖配置、配置文件、实体类、Mapper接口及其XML配置文件、服务类、以及一个简单的控制器。 1. 项目依赖配…

MyBatis-Plus 快速入门

介绍 j​​​​​MyBatis-Plus (opens new window)&#xff08;简称 MP&#xff09;是一个 MyBatis (opens new window)的增强工具&#xff0c;在 MyBatis 的基础上只做增强不做改变&#xff0c;为简化开发、提高效率而生。 官网&#xff1a;MyBatis-Plus (baomidou.com) 1.…

sheng的学习笔记-卷积神经网络经典架构-LeNet-5、AlexNet、VGGNet-16

目录&#xff1a;目录 看本文章之前&#xff0c;需要学习卷积神经网络基础&#xff0c;可参考 sheng的学习笔记-卷积神经网络-CSDN博客 目录 LeNet-5 架构图 层级解析 1、输入层&#xff08;Input layer&#xff09; 2、卷积层C1&#xff08;Convolutional layer C1&…

2W字-35页PDF谈谈自己对QT某些知识点的理解

2W字-35页PDF谈谈自己对QT某些知识点的理解 前言与总结总体知识点的概况一些笔记的概况笔记阅读清单 前言与总结 最近&#xff0c;也在对自己以前做的项目做一个知识点的梳理&#xff0c;发现可能自己以前更多的是用某个控件&#xff0c;以及看官方手册&#xff0c;但是没有更…

卷积神经网络(CNN)原理与实现

卷积神经网络(CNN) 卷积神经网络原理卷积神经网络的数学推导卷积层反向传播算法数学推导卷积层实现代码 卷积神经网络(CNN) 卷积神经网络原理 卷积神经网络是一种用于图像、语音、自然语言等数据的深度学习模型&#xff0c;其核心思想是使用卷积操作提取输入数据的特征&…

tmux 工具常用命令

Tmux 是一个终端复用器&#xff08;terminal multiplexer&#xff09;&#xff0c;类似于 GNU screen 非常有用&#xff0c;属于常用的运维管理工具。 安装步骤 Ubuntu apt install tmux centos yum install tmux常用命令 以下所有快捷键&#xff0c;均是 ctrlb 按完之后先…

SpringBoot+Vue全栈开发-刘老师教编程(b站)(二)

创建SpringBoot项目 1.配置maven 出现bug java: 无法访问org.springframework.boot.SpringApplication 错误的类文件: /D:/maven/repository/org/springframework/boot/spring-boot/3.0.0/spring-boot-3.0.0.jar!/org/springframework/boot/SpringApplication.class 类…

react-router 源码之matchPath方法

1. 基础依赖path-to-regexp react-router提供了专门的路由匹配方法matchPath(位于packages/react-router/modules/matchPath.js)&#xff0c;该方法背后依赖的其实是path-to-regexp包。 path-to-regexp输入是路径字符串&#xff08;也就是Route中定义的path的值&#xff09;&…

AI:145-智能监控系统下的行人安全预警与法律合规分析

🚀点击这里跳转到本专栏,可查阅专栏顶置最新的指南宝典~ 🎉🎊🎉 你的技术旅程将在这里启航! 从基础到实践,深入学习。无论你是初学者还是经验丰富的老手,对于本专栏案例和项目实践都有参考学习意义。 ✨✨✨ 每一个案例都附带关键代码,详细讲解供大家学习,希望…

vscode 设置打开终端的默认工作目录/路径

vscode 设置打开终端的默认工作目录/路径** 文章目录 vscode 设置打开终端的默认工作目录/路径**打开vscode&#xff0c;打开设置UI 或是设置JSON文件&#xff0c;找到相关设置项方式1&#xff1a;通过打开settings.json的UI界面 设置:方式2&#xff1a;通过打开设置settings.j…

应急响应靶机训练-Web1【题解】

前言 接上文&#xff0c;应急响应靶机训练-Web1。 此文为应急响应靶机训练-Web1【题解】篇 解题过程 视频版&#xff1a; 另外&#xff0c;师傅们可以关注一下我们的bilibili&#xff0c;以后跟应急响应相关的靶机都会在bilibili发布一份视频 应急响应靶机训练-Web1【题解…