C++初学者指南-3.自定义类型(第一部分)-基本自定义类型/类

news2024/11/26 3:20:57

C++初学者指南-3.自定义类型(第一部分)-基本自定义类型/类

文章目录

  • C++初学者指南-3.自定义类型(第一部分)-基本自定义类型/类
    • 1.类型种类(简单)
    • 2.为什么选择自定义类型?
      • 单向计数器
      • 提升序列
    • 3.限制成员访问
      • 成员函数
      • 公共(public) vs. 私有(private)的可见性
      • const成员函数
      • 成员声明 vs. 定义
      • 操作符成员函数
    • 3.初始化
      • 成员初始化
      • 构造函数
      • 默认构造函数与自定义构造函数
      • 显式构造函数 ↔ 隐式转换
      • 构造函数委托
      • 最令人困扰的解析
    • 4.设计,约定和风格
      • 接口中的数据类型
      • 成员和非成员
      • 避免使用Setter/Getter对!
      • 命名
      • 使用专用类型!
    • 5.示例实现
      • 示例1:单向的计数器
      • 示例2:递增序列

1.类型种类(简单)

基本类型void, bool, char, int, double, …
简单聚合主要目的:数据分组
聚合:可能包含一个或多个基本类型或其他兼容的聚合类型
无法控制组成类型的相互作用。
简单,如果只有(编译器生成)默认构造函数/析构函数/拷贝构造函数/赋值函数
标准的内存布局(所有成员按声明顺序连续排列),如果所有成员都具有相同的访问控制(例如全部是公共的)
更复杂自定义类型主要目的:确保正确性和安全性保证。
自定义不变量和对成员相互作用的控制。
限制成员访问。
成员函数
用户定义的构造函数/成员初始化。
用户自定义析构函数/拷贝构造函数/赋值函数。
可以是多态的(包含虚成员函数)。

2.为什么选择自定义类型?

正确性保证

  • 不变量 = 从不改变的行为和/或数据属性
  • 通过控制/限制对数据成员的访问来避免数据损坏
  • 使用专用类型来限制函数的输入/输出值

可复用的抽象

  • 隐藏低级实现细节的易于使用的接口
  • 不受内部实现变化影响的稳定接口
  • 可重用的常用功能的抽象(例如,动态数组)

资源管理
也被称为 RAII(资源获取即初始化)

  • 在构造对象时获取一些资源(内存、文件句柄、连接等)
  • 当对象被销毁时释放/清理资源(释放内存、关闭连接等)

单向计数器

  • 存储整数
  • 初始化为0
  • 不变性:计数只能增加(不能减少或重置)
monotonous_counter c;   
cout << c.reading();  // prints 0
c.increment();
cout << c.reading();  // prints 1
c.increment();
c.increment();
cout << c.reading();  // prints 3

简单的聚合类型不能保证:

struct frail_counter {
  int count;
};
frail_counter c;    
cout << c.count; // any value
c.count++;
c.count = 11;
  • 整数成员未自动初始化为 0
  • 可以自由修改聚合类型的任何整数成员
  • ⇒仅使用整数没有任何优势

提升序列

  • 应该存储整数
  • 不变性:元素数量只能增加,即只能插入新元素,而不能移除它们
  • 不变性:元素必须始终按升序排序
    在这里插入图片描述
    简单的聚合不能保证:
struct chaotic_sequence {
  std::vector<int> nums;
};
chaotic_sequence s;     
s.nums.push_back(8);   8
s.nums.push_back(1);   8  1
s.nums.push_back(4);   8  1  4
s.nums.pop_back(4);    8  1 

可能违反要求

  • 数字不一定按升序排序
  • 我们可以随意操作数字,比如删除数字等
  • 与使用普通std::vector相比没有什么优势

3.限制成员访问

成员函数

class monotonous_counter {
  int count_;  // ← 数据成员
…
  void increment () {  // ← 成员函数
    ++count_; 
  }
};
class ascending_sequence {
  std::vector<int> seq_;  // ← 数据成员
…
  void insert (int x) {   // ← 成员变量
    // 在正确的位置将 x 插入到 nums 中
  }
};

