【Effective C++】阅读笔记3

1. 成员变量声明为Private

建议将成员变量声明为Private,然后再public中提供调用该数据的接口

设置成Private的原因分析

  • 类内成员变量被声明为Private,那么就可以外部代码直接访问或者修改内部数据
  • 通过公共接口获取内部数据,这样可以减少对外部代码的影响
  • 直接将成员变量设置为public,可能会导致数据不一致或者逻辑错误,将成员变量设置为Private就可以避免外界随意修改数据,从而确保数据的完整性

将成员变量设置为public错误事例分析 

外部代码可以随意修改变量的数值,与此同时还可以将其设置负数 

#include <iostream>
#include <string>class Person {
public:std::string name;  int age;           
};int main() {Person p;p.name = "Alice";p.age = -5;  std::cout << "名字:" << p.name << ",年龄:" << p.age << std::endl;return 0;
}

 解决方法:成员变量声明为Private,同时提供公共接口

  • 获取成员变量只可以通过getter方法访问
  • 设置成员变量则是通过setAge,该方法内部会检查数据的有效性

#include <iostream>
#include <string>class Person {
public:Person(const std::string& name, int age) : name_(name) {setAge(age);  // 使用 setter 进行初始化}// Getterstd::string getName() const { return name_; }int getAge() const { return age_; }// Settervoid setAge(int age) {if (age >= 0) {  // 检查年龄有效性age_ = age;}else {std::cerr << "错误:年龄不能为负数" << std::endl;}}private:std::string name_;int age_;
};int main() {Person p("Alice", 25);std::cout << "名字:" << p.getName() << ",年龄:" << p.getAge() << std::endl;p.setAge(-5);  // 试图设置无效年龄std::cout << "名字:" << p.getName() << ",年龄:" << p.getAge() << std::endl;return 0;
}

成员变量声明为Private的好处

还可以支持数据验证和日志记录功能,也就是通过接口访问成员变量的时候,可以添加验证逻辑或者日志逻辑,从而更好的实现数据管理;

成员变量设置为Private后,还可以延迟初始化,避免不必要的消耗

 2. 优先使用非成员非友元函数

主要是为了减少类的复杂性,如果一个函数在实现功能的时候不需要访问类的私有成员,那么就可以将这个类设计成为非成员非友元函数

问题分析

例如在一个表示分数的类中,实现了*运算符重载,此时如果将*运算符重载成为成员函数,那么就会增加这个类的复杂性

#include <iostream>class Rational {
public:Rational(int numerator = 0, int denominator = 1): numerator_(numerator), denominator_(denominator) {}Rational operator*(const Rational& rhs) const {return Rational(numerator_ * rhs.numerator_, denominator_ * rhs.denominator_);}void print() const {std::cout << numerator_ << "/" << denominator_ << std::endl;}private:int numerator_;int denominator_;
};int main() {Rational a(1, 2);Rational b(3, 4);Rational result = a * b;result.print(); return 0;
}

解决方法:如果一个类并不需要访问私有成员(因为a,b的数值都是构造时就赋值了),此时就可以将其设计为非成员非友元函数

  • operator*定义成非成员函数,然后声明为Rational的友元,这样就可以访问其私有成员
  • 通过这样的方法简化结构的同时,还可以访问其成员 
class Rational {
public:Rational(int numerator = 0, int denominator = 1): numerator_(numerator), denominator_(denominator) {}void print() const {std::cout << numerator_ << "/" << denominator_ << std::endl;}private:int numerator_;int denominator_;// 提供访问私有成员的友元声明friend Rational operator*(const Rational& lhs, const Rational& rhs);
};// 非成员运算符重载
Rational operator*(const Rational& lhs, const Rational& rhs) {return Rational(lhs.numerator_ * rhs.numerator_, lhs.denominator_ * rhs.denominator_);
}int main() {Rational a(1, 2);Rational b(3, 4);Rational result = a * b;result.print(); return 0;
}

适合使用该方法场景分析

  • 不依赖对象内部状态的操作,例如只是数学计算、全局功能等
  • 运算符重载
  • 类中的一些辅助函数

