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

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.mfbz.cn/a/772416.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

firewalld(7)NAT、端口转发

简介 在前面的文章中已经介绍了firewalld了zone、rich rule等规则设置&#xff0c;并且在iptables的文章中我们介绍了网络防火墙、还有iptables的target,包括SNAT、DNAT、MASQUERADE、REDIRECT的原理和配置。那么在这篇文章中&#xff0c;将继续介绍在firewalld中的NAT的相关配…

cpp随笔——如何实现一个简单的进程心跳功能

什么是进程的心跳 在我们日常后台服务程序运行中,一般是调度模块&#xff0c;进程心跳以及进程监控共同工作&#xff0c;进而实现实现服务的稳定运行,在前面我们介绍过如何去实现一个简单的调度模块,而今天我们所要介绍的就是如何实现进程的心跳&#xff0c;首先什么是进程的心…

MCU中如何利用串口通信,增加AT指令框架

第一步&#xff0c;通过串口与PC端建立通信第二步&#xff0c;根据PC端发来的AT指令&#xff0c;MCU执行相应代码 主要是解析PC端发来的字符串&#xff0c;也就是获取字符串、处理字符串、以及分析字符串。 1. 串口通信 用到的是DMA串口通信&#xff0c;收发字符串数据时&…

什么是JavaScript中的箭头函数(arrow functions)?

聚沙成塔每天进步一点点 本文回顾 ⭐ 专栏简介什么是JavaScript中的箭头函数&#xff08;arrow functions&#xff09;&#xff1f;1. 引言2. 箭头函数的语法2.1 基本语法2.2 示例 3. 箭头函数的特点3.1 简洁的语法3.2 没有this绑定3.3 不能用作构造函数3.4 没有arguments对象3…

基于SpringBoot的就业信息管理系统

你好&#xff0c;我是计算机学姐码农小野&#xff01;如果你对就业信息管理系统感兴趣或有相关需求&#xff0c;欢迎私信联系我。 开发语言&#xff1a; Java 数据库&#xff1a; MySQL 技术&#xff1a; SpringBootMySql 工具&#xff1a; MyEclipse、Tomcat 系统展示…

隐私信息管理体系认证:守护个人信息,筑牢隐私防线

在数字化浪潮汹涌的当下&#xff0c;个人信息安全问题愈发凸显其重要性。随着互联网技术的飞速发展&#xff0c;我们的隐私信息如同裸露在阳光下的沙滩&#xff0c;稍有不慎就可能被不法分子窃取或滥用。因此&#xff0c;构建一个完善的隐私信息管理体系&#xff0c;成为了保障…

结合数据索引结构看SQL的真实执行过程

引言 关于数据库设计与优化的前几篇文章中&#xff0c;我们提到了数据库设计优化应该遵守的指导原则、数据库底层的索引组织结构、数据库的核心功能组件以及SQL的解析、编译等。这些其实都是在为SQL的优化、执行的理解打基础。 今天这篇文章&#xff0c;我们以MySQL中InnoDB存…

软件测评机构:关于软件验收测试作用与实施步骤全解析

软件验收测试是指在软件项目交付给用户之前进行的一系列测试活动&#xff0c;其主要目的是验证软件是否符合用户需求和设计规范&#xff0c;以确保软件的质量和稳定性。 软件验收测试在软件开发生命周期的最后阶段进行&#xff0c;起到了至关重要的作用。它能够帮助客户确认软…

AI PC(智能电脑)技术分析

一文看懂AI PC&#xff08;智能电脑&#xff09; 2024年&#xff0c;英特尔、英伟达等芯片巨头革新CPU技术&#xff0c;融入AI算力&#xff0c;为传统PC带来质的飞跃&#xff0c;引领智能计算新时代。 2024年&#xff0c;因此被叫作人工智能电脑&#xff08;AI PC&#xff09;…

【elementui】记录解决el-tree开启show-checkbox后,勾选一个叶结点后会自动折叠的现象