成员函数可用于

  • 操作或查询数据成员
  • 控制/限制对数据成员的访问
  • 隐藏低级实现细节
  • 确保正确性:保持/保证不变量
  • 确保清晰度:为不同类型的用户设计良好结构的接口
  • 确保稳定性:内部数据表示(大部分)独立于接口
  • 避免重复/模板:避免重复/样板:对于潜在的复杂操作,只需调用一次即可

公共(public) vs. 私有(private)的可见性

私有成员只能通过成员函数访问:

class ascending_sequence {
private:
  std::vector<int> seq_;
  // … more private members
public:
  void insert (int x) {}
  auto size () const { return seq_.size(); }
  // … more public members
};
int main () {
  ascending_sequence s;
  s.insert(8);         //  'insert' 是公共的
  auto n = s.size();   //  'size' 是公共的
  auto i = s.seq_[0];  //  编译错误: 'seq_' 是私有的
  auto m = s.seq_.size(); //  编译错误
  s.seq_.push_back(1);    //  编译错误
}

struct 与 class – 主要区别在于默认可见性:
在这里插入图片描述

关键字通常用于
struct公共数据的简单聚合
class私有数据、成员函数、不变量……

const成员函数

只有带 const 修饰的成员函数才能被 const 对象调用:

class ascending_sequence {
  std::vector<int> seq_;
public:void insert {}
  auto size () const { return seq_.size(); }
};
int main () {
  ascending_sequence s;
  s.insert(88);  //  s不是const的
  auto const& cs = s; 
  cs.insert(5);  //  编译错误: 'insert' 不是const的
}

接受常量(引用)参数的函数不仅承诺不修改它,这个承诺还将被编译器检查并强制执行。

void foo (ascending_sequence const& s) {
  // 's' is const reference ^^^^^
  auto n = s.size();  //  'size' 是 const
  s.insert(5);  //  编译错误: 'insert' 不是 const
}

const成员函数内部的成员是const

class monotonous_counter {
  int count_;
public:int reading () const { 
    //  编译错误: count_ 是 const:
    count_ += 2;
    return count_;
  }
};
class ascending_sequence {
  std::vector<int> seq_;
public:auto size () const {  // 'seq_' 是 const
    //  编译错误: 调用非const的'push_back'
    seq_.push_back(0);  

    //  vector的成员 'size()' 是const的
    return seq_.size();  
  }
};

成员函数可以通过const进行重载
如果一个成员函数是const-限定的,另一个不是,它们可以有相同的名称(和参数列表)。这样可以清楚地区分只读访问和读写操作。

class interpolation {int t_;public:// 读/写函数对:
  void threshold (int t)  { if (t > 0) t_ = t; }
  int  threshold () const { return t_; }
  // 可写访问一个'node'
  node& at (int x) {}
  // 只读访问一个'node'
  node const& at (int x) const {}
};

成员声明 vs. 定义

class MyType {
  int n_;
  // 更多的成员 …
public:
  // 声明 + 内联定义
  int count () const { return n_; } 
  // 只声明
  double foo (int, int);
};
// 独立定义
double MyType::foo (int x, int y) {
  // lots of stuff …
}
  • 通常复杂的成员函数的定义会放在类外面(放到单独的源文件中)。
  • 然而,像接口适配器函数、获取器(如 count)这样的小成员函数应该嵌入实现,即直接在类体中,以达到最佳性能。
  • 暂时我们会将所有成员函数保持内联,直到我们了解有关分离编译的知识。

操作符成员函数

特殊成员函数

class X { …
  Y operator [] (int i) { … } 
};

使用下标运算符。

X x;
Y y = x[0];

在这里插入图片描述

3.初始化

成员初始化

1.成员初始化器 (C++11)

class counter {
  // counter 应该从0开始
  int count_ = 0;
public:
  …
};
class Foo {
  int i_ = 10;
  double x_ = 3.14;
public:
  …
};

2.构造函数初始化列表
构造函数(ctor) = 创建对象时执行的特殊成员函数