总结反思

  • 优先使用非成员非友元函数:如果一个函数不需要访问类的私有成员,将它设计为非成员非友元函数更好
  • 减少类的复杂性:使用非成员函数可以减少类的职责和耦合度,使类的设计更清晰
  • 运算符重载优先使用非成员实现:运算符重载时,优先考虑非成员实现,除非必须访问类的私有成员
  • 提供友元函数访问私有成员:在非成员函数需要访问私有数据时,可以将其声明为友元函数

 3. 如果所有参数都需要类型转换,使用非成员函数

C++中运算符重载时,可能会需要对操作数进行隐式类型转换,当所有操作数都需要进行类型转换的时候,最好选择非成员函数实现运算符重载,因为成员函数的运算符重载只会对右侧参数进行隐式类型转换,而非成员函数则允许对所有参数进行隐式类型转换。

成员函数运算符重载限制分析

成员函数实现运算符重载的时候,隐式类型转换只会应用右侧的参数,也就是说,左侧的参数必须和对象类型匹配,否则编译器不会进行隐式类型转换

  • 下述代码编译的时候,编译器会尝试将2作为*左侧的操作数(this对象,也就是Rational类型)
  • 但是2不是Rational类型,所以最终肯定会导致编译失败

#include <iostream>class Rational {
public:Rational(int numerator = 0, int denominator = 1): numerator_(numerator), denominator_(denominator) {}// 成员函数重载运算符*Rational operator*(const Rational& rhs) const {return Rational(numerator_ * rhs.numerator_, denominator_ * rhs.denominator_);}void print() const {std::cout << numerator_ << "/" << denominator_ << std::endl;}private:int numerator_;int denominator_;
};int main() {Rational r1(1, 2);Rational result = 2 * r1;result.print();return 0;
}

解决方法:使用非成员函数运算符重载

因为非成员函数可以允许对所有参数进行隐式类型转换的,这样就可以让编译器将2隐式转换为Rational类型,从而避免了成员函数的类型限制

#include <iostream>class Rational {
public:Rational(int numerator = 0, int denominator = 1): numerator_(numerator), denominator_(denominator) {}void print() const {std::cout << numerator_ << "/" << denominator_ << std::endl;}private:int numerator_;int denominator_;// 友元声明,允许非成员函数访问私有成员friend Rational operator*(const Rational& lhs, const Rational& rhs);
};// 非成员函数重载运算符*
Rational operator*(const Rational& lhs, const Rational& rhs) {return Rational(lhs.numerator_ * rhs.numerator_, lhs.denominator_ * rhs.denominator_);
}int main() {Rational r1(1, 2);Rational result = 2 * r1;  // 允许隐式类型转换result.print();  return 0;
}

使用场景分析

  • 非成员函数运算符重载
    • 所有参数可能都需要进行隐式类型转换:例如 int * Rational,这时非成员函数更灵活
    • 对称的二元运算符:像 +-*/ 等对称运算符,通常适合实现为非成员函数
    • 允许访问私有成员:通过 friend 声明,可以让非成员函数访问类的私有数据成员,确保函数实现的灵活性
  • 成员函数运算符重载
    • 赋值相关运算符:如 =+=-=*=/=,因为它们会改变左侧操作数的值,通常应实现为成员函数
    • 单目运算符:如前置和后置 ++--、取地址 &、解引用 * 等,通常适合实现为成员函数,因为它们通常只对一个操作数进行操作

4. 实现一个不抛异常的swap函数

自我实现swap函数的原因

首先std库中提供的swap函数默认是通过拷贝构造函数和赋值运算符来实现,但是这些操作有可能会出现异常,其次主要目的就是减少性能开销,提高代码安全性

错误分析,使用默认swap交换一个有动态资源的类

  • swap使用的是拷贝构造和赋值与赋值运算符,那么就很有可能触发内存分配和释放操作
  • 代码中如果构造和赋值运算符抛出异常,那么swap就可能引发异常,最终导致w1和w2状态不一致
  • 中断原因分析:widget类中name_是一个指针类型,当使用swap交换两个widget对象中的name_指针时,一个对象被销毁后,其会释放name_指针指向的内存,而另一个对象也会释放这块已经释放的内存,所以导致了双重释放,最终导致了异常中断
    • 双重释放的根源在于,在没有定义深拷贝的时候,swap使用的是浅拷贝,所以只是简单的拷贝了其数值,所以就造成了多个对象持有相同的指针,所以最终在析构的时候就会重复释放同一块内存,最终引发异常