第一种解决方案&#xff1a;设置default-expand-keys的值为当前选中的key值即可 <el-treeref"tree"class"checkboxSelect-wrap":data"treeData"show-checkboxnode-key"id":expand-on-click-node"true":props"defau…

MATLAB——循环语句

一、for end语句 在该语法中&#xff0c;循环变量是用于迭代的变量名&#xff0c;它会在每次循环迭代中从向量或矩阵中取出一列的值。数值向量或者矩阵则表示了循环变量可以取值的范围&#xff0c;通常根据实际需要事先给定。一旦循环变量遍历完数值向量或者矩阵中的所有值&…

初试成绩占比百分之70!计算机专硕均分340+!华中师范大学计算机考研考情分析!

华中师范大学&#xff08;Central China Normal University&#xff09;简称“华中师大”或“华大”&#xff0c;位于湖北省会武汉&#xff0c;是中华人民共和国教育部直属重点综合性师范大学&#xff0c;国家“211工程”、“985工程优势学科创新平台”重点建设院校&#xff0c…

苹果公司的Wifi定位服务(WPS)存在被滥用的风险

安全博客 Krebs on Security 2024年5月21日发布博文&#xff0c;表示苹果公司的定位服务存在被滥用风险&#xff0c;通过 "窃取"WPS 数据库&#xff0c;可以定位部队行踪。 相关背景知识 手机定位固然主要依赖卫星定位&#xff0c;不过在城市地区&#xff0c;密集的…

YOLOv10全网最新创新点改进系列:融合GSConv+Slim Neck,双改进、双增强,替换特征融合层实现, 轻量化涨点改进策略,有效涨点神器!

YOLOv10全网最新创新点改进系列&#xff1a;融合GSConvSlim Neck&#xff0c;双改进、双增强&#xff0c;替换特征融合层实现&#xff0c; 轻量化涨点改进策略&#xff0c;有效涨点神器&#xff01; 所有改进代码均经过实验测试跑通&#xff01;截止发稿时YOLOv10已改进40&…

vue中的坑·

常规 1.使用watch时&#xff0c;immediate true会在dom挂载前执行 2.使用this.$attrs和props 可以获取上层非原生属性&#xff08;class/id&#xff09; 多层次嵌套引用 设置的时候直接赋值&#xff0c;修改的时候即使用的双向绑定加上$set / nextick / fouceUpdate都不会同步…

MySQL表的练习

二、创建表 1、创建一个名称为db_system的数据库 create database db_system; 2、在该数据库下创建两张表&#xff0c;具体要求如下 员工表 user 字段 类型 约束 备注 id 整形 主键&#xff0c;自增长 id N…

探索设计的未来:了解设计师对生成式人工智能(AIGC)工具的采用

在数字化浪潮的推动下&#xff0c;设计行业正经历着一场革命性的变革。随着生成式人工智能&#xff08;AIGC&#xff09;技术的发展&#xff0c;设计师们迎来了前所未有的机遇与挑战。这些工具不仅重塑了传统的设计流程&#xff0c;还为设计师们提供了更广阔的创意空间和更高效…

vue模板语法v-html

模板语法v-html vue使用一种基于HTML的模板语法&#xff0c;使我们能够声明式的将其组件实例的数据绑定到呈现的DOM上&#xff0c;所有的vue模板都是语法层面的HTML&#xff0c;可以被符合规范的浏览器和HTML解释器解析。 一.文本插值 最基本的数据绑定形式是文本插值&#…

理解神经网络的通道数

理解神经网络的通道数 1. 神经网络的通道数2. 输出的宽度和长度3. 理解神经网络的通道数3.1 都是错误的图片惹的祸3.1.1 没错但是看不懂的图3.1.2 开玩笑的错图3.1.3 给人误解的图 3.2 我或许理解对的通道数3.2.1 动图演示 1. 神经网络的通道数 半路出嫁到算法岗&#xff0c;额…