class counter {
  int count_;
public:
  counter(): count_{0} { }
  …
};
class Foo {
  int i_;     // 1st
  double x_;  // 2nd
public:    
  Foo(): i_{10}, x_{3.14} { }
  // same order: i_ , x_ 
  …
};

提示:确保初始化列表中的成员顺序始终是与成员声明顺序相同!

构造函数

构造函数(ctor) = 创建对象时执行的特殊成员函数

  • 构造函数的 函数名称 = 类名称
  • 没有返回类型
  • 可以通过初始化列表初始化数据成员
  • 可以在第一次使用对象之前执行代码
  • 可用于建立不变量
  • 默认构造函数 = 不带参数的构造函数
    在这里插入图片描述
    构造函数的独立定义
    与其他成员函数的方式相同
class MyType { …
public:
  MyType ();  // 声明
  …
};
// 独立定义
MyType::MyType (): … { … }

注意:确保初始化列表中的成员顺序始终是 与成员声明顺序相同!

  • 初始化列表中的不同顺序可能会导致未定义的行为,例如访问未初始化的内存。
  • 这里,在默认构造函数中,我们需要确保只有在min_和max_被初始化之后才能访问v_{min_,max_}。
  • 有些编译器会对此发出警告:例如 g++/clang++ 使用 -Wall 或 -Wreorder 选项,这就是为什么要始终启用并且决不忽略编译器警告的另一个原因!

默认构造函数与自定义构造函数

没有用户定义的构造函数⇒编译器生成一个

class BoringType { public: int i = 0; };
BoringType obj1;     // 正确
BoringType obj2 {};  // 正确

至少有一个特殊构造函数
⇒ 编译器不生成默认构造函数

class SomeType {public:
  // special constructor:
  explicit SomeType (int x){}
};
SomeType s1 {1};  //  特殊 (int) 构造函数
SomeType s2;      //  编译错误: 没有默认构造函数!
SomeType s3 {};   //  编译错误: 没有默认构造函数!

TypeName() = default;
⇒ 编译器生成默认构造函数的实现(编译器实现没有参数的构造函数就是默认构造函数)

显式构造函数 ↔ 隐式转换

// 函数有一个 'Counter' 参数
void foo (Counter c) { … }
void bar (Counter const& c) { … }

隐式转换(不好的方式)

class Counter {
  int count_ = 0;
public:

  Counter (int initial):
    count_{initial} {}};
// 从‘2‘创建了'Counter'对象
foo(2);           // 正确
bar(2);           // 正确
foo(Counter{2});  // 正确
bar(Counter{2});  // 正确

显式构造函数(推荐的方式)

class Counter {
  int count_ = 0;
public:
  explicit
  Counter (int initial):
    count_{initial} {}};
// 没有隐式转换: 
foo(2);  //  编译错误
bar(2);  //  编译错误
foo(Counter{2});  // 正确
bar(Counter{2});  // 正确

注意:默认情况下,让用户定义的构造函数显式!

  • 隐式转换是难以发现的错误的主要来源!
  • 只有在绝对必要且含义明确时,才使用非显式构造函数,如果需要直接从参数类型进行转换。
  • 一些较老的教材和使用 C++98 的人可能会告诉你,只需要关心单参数构造函数的隐式转换。然而自C++11以来,情况已经改变,因为现在你也可以从花括号括起的值列表中隐式地构造对象。

构造函数委托

= 调用初始化列表中的其他构造函数

class Range {
  int a_;
  int b_;
public:
  // 1) 特殊构造函数
  explicit Range (int a, int b): a_{a}, b_{b} {
    if (b_ > a_) std::swap(a_,b_);
  }
  // 2) 特殊[a,a]构造 - 委托给[a,b]构造函数
  explicit Range (int a): Range{a,a} {}
  // 3) default constructor - delegates to [a,a] ctor
  Range (): Range{0} {}};