#include <iostream>
#include <string>
#include <algorithm>  // for std::swapclass Widget {
public:Widget(const std::string& name) : name_(new std::string(name)) {}~Widget() { delete name_; }// 打印 name_void print() const {std::cout << "Widget 名称: " << *name_ << std::endl;}private:std::string* name_;
};int main() {Widget w1("Alice");Widget w2("Bob");std::swap(w1, w2);  // 使用 std::swap,默认调用拷贝构造和赋值w1.print();w2.print();return 0;
}

解决方法:实现一个成员函数swap来直接交换数据成员,这样就可以有效避免不必要的拷贝和赋值

  • 通过交换指针,实现两个对象动态资源的交换,这样就避免了浅拷贝的问题
  • 通过非成员函数调用类中的swap函数,从而确保std::swap兼容

#include <iostream>
#include <string>
#include <utility>  // for std::swapclass Widget {
public:Widget(const std::string& name) : name_(new std::string(name)) {}~Widget() { delete name_; }// 提供一个不抛异常的 swap 成员函数void swap(Widget& other) noexcept {std::swap(name_, other.name_);  // 直接交换指针,不抛异常}// 打印 name_void print() const {std::cout << "Widget 名称: " << *name_ << std::endl;}private:std::string* name_;
};// 提供一个非成员的 swap 函数,以便与 std::swap 兼容
void swap(Widget& lhs, Widget& rhs) noexcept {lhs.swap(rhs);  // 使用 Widget 的成员 swap
}int main() {Widget w1("Alice");Widget w2("Bob");swap(w1, w2);  // 使用自定义 swap 函数w1.print();w2.print();return 0;
}

总结反思

  • 优先自己定义一个没有异常的swap函数,同时通过noexcept声明来确保swap函数不会抛出异常
  • 实现一个非成员swap函数,也就是通过调用成员swap函数,从而确保与std库中的swap函数兼容,使得可以无缝衔接到自己类函数中

5. 尽量延后变量定义的时间

减少性能开销,当定义个类对象的时候,构造函数会立即调用,有可能会涉及到资源分配,所以适当的将变量定义延后,可以避免不必要的初始化

错误分析

例如代码中,即使最终a不是大于b的,变量Result和temp还是被创建了,这就浪费了内存空间

#include <iostream>
#include <string>int main() {int a = 5;int b = 10;int result = 0;  // 提前定义变量std::string temp = "未使用的字符串";  // 提前定义变量if (a > b) {result = a + b;}std::cout << "Result: " << result << std::endl;return 0;
}

解决方法:延后变量的定

也就是说,只有在变量Result和temp需要的时候才被定义,这样就可以避免不必要的内存分配

int main() {int a = 5;int b = 10;if (a > b) {int result = a + b;  // 延后定义变量std::cout << "Result: " << result << std::endl;}if (a == 5) {std::string temp = "延迟定义的字符串";  // 延后定义变量std::cout << "Temp: " << temp << std::endl;}return 0;
}

总结反思

  • 尽量延后变量的定义,减少性能损耗
  • 变量定义在首次使用的地方,可以增强代码可读性

6. 尽量少进行转换

转换增加的劣势

  • 频繁的类型转换会增加代码的复杂程度,使得代码难以理解和维护
  • 运行时的类型转换和多层次的隐式类型转换会损耗性能,最终会拖慢程序的运行速度

隐式类型转换问题分析

  • 代码中如果将double转换成int类型,那么double后面的小数点数据就会丢失

#include <iostream>void printDouble(double value) {std::cout << "Double 值: " << value << std::endl;
}int main() {int intValue = 10;printDouble(intValue);  // 隐式转换 int 到 doubledouble doubleValue = 5.99;int truncatedValue = doubleValue;  // 隐式转换 double 到 intstd::cout << "截断后的 int 值: " << truncatedValue << std::endl;return 0;
}

方法:使用显式类型转换,明确自己想要转换成何种类型

  • 例如可以使用static_cast表明转换意图
#include <iostream>int main() {double doubleValue = 5.99;int truncatedValue = static_cast<int>(doubleValue);  std::cout << "截断后的 int 值: " << truncatedValue << std::endl;return 0;
}

技巧:要避免将基础类型转换为自定义类型的行为

  • 使用explicit关键字避免构造函数进行隐式类型转换,也就是只有在显式声明的时候才可以将double转换为complex对象

  • 隐式类型转换分析
    • Complex c2 = 3.0:double到complex的隐式类型转换
    • 首先explicit关键字的作用就是在一个类中接受单个参数的构造函数,编译器允许使用该函数进行隐式类型转换,所以在这个类中c1对象可以通过传入单个参数完成构造
    • c2报错的原因在于编译器需要从double隐式转换到complex,但是此时构造函数被标记为explicit,所以造成了编译器报错,阻止了这种隐式转换的发生
#include <iostream>class Complex {
public:Complex(double real, double imaginary) : real_(real), imaginary_(imaginary) {}// 避免隐式转换的构造函数explicit Complex(double real) : real_(real), imaginary_(0) {}void print() const {std::cout << "Complex 数: " << real_ << " + " << imaginary_ << "i" << std::endl;}private:double real_;double imaginary_;
};int main() {Complex c1(3.0);  // 合法// Complex c2 = 3.0;  // 编译错误,防止隐式转换c1.print();return 0;
}

总结反思

  • 代码中减少隐式类型转换的使用,从而避免隐式类型转换而导致的错误
  • 多使用显示类型转换,例如可以使用static_cast或者dynamic_cast等,以确保代码意图明确
  • 使用explicit关键字,防止构造函数的隐式类型转换

7. 避免返回对象内部的指针或者引用

直接返回对象的内部指针或者引用会使得对象的实现细节暴露,这样就会导致数据不安全和未定义的行为,特别是当返回指针或者引用是一个局部变量的时候,如果无意中修改了类中的数据,这样就可能会导致问题。

错误分析:返回对象的内部指针或者引用

  • getName函数返回了name_指针,暴露了Person类中的细节
  • 外部可以通过namePtr直接访问或者间接修改Person类的私有数据,最终破坏其封装性

#include <iostream>
#include <string>class Person {
public:Person(const std::string& name) : name_(name) {}// 返回内部指针(不安全)const std::string* getName() const {return &name_;}private:std::string name_;
};int main() {Person p("Alice");const std::string* namePtr = p.getName();std::cout << *namePtr << std::endl;  // 输出:Alice// 非法操作:尽管是 const,外部代码可以修改底层数据或产生悬空指针风险return 0;
}

解决方法:返回name数据的副本,保证不对原始对象的影响

#include <iostream>
#include <string>class Person {
public:Person(const std::string& name) : name_(name) {}// 返回副本std::string getName() const {return name_;}private:std::string name_;
};int main() {Person p("Alice");std::string name = p.getName();std::cout << name << std::endl; // 修改 name 的副本不会影响 Person 对象的 name_name = "Bob";std::cout << p.getName() << std::endl; return 0;
}

返回指针或者引用保证安全方法:使用const限制修改

通过返回数据引用,从而使得const从而确保调用者无法修改返回的内容


#include <iostream>
#include <string>class Person {
public:Person(const std::string& name) : name_(name) {}// 返回 const 引用,防止修改const std::string& getName() const {return name_;}private:std::string name_;
};int main() {Person p("Alice");const std::string& nameRef = p.getName();std::cout << nameRef << std::endl; nameRef = "Bob"; return 0;
}

 使用智能指针安全的返回指针和引用

针对于动态资源分配,使用智能指针可以更好的管理资源分配

  • 通过智能指针管理动态分配的资源,同时通过const限制修改权限