Range r1;        // 3) ⇒ r1.a_: 0  r1.b_: 0
Range r2 {3};    // 2) ⇒ r2.a_: 3  r2.b_: 3
Range r3 {4,9};  // 1) ⇒ r3.a_: 4  r3.b_: 9
Range r4 {8,2};  // 1) ⇒ r4.a_: 2  r4.b_: 8

最令人困扰的解析

由于C++语法中的歧义,无法使用空括号进行对象构造:

class A { … };
A a ();  // 声明了没有参数和返回值的函数'a'
A a;     // 构造一个A类型对象
A a {};  // 构造一个A类型对象

4.设计,约定和风格

每种类型都应该有一个目的

  • 因为这样可以减少将来对它的修改可能性。
  • 降低出现新错误的风险
  • 根据您的类型保持代码更加稳定

保持数据成员私有并使用成员函数访问/修改数据

  • 这样用户只能通过稳定的接口与您的类型进行交互。
  • 避免数据损坏 / 允许不变量保证。
  • 如果你改变了类型的内部实现,类型的用户不需要改变他们的代码。

const - 限定所有非修改成员函数

  • 为了清楚地表明对象的内部状态如何以及何时发生改变。
  • 使您更难错误地使用您的类型。
  • 启用编译器可变性检查。
  • 更好地推理正确性,特别是在涉及同时访问对象的情况下,例如来自多个线程。
接口应该易于正确使用,并且难以错误使用。
 —  Scott Meyers

函数或类型的用户不应该对其目的、参数的意义、先决条件/后置条件和副作用感到困惑。

接口中的数据类型

#include <cstdint>
#include <numeric_limits>
class monotonous_counter {
public:
  // 公共类型别名
  using value_type = std::uint64_t;
private:
  value_type count_ = 0;
public:
  value_type reading () const { return count_; }
  …
};
const auto max = std::numeric_limits<monotonous_counter::value_type>::max();

不要泄露实现细节:

  • 只有当别名类型在您的类的公共接口中使用时,即作为公共成员函数的返回类型或参数时,才将类型别名公开。
  • 如果别名类型只在私有成员函数中使用或用于私有数据成员,请不要将类型别名公开。

成员和非成员

如何实现一个特性/添加新功能?

  • 只需要访问公共数据(例如通过成员函数访问)⇒ 实现为独立函数
  • 需要访问私有数据⇒作为成员函数实现

示例:间隔类型 gap类
如何实现一个函数,使新的间隔对象的两个边界都移动相同的量?

class gap {
  int a_; 
  int b_;
public:
  explicit gap (int a, int b): a_{a}, b_{b} {}
  int a () const { return a_; }
  int b () const { return b_; }
};

推荐的独立式函数实现

gap shifted (gap const& g, int x) {
  return gap{g.a()+x, g.b()+x};
}
  • 实现仅依赖于gap的公共接口
  • 我们没有更改类型 gap 本身 ⇒ 依赖它的其他代码不需要重新编译

不推荐的成员函数实现

class gap { 
  …
  gap shifted (int x) const {
    return gap{a_+x, b_+x};
  }
};
  • gap的其他用户可能想要一个具有不同语义的移位函数,但他们现在只能使用我们的函数了。
  • 所有其他代码(取决于 gap)都需要重新编译。

避免使用Setter/Getter对!

  • 使用动作/动词函数而不是仅仅使用设置器(Setter)。
  • 通常可以更好地对问题进行建模。
  • 更精细的控制。
  • 更好的代码可读性/意图表达。

推荐的描述性操作:

class Account { …
  void deposit (Money const&);
  Money try_withdraw (Money const&);
  Money const& balance () const;
};

不推荐的Setter/Getter对:

class Account { …
  void set_balance (Money const&);
 
  Money const& balance () const;
};

命名

名称应反映类型/函数的用途
推荐的:可理解的

class IPv6_Address {…};
class ThreadPool {…};
class cuboid {…};
double volume (cuboid const&) {…}

不推荐的:太笼统了

class Manager {…};
class Starter {…};
class Pool {…};
int get_number (Pool const&) {…}