#include <iostream>
#include <memory>
#include <string>class Person {
public:Person(const std::string& name) : name_(std::make_shared<std::string>(name)) {}// 返回智能指针std::shared_ptr<const std::string> getName() const {return name_;}private:std::shared_ptr<const std::string> name_;
};int main() {Person p("Alice");std::shared_ptr<const std::string> namePtr = p.getName();std::cout << *namePtr << std::endl;  // 输出:Alice// *namePtr = "Bob";  // 编译错误,无法修改return 0;
}

总结反思

  •  要避免返回内部数据的指针或引用,尽量选择返回数据的副本,从而保证类的封装性和安全性
  • 如果必须返回引用或者指针,那么使用const限制访问权限,防止其调用修改内部数据
  • 如果是动态资源,需要分配内存空间,那么优先使用智能指针对资源进行管理,从而避免内存泄漏或者悬空指针的情况

8. 为“异常安全”努力

核心意思就是实现在抛出异常的时候保证程序运行安全,也就是在抛出异常的时候要确保状态的完整性和一致性

异常安全的三种保护标准

  • 基本保证:也就是即使发生异常,程序不会发生资源泄漏或者数据不会发生资源泄漏或者数据损坏,即使程序在异常发生后不能恢复正常操作
  • 强烈保证:要求程序在异常后可以退回到异常发生前的状态,也就是操作要么成功要么完全失败,类似于数据库中的原子性
  • 不抛出异常安全保证:承诺某个函数绝对不会抛出异常,也是最强的安全保证,可以通过noexcept关键字声明不抛出异常的函数

方法1:使用RAII

通过RAII机制,保证即使发生异常的时候,也可以保证资源正常释放

#include <iostream>
#include <memory>
#include <string>class Person {
public:Person(const std::string& name) : name_(std::make_unique<std::string>(name)){}void printName() const {std::cout << "姓名: " << *name_ << std::endl;}private:std::unique_ptr<std::string> name_;
};int main() {try {Person p("Alice");p.printName();}catch (...) {std::cout << "异常发生" << std::endl;}return 0;
}

方法2:使用noexcept保证不抛出异常

利用该关键字声明一个函数不会抛出异常,通过noexcept提高代码的稳定性和性能

  • swap函数使用noexcept声明不抛出异常,从而保证swap操作异常的安全性
#include <iostream>
#include <utility>
#include <vector>class Widget {
public:Widget(int value) : value_(value) {}void swap(Widget& other) noexcept {std::swap(value_, other.value_);}private:int value_;
};int main() {Widget w1(1);Widget w2(2);w1.swap(w2);  // 不抛出异常的 swap 操作return 0;
}

方法3:使用事务式编程 

也就是进行操作之前,先创建一个副本或者备份,目的就是确保操作成功后替换原始对象,这样即使操作过程中发生异常的时候,程序依然可以恢复原状

class Transaction {
public:Transaction(const std::string& data) : data_(data) {}void setData(const std::string& newData) {// 先创建备份,保证操作失败时能够恢复std::string backup = data_;// 假设此操作可能抛出异常processData(newData);// 更新成功才替换原数据data_ = newData;}void printData() const {std::cout << "数据: " << data_ << std::endl;}private:void processData(const std::string& newData) {if (newData.empty()) throw std::runtime_error("无效的数据");}std::string data_;
};int main() {Transaction transaction("初始数据");try {transaction.setData("");}catch (const std::exception& e) {std::cout << "异常: " << e.what() << std::endl;}transaction.printData();  // 输出:新数据或初始数据return 0;
}

总结反思

  • 优先使用智能指针管理动态内存资源,从而减少资源泄漏的风险
  • 优先使用noexcept,可以避免其抛出异常
  • 要谨慎操作状态修改,使用备份或者事务机制,从而确保即使发生异常也可以回滚

9. 理解Inline函数的利弊

内联函数就是将函数代码直接嵌入到调用点,从而避免函数调用的开销,编译器会在调用点替换函数调用为函数体代码,这样就减少了调用堆栈的实践。

内联函数的优点分析