不要在类型、变量、函数、私有数据成员等名称中使用前导下划线或双下划线!

  • 以下划线开头和/或包含双下划线的名称是保留给标准库和/或编译器生成的实体的。
  • 使用具有前置下划线或双下划线的名称可能会引发未定义行为!
  • 一个常见且没有问题的约定是在私有数据成员后面加下划线。
    在这里插入图片描述

使用专用类型!

  • 限制输入参数值
  • 确保中间结果的有效性
  • 保证返回值有效性

⇒编译器作为正确性检查器,如果它能编译通过,它应该是正确的

// 明确的接口:
double volume (Cuboid const&);
// 输入保证:角度以弧度为单位
Square make_rotated (Square const&, Radians angle);
// 只接受有效数量(例如:> 0)
Gadget duplicate (Gadget const& original,  Quantity times);
// 结果保证:向量已被规范化。
UnitVector3d dominant_direction (WindField const&);
//避免混淆,使用一个好的单位库。
si::kg mass (EllipsoidShell const&, si::g_cm3 density);
bool has_cycles (DirectedGraph const&);
// 易于理解的控制流程和逻辑:
Taxon species1 = classify(image1);
Taxon species2 = classify(image2);
Taxon lca = taxonomy.lowest_common_ancestor(species1, species2);

5.示例实现

示例1:单向的计数器

  • 新计数器从 0 开始
  • 只能往上数,不能往下数。
  • 对当前计数值的只读访问
#include <iostream>   // std::cout
#include <cstdint>    // std::uint64_t
class monotonous_counter {
public:
  using value_type = std::uint64_t;
private:
  value_type count_ = 0;  // initial
public:
  monotonous_counter () = default;
  explicit monotonous_counter (value_type init) noexcept: count_{init} {}
  void increment () noexcept { ++count_; }
  [[nodiscard]] value_type reading () const noexcept { return count_; }
};
int main () {
  monotonous_counter c;
  c.increment();
  std::cout << c.reading();  // prints 1
  c.increment();
  c.increment();
  std::cout << c.reading();  // prints 3
}

运行示例

示例2:递增序列

  • 存储整数
  • 通过索引对存储元素进行只读访问
  • 只能插入新元素,但不能删除它们
  • 元素始终按升序排序
  • 只能通过公共接口修改内容

‘insert’ 操作的实现以及 ‘begin’ 和 ‘end’ 成员函数的作用在我们学习了迭代器和标准库中的算法后会变得更加清晰。

#include <iostream>   // std::cout
#include <vector>     // std::vector
#include <algorithm>  // std::lower_bound
class ascending_sequence {
public:
  using value_type = int;
private:
  using storage_t = std::vector<value_type>;
  storage_t seq_;
public:
  using size_type = storage_t::size_type;
  void insert (value_type x) {
    // use binary search to find insert position
    seq_.insert(std::lower_bound(seq_.begin(), seq_.end(), x), x);
  }
  [[nodiscard]] value_type operator [] (size_type idx) const noexcept { 
    return seq_[idx]; }
  [[nodiscard]] size_type size () const noexcept { return seq_.size(); }
  // enable range based iteration
  [[nodiscard]] auto begin () const noexcept { return seq_.begin(); }
  [[nodiscard]] auto end ()   const noexcept { return seq_.end(); }
};
int main () {
  ascending_sequence s;  // s.seq_:  
  s.insert(7);           // s.seq_: 7
  s.insert(2);           // s.seq_: 27
  s.insert(4);           // s.seq_: 247
  s.insert(9);           // s.seq_: 2479
  s.insert(5);           // s.seq_: 24579
  std::cout << s[3];     // prints 7
  for (auto x : s) {
    std::cout << x <<' ';  // 2 4 5 7 9
  }
  // use type aliases
  ascending_sequence::value_type x = 1;
  ascending_sequence::size_type  n = 2;
}

运行示例

附上原文地址
如果文章对您有用,请随手点个赞,谢谢!^_^

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

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

相关文章

软考满分范文“论模型驱动架构设计方法及其应用”,软考高级,系统架构设计师