  • 减少函数调用时的开销
    • 因为调用函数的时候,程序会进行一系列的操作,例如压栈、跳转到函数地址、返回值处理等,这些都会增加函数的开销
  • 提高性能
    • 对于一些小型、频繁调用的函数使用内联函数可以显著的提高性能
class Rectangle {
public:Rectangle(int width, int height) : width_(width), height_(height) {}// 内联 getter 函数inline int getWidth() const { return width_; }inline int getHeight() const { return height_; }private:int width_;int height_;
};int main() {Rectangle rect(10, 20);int area = rect.getWidth() * rect.getHeight();  // 内联后无调用开销return 0;
}

内联函数的缺点

  • 增加代码体积
    • 内联函数会显著增加代码的体积,也就会导致指令缓存压力增加,最终降低性能
  • 编译时间加长
    • 内联增加了编译器的负担,特别是当函数定义在头文件中时,每次编译都会将函数体嵌入每个调用点,导致编译时间变长,且链接时间也可能增加
  • 内联函数多不方便进行调试

内联函数适用的场景

小型而且简单函数,因为这样的函数逻辑简单,编译器更好对其进行优化;对于一些执行频繁而且对性能要求高的小函数,内联函数也可以显著的提高其性能。

不适合内联函数分析

  • 复杂大型的函数
  • 递归函数
    • 因为递归的调用次数在编译期间是未知的,递归的内联会导致代码膨胀
  • 虚函数
    • 调用的时候需要通过虚函数表进行解析,编译器通常无法再编译期间确定调用的具体函数

总结反思

  • 小函数适合使用内联,相反大且复杂的函数不适合内联
  • 递归和虚函数不适合内联,因为递归次数是不确定,且虚函数通常需要通过表去查找,内联通常会被编译器忽略
  • 编译器自动优化,编译器会自动识别哪些函数适合内联

10. 将文件间的编译依赖降到最低

文件之间编译过度依赖代价

  • 增加编译时间
    • 因为每次修改头文件,或者所有直接或间接依赖该头文件的文件都需要重新编译,这可能导致整个项目的编译时间显著增加。随着项目规模增长,频繁的头文件依赖会拖慢编译速度
  • 耦合度增加
    • 修改一个文件,会影响其他所有与之相关的文件
  • 代码难以维护

方法1:使用前置声明

头文件中尽量使用前置声明,而不是选择包含完整的头文件,使用前置声明一个类的存在,而不需要包含其完整定义,从而减少头文件之间的依赖关系

  • 前置声明适用于指针或者引用成员变量的声明
// Employee.h
#include <string>class Department;  // 前向声明 Department 类class Employee {
public:Employee(const std::string& name);void setDepartment(Department* dept);  // 使用前向声明的指针
private:std::string name_;Department* department_;
};
// Department.h
#include <string>class Department {
public:Department(const std::string& name);
private:std::string name_;
};

方法2:使用#include在源文件中实现依赖

也就是说将头文件的依赖放在源文件中,而不是头文件中,这样就可以减少头文件的包含。只有在类的成员函数需要某个类的定义时,才在.cpp文件中使用#include所需头文件即可

// Employee.h (头文件中只放前置声明)
#include <string>class Department;  // 使用前向声明class Employee {
public:Employee(const std::string& name);void setDepartment(Department* dept);
private:std::string name_;Department* department_;
};
// Employee.cpp
#include "Employee.h"
#include "Department.h"  // 只在 .cpp 文件中包含Employee::Employee(const std::string& name) : name_(name), department_(nullptr) {}void Employee::setDepartment(Department* dept) {department_ = dept;
}

方法3:pimpl方法

在类中使用指针指向该类的方法,将类的实现细节隐藏在源文件中,从而降低头文件的依赖关系;简单可以理解成调用的时候,直接通知封装好的指针

// Widget.h
#include <memory>
class WidgetImpl;  // 前向声明 WidgetImpl 类class Widget {
public:Widget();~Widget();void doSomething();private:std::unique_ptr<WidgetImpl> pImpl;  // Pimpl 指针
};
// Widget.cpp
#include "Widget.h"
#include "WidgetImpl.h"  // 只有在实现文件中包含实现类Widget::Widget() : pImpl(std::make_unique<WidgetImpl>()) {}Widget::~Widget() = default;void Widget::doSomething() {pImpl->performTask();
}

总结反思

  • 头文件使用前向声明,减少对编译的依赖
  • 尽量在.cpp文件中使用#include,而不是头文件
  • pimpl收发,通过指针调用,隐藏细节

 

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

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

相关文章

QCustomPlot添加自定义的图例,实现隐藏、删除功能(二)

文章目录 QCustomPlot初识和基本效果图实现步骤:详细代码示例:实现原理和解释:使用方法:其他参考要实现一个支持复选框来控制曲线显示和隐藏的自定义 QCPLegend 类,可以通过继承 QCPLegend 并重写绘制和事件处理方法来实现,同时发出信号通知曲线的状态变更。 QCustomPl…

基于Java公益志愿捐赠管理系统设计与实现(源码+LW+调试+开题报告)

项目描述 临近学期结束&#xff0c;还是毕业设计&#xff0c;你还在做java程序网络编程&#xff0c;期末作业&#xff0c;老师的作业要求觉得大了吗?不知道毕业设计该怎么办?网页功能的数量是否太多?没有合适的类型或系统?等等。这里根据疫情当下&#xff0c;你想解决的问…

【反射率】-- Lab 转换(excel)

系列文章目录 文章目录 系列文章目录前言一、CIE1.CIE 简介2.cie 1931标准色度匹配函数数据3.从CIE1931RGB到CIE1931 XYZ 二、Lab颜色空间的理解1.Lab色差公式怎么计算色差 三、D65光源Lab计算总结 前言 一、CIE 1.CIE 简介 CIE是由国际照明工程领域中光源制造、照明设计和光…

RRSIS: Referring Remote SensingImage Segmentation

Abstract 从遥感图像中定位目标在实际应用中有很大的用处。 参考图像分割的目的是分割出给定表达所指向的对象&#xff0c;这在自然图像中得到了广泛的研究。我们为这项任务创建了一个名为RefSegRS的新数据集&#xff0c;使我们能够评估不同的方法。我们提出了一种语言引导的跨…

【论文复现】自动化细胞核分割与特征分析

本文所涉及所有资源均在这里可获取。 作者主页&#xff1a; 七七的个人主页 文章收录专栏&#xff1a; 论文复现 欢迎大家点赞 &#x1f44d; 收藏 ⭐ 加关注哦&#xff01;&#x1f496;&#x1f496; 自动化细胞核分割与特征分析 引言效果展示HoverNet概述HoverNet原理分析整…

长亭那个检测能力超强的 WAF,出免费版啦

告诉你们一个震撼人心的消息&#xff0c;那个检测能力超强的 WAF——长亭雷池&#xff0c;他推出免费社区版啦&#xff0c;体验地址见文末。 八年前我刚从学校毕业&#xff0c;在腾讯做安全研究&#xff0c;看到宇森在 BlackHat 上演讲的议题 《永别了&#xff0c;SQL 注入》 …

U-net网络实现细胞分割

项目源码获取方式见文章末尾&#xff01; 600多个深度学习项目资料&#xff0c;快来加入社群一起学习吧。 《------往期经典推荐------》 项目名称 1.【基于CNN-RNN的影像报告生成】 2.【卫星图像道路检测DeepLabV3Plus模型】 3.【GAN模型实现二次元头像生成】 4.【CNN模型实现…

Word首行空格不显示空格符号问题

Word段落首行空格设置指南 问题描述 在Word中编辑文档时&#xff0c;有时会遇到段落首行敲击空格键却不显示空格的问题。这通常与Word的自动更正设置有关。 解决方法 要解决此问题&#xff0c;需要调整Word的自动更正设置。具体步骤如下&#xff1a; 打开Word的"自动更…

Remix部署智能合约时报错:Gas estimation failed

1、在Remix部署智能合约时报错如下&#xff1a; 2、这时候即使发送交易&#xff0c;也无法部署 3、后来看到有人建议说调整一下GAS LIMIT&#xff0c;调整到30000000也不行&#xff0c;甚至当调整到6000000以后连交易记录都没有了 4、最终解决办法&#xff1a;Remix 和 Ganache…

我要精通前端-块级元素和行内元素深入学习笔记