论文真题 模型驱动架构设计是一种用于应用系统开发的软件设计方法,以模型构造、模型转换和精化为核心,提供了一套软件设计的指导规范。在模型驱动架构环境下,通过创建出机器可读和高度抽象的模型实现对不同问题域的描述,这些模型独立于实现技术,以标准化的方式储存,利用…

北京高校大学智能制造实验室数字孪生可视化系统平台建设项目验收

在北京高校大学智能制造实验室&#xff0c;一项具有划时代意义的数字孪生可视化系统平台建设项目近日顺利完成了验收工作。这一项目的成功实施&#xff0c;不仅标志着高校智能制造领域教学与研究步入了全新的数字化时代&#xff0c;更为未来制造业的智能化、信息化发展奠定了坚…

【Android源码】编译源码,错误解决

Android源码需求 定制Android系统将最新版本的Android系统刷入到自己的Android设备中将整个系统源码导入到AndroidStudiozhong动态调试Android系统源码 命令介绍 cd aosp source build/envsetup.sh lunch lunch aosp_x86_64-eng make -j16 2>&1 | tee build.log版本介…

HarmonyOS--路由管理--组件导航 (Navigation)

文档中心 什么是组件导航 (Navigation) &#xff1f; 1、Navigation是路由容器组件&#xff0c;一般作为首页的根容器&#xff0c;包括单栏(Stack)、分栏(Split)和自适应(Auto)三种显示模式 2、Navigation组件适用于模块内和跨模块的路由切换&#xff0c;一次开发&#xff0…

机器学习原理之 -- 朴素贝叶斯分类器:由来及原理详解

朴素贝叶斯&#xff08;Naive Bayes&#xff09;分类器是一类基于贝叶斯定理&#xff08;Bayes Theorem&#xff09;的简单而有效的概率分类算法。由于其假设特征之间的条件独立性&#xff0c;因此被称为“朴素”贝叶斯分类器。尽管这种独立性假设在现实中很少完全成立&#xf…

基于PHP技术的校园论坛设计的设计与实现-计算机毕业设计源码08586

摘 要 本项目旨在基于PHP技术设计与实现一个校园论坛系统&#xff0c;以提供一个功能丰富、用户友好的交流平台。该论坛系统将包括用户注册与登录、帖子发布与回复、个人信息管理等基本功能&#xff0c;并结合社交化特点&#xff0c;增强用户之间的互动性。通过利用PHP语言及其…

Pytorch实战(一):LeNet神经网络

文章目录 一、模型实现1.1数据集的下载1.2加载数据集1.3模型训练1.4模型预测 LeNet神经网络是第一个卷积神经网络&#xff08;CNN&#xff09;&#xff0c;首次采用了卷积层、池化层这两个全新的神经网络组件&#xff0c;接收灰度图像&#xff0c;并输出其中包含的手写数字&…

【吊打面试官系列-MyBatis面试题】#{}和${}的区别是什么?

大家好&#xff0c;我是锋哥。今天分享关于 【#{}和${}的区别是什么&#xff1f;】面试题&#xff0c;希望对大家有帮助&#xff1b; #{}和${}的区别是什么&#xff1f; #{} 是预编译处理&#xff0c;${}是字符串替换。 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网…

对话贾扬清:我创业这一年所看到的 AI

引言 在这次对话中&#xff0c;前阿里巴巴人工智能专家、现LIBRINAI创始人贾扬清分享了他在AI领域创业一年的见解和经历。作为一位从科学家转型为CEO的创业者&#xff0c;他探讨了AI计算、异构计算和云原生软件的结合带来的革命性变化&#xff0c;并讨论了LIBRINAI如何在激烈的…

Redis 集群模式

一、集群模式概述 Redis 中哨兵模式虽然提高了系统的可用性&#xff0c;但是真正存储数据的还是主节点和从节点&#xff0c;并且每个节点都存储了全量的数据&#xff0c;此时&#xff0c;如果数据量过大&#xff0c;接近或超出了 主节点 / 从节点机器的物理内存&#xff0c;就…

无人机远程控制:北斗短报文技术详解