真的发现前端天天增删改查&#xff0c;真的是问一些比较细节的知识&#xff0c;我真的懂么 1、块级元素间的margin会重叠&#xff0c; <div class"head"></div> <div class"content"></div>.head {margin: 5px;border: 10px sol…

C语言 循环高级

时间&#xff1a;2024.11.6 一、学习内容 1、无限循环 无限循环&#xff1a;循环永远停不下来 注意点&#xff1a;无限循环因为永远停不下来&#xff0c;所以下面不能再写其他的代码了 2、break 跳转控制语句&#xff1a; 在循环的过程中&#xff0c;跳到其他语句上执行 #…

62-Java-面试专题(1)__基础

62-Java-面试专题(1)__基础-- 笔记 笔记内容来源与黑马程序员教学视频 文章目录 62-Java-面试专题(1)__基础-- 笔记Java-面试专题(1)笔记中涉及资源&#xff1a; 一、二分查找①&#xff1a;代码实现1. 流程2. 代码实现3. 测试 ②&#xff1a;解决整数溢出&#xff08;方法一&…

解决CORS (跨源资源共享) 错误

问题引入 前端代码 <template><div id"hello-vue" class"demo">{{ message }}</div><el-button type"primary" click"handleClick">我是一个按钮</el-button></template><script setup>//加…

微信小程序 uniapp网络记账设计个人理财系统

文章目录 项目介绍具体实现截图技术介绍mvc设计模式小程序框架以及目录结构介绍错误处理和异常处理java类核心代码部分展示详细视频演示源码获取 项目介绍 aa账簿 (自动编号、用户id、用户账号、备注、登记时间、消费类型、创建时间、消费金额)&#xff1b; 配置文件 (自动编…

【SQL Server】华中农业大学空间数据库实验报告 实验一 数据库

实验目的 熟悉了解掌握SQL Server软件的基本操作与使用方法&#xff0c;认识界面&#xff0c;了解其两个基本操作系统文件&#xff0c;并能熟练区分与应用交互式与T-SQL式两种方法在SQL Server中如何进行操作&#xff1b;学习有关数据库的基本操作&#xff0c;包括&#xff1a…

大腾智能3D一览通携手飞书,实现高效设计协同

设计生产作为制造业的核心环节&#xff0c;其效率与协同性直接关系到企业的市场竞争力和响应速度。 在设计部门内部&#xff0c;虽然可以直接发送3D格式的文件进行沟通和评审&#xff0c;但当涉及到跨部门甚至是跨企业的协作时&#xff0c;情况就变得复杂。通常需要大量的多角…

Java项目实战II基于Spring Boot的药店管理系统的设计与实现(开发文档+数据库+源码)

目录 一、前言 二、技术介绍 三、系统实现 四、文档参考 五、核心代码 六、源码获取 全栈码农以及毕业设计实战开发&#xff0c;CSDN平台Java领域新星创作者&#xff0c;专注于大学生项目实战开发、讲解和毕业答疑辅导。 一、前言 随着医疗行业的快速发展和人们对健康需…

qt QHeaderView详解

1、概述 QHeaderView 是 Qt 框架中的一个类&#xff0c;它通常作为 QTableView、QTreeView 等视图类的一部分&#xff0c;用于显示和管理列的标题&#xff08;对于水平头&#xff09;或行的标题&#xff08;对于垂直头&#xff09;。QHeaderView 提供了对这些标题的排序、筛选…

【Android】使用productFlavors构建多个变体

项目需求 在一个设备上安装两个一样的程序app 需求解决 我们知道每一个app都有一个包名的&#xff0c;如果一个app在Android设备上安装之后&#xff0c;再安装这个app的话会进行覆盖安装&#xff0c;因为他们两个的包名是一样的&#xff0c;默认是一个app。 但是我们现在需…

AI笔筒操作说明及应用场景

AI笔筒由来&#xff1a; 在快节奏的现代办公环境中&#xff0c;我们一直在寻找既能提升效率、增添便利&#xff0c;又能融入企业文化、展现个人品味的桌面伙伴。为此&#xff0c;我们特推出专为追求卓越、注重细节的您设计的AI笔筒礼品版&#xff0c;它集高科技与实用性于一身…