无人机&#xff08;UAV&#xff09;技术的快速发展和应用&#xff0c;使得远程控制成为了一项关键技术。无人机远程控制涉及无线通信、数据处理等多个方面&#xff0c;其中北斗短报文技术以其独特的优势&#xff0c;在无人机远程控制领域发挥着重要作用。本文将详细解析无人机远…

【SQL】已解决:MySQL 服务无法启动

文章目录 一、分析问题背景二、可能出错的原因三、错误代码示例四、正确代码示例五、注意事项 已解决&#xff1a;MySQL 服务无法启动 一、分析问题背景 MySQL是一种流行的开源关系型数据库管理系统&#xff0c;在许多应用中被广泛使用。有时在启动MySQL服务时&#xff0c;可…

Spring Boot集成jasypt快速入门Demo

1.什么是Jasypt&#xff1f; Jasypt&#xff08;Java Simplified Encryption&#xff09;是一个专注于简化Java加密操作的工具。 它提供了一种简单而强大的方式来处理数据的加密和解密&#xff0c;使开发者能够轻松地保护应用程序中的敏感信息&#xff0c;如数据库密码、API密…

PHP校园论坛-计算机毕业设计源码08586

摘 要 本项目旨在基于PHP技术设计与实现一个校园论坛系统&#xff0c;以提供一个功能丰富、用户友好的交流平台。该论坛系统将包括用户注册与登录、帖子发布与回复、个人信息管理等基本功能&#xff0c;并结合社交化特点&#xff0c;增强用户之间的互动性。通过利用PHP语言及其…

【D3.js in Action 3 精译】1.2.2 可缩放矢量图形(二)

当前内容所在位置 第一部分 D3.js 基础知识 第一章 D3.js 简介 1.1 何为 D3.js&#xff1f;1.2 D3 生态系统——入门须知 1.2.1 HTML 与 DOM1.2.2 SVG - 可缩放矢量图形 ✔️ 第一部分【第二部分】✔️第三部分&#xff08;精译中 ⏳&#xff09; 1.2.3 Canvas 与 WebGL&#x…

Linux多进程和多线程(一)

进程 进程的概念 进程&#xff08;Process&#xff09;是操作系统对一个正在运行的程序的一种抽象。它是系统运行程序的最小单位&#xff0c;是资源分配和调度的基本单位。 进程的特点如下 进程是⼀个独⽴的可调度的活动, 由操作系统进⾏统⼀调度, 相应的任务会被调度到cpu …

【鸿蒙学习笔记】尺寸设置

官方文档&#xff1a;尺寸设置 目录标题 width&#xff1a;设置组件自身的宽度&#xff0c;缺省时自适应height&#xff1a;设置组件自身的高度&#xff0c;缺省时自适应size&#xff1a;设置高宽尺寸。margin&#xff1a;设置组件的外边距padding&#xff1a;设置组件的内边距…

数据库-数据完整性-用户自定义完整性实验

NULL/NOT NULL 约束&#xff1a; 在每个字段后面可以加上 NULL 修饰符来指定该字段是否可以为空&#xff1b;或者加上 NOT NULL 修饰符来指定该字段必须填上数据。 DEFAULT约束说明 DEFAULT 约束用于向列中插入默认值。如果列中没有规定其他的值&#xff0c;那么会将默认值添加…

electron线上跨域问题

一、配置background.js win new BrowserWindow({webPreferences: {nodeIntegration: true, // 使渲染进程拥有node环境//关闭web权限检查&#xff0c;允许跨域webSecurity: false,// Use pluginOptions.nodeIntegration, leave this alone// See nklayman.github.io/vue-cli-p…

【计算机网络】HTTP——基于HTTP的功能追加协议(个人笔记)

学习日期&#xff1a;2024.6.29 内容摘要&#xff1a;基于HTTP的功能追加协议和HTTP/2.0 HTTP的瓶颈与各功能追加协议 需求的产生 在Facebook、推特、微博等平台&#xff0c;每分每秒都会有人更新内容&#xff0c;我们作为用户当然希望时刻都能收到最新的消息&#xff0c;为…