C++(21):特殊工具与技术
控制内存分配
某些应用程序对内存分配有特殊需求,无法直接应用标准内存管理机制。需要自定义内存分配的细节。
重载 new 和 delete
void* operator new(std::size_t size) {// 自定义内存分配逻辑void* ptr = std::malloc(size);if (!ptr) {throw std::bad_alloc(); // 内存分配失败时抛出异常}return ptr;
}
上述代码中,重载了new运算符,std::size_t size参数表示要分配的字节数。在这个函数中,可以实现自己的内存分配逻辑,例如使用malloc来分配内存。如果分配失败,通常会抛出std::bad_alloc异常。
void operator delete(void* ptr) noexcept {// 自定义内存释放逻辑std::free(ptr);
}
重载了delete运算符,接受一个指向要释放内存的指针。在这个函数中,可以实现自己的内存释放逻辑,例如使用free来释放内存。
malloc 和 free 函数定义在头文件<cstdlib>中。
定位 new 表达式
定位 new 允许在已分配的内存块上创建对象,而不是使用默认的内存分配方式。通常,它用于在已分配的内存上构造对象,例如在内存池或特定内存区域中。
new (place_address) type
new (place_address) type (initializers)
new (place_address) type [size]
new (place_address) type [size] { braced initializer list }
其中 place_address必须是一个指针,同时在initializers中提供一个(可能为空的)以逗号分隔的初始值列表,该初始值列表将用于构造新分配的对象。
当只传入一个指针类型的实参时,定位new表达式构造对象但是不分配内存。
#include <iostream>class MyClass {
public:MyClass(int val) : value(val) {}void Print() {std::cout << "Value: " << value << std::endl;}
private:int value;
};int main() {// 分配一块内存用于存储 MyClass 对象void* memory = ::operator new(sizeof(MyClass));// 使用定位 new 表达式在已分配的内存上创建对象MyClass* obj = new (memory) MyClass(42);// 访问对象的方法obj->Print();// 使用定位 delete 表达式释放对象obj->~MyClass();// 释放内存块::operator delete(memory);return 0;
}
运行时类型识别
运行时类型识别(run-time type identification,RTTI)的功能由两个运算符实现:
typeid运算符,用于返回表达式的类型。
dynamic_cast运算符,用于将基类的指针或引用安全地转换成派生类的指针或引用。
当我们将这两个运算符用于某种类型的指针或引用,并且该类型含有虚函数时,运算符将使用指针或引用所绑定对象的动态类型。
dynamic_cast 运算符
dynamic_cast运算符的使用形式如下所示:
dynamic_cast<type*>(e)
dynamic_cast<type&>(e)
dynamic_cast<type&&>(e)
其中,type必须是一个类类型,并且通常情况下该类型应该含有虚函数。在第一种形式中,e必须是一个有效的指针;在第二种形式中,e必须是一个左值;在第三种形式中,e不能是左值。
dynamic_cast 运算符用于在继承层次结构中进行安全的向下转型。它可以将基类指针或引用转换为派生类指针或引用,并且在类型不匹配时返回 nullptr(对指针)或引发 std::bad_cast 异常(对引用)。
#include <iostream>class Base {
public:virtual ~Base() {}
};class Derived : public Base {
public:void DerivedFunction() {std::cout << "DerivedFunction called." << std::endl;}
};int main() {Base* basePtr = new Derived;Derived* derivedPtr = dynamic_cast<Derived*>(basePtr);if (derivedPtr) {derivedPtr->DerivedFunction();} else {std::cout << "无法进行向下转型。" << std::endl;}delete basePtr;return 0;
}//dynamic_cast 运算符用于将 basePtr 转换为 Derived* 类型的指针,如果转换成功,就可以调用 Derived 类的成员函数。
typeid 运算符
typeid表达式的形式是typeid(e),其中e可以是任意表达式或类型的名字。
typeid操作的结果是一个常量对象的引用,该对象的类型是标准库类型type_info或者type_info的公有派生类型。
typeid运算符可以作用于任意类型的表达式。
当运算对象不属于类类型或者是一个不包含任何虚函数的类时,typeid运算符指示的是运算对象的静态类型。而当运算对象是定义了至少一个虚函数的类的左值时,typeid的结果直到运行时才会求得。
typeid 运算符允许获取对象的实际类型信息,以便在运行时判断对象的类型。它通常与 std::type_info 类一起使用。
#include <iostream>
#include <typeinfo>class Base {
public:virtual ~Base() {}
};class Derived : public Base {
};int main() {Base* basePtr = new Derived;if (typeid(*basePtr) == typeid(Derived)) {std::cout << "basePtr指向的对象是Derived类型" << std::endl;} else if (typeid(*basePtr) == typeid(Base)) {std::cout << "basePtr指向的对象是Base类型" << std::endl;}delete basePtr;return 0;
}
使用 RTTI
运行时类型识别(RTTI)可以用来获取对象的实际类型信息,并在运行时根据对象的类型进行不同的处理。
#include <iostream>
#include <typeinfo>class Animal {
public:virtual void Speak() const {std::cout << "Animal speaks." << std::endl;}
};class Dog : public Animal {
public:void Speak() const override {std::cout << "Dog barks." << std::endl;}
};class Cat : public Animal {
public:void Speak() const override {std::cout << "Cat meows." << std::endl;}
};int main() {Animal* animals[3];animals[0] = new Dog();animals[1] = new Cat();animals[2] = new Animal();for (int i = 0; i < 3; i++) {const std::type_info& typeInfo = typeid(*animals[i]);if (typeInfo == typeid(Dog)) {std::cout << "This is a Dog. ";} else if (typeInfo == typeid(Cat)) {std::cout << "This is a Cat. ";} else {std::cout << "This is an unknown animal. ";}animals[i]->Speak();}for (int i = 0; i < 3; i++) {delete animals[i];}return 0;
}//在上述示例中,我们创建了一个基类Animal和两个派生类Dog和Cat。然后,我们创建了一个包含不同类型的Animal指针的数组,
//并使用typeid运算符获取对象的类型信息。最后,我们根据类型信息执行不同的操作。//要注意的是,typeid返回的是std::type_info类型,可以用来进行类型比较。
//另外,为了使类型比较有效,Animal类中的Speak函数被声明为虚函数,以启用多态性。
尽管RTTI可以实现运行时类型检查和操作,但通常应该避免使用它,因为它可能会引入性能开销,并且有时更好的做法是使用多态性和虚函数来避免需要RTTI。 RTTI通常在某些特定的情况下才会用到。
type_info 类
type_info类的精确定义随着编译器的不同而略有差异。
它用于表示类型信息。std::type_info 的对象通常由运行时类型识别(RTTI)操作 typeid 返回。它包含有关一个类型的信息,例如类型的名称或类型的标识。
1.获取类型信息:std::type_info 通常通过 typeid 运算符来获取类型信息。
const std::type_info& typeInfo = typeid(MyClass);
2.比较类型信息:可以使用 std::type_info 对象来比较类型信息。
const std::type_info& typeInfo1 = typeid(MyClass1);
const std::type_info& typeInfo2 = typeid(MyClass2);if (typeInfo1 == typeInfo2) {// 类型相同
} else {// 类型不同
}
3.获取类型名称:std::type_info 对象可以用于获取类型的名称。
const std::type_info& typeInfo = typeid(MyClass);
std::cout << "Type name: " << typeInfo.name() << std::endl;
4.比较类型信息的安全性:std::type_info 对象的比较是类型安全的,即使在继承层次结构中也可以正常工作。这对于执行基于多态性的类型检查非常有用。
Base* basePtr = new Derived;
const std::type_info& baseTypeInfo = typeid(Base);
const std::type_info& derivedTypeInfo = typeid(*basePtr);if (derivedTypeInfo == baseTypeInfo) {// 此代码块不会执行,因为类型不同
}
枚举类型
枚举类型可以将一组整型常量组织在一起。和类一样,每个枚举类型定义了一种新的类型。枚举属于字面值常量类型。
C++包含两种枚举:限定作用域的和不限定作用域的。
定义限定作用域的枚举类型:首先关键字 enum class(或等价使用 enum struct),随后是枚举类型名字以及用花括号括起来以逗号分隔的枚举成员列表,最后是一个分号:enum class open_modes {input,output,append) ;
定义不限定作用域的枚举类型:省略掉关键字 class(或 struct),枚举类型的名字是可选的:
enum color{red, yellow, green}; //不限定作用域的枚举类型
//未命名的、不限定作用域的枚举类型
enum (floatPrec = 6, doublePrec = 10,double_doublePrec = 10);
枚举成员
在限定作用域的枚举类型中,枚举成员的名字遵循常规的作用域准则,并且在枚举类型的作用域外是不可访问的。
与之相反,在不限定作用域的枚举类型中,枚举成员的作用域与枚举类型本身的作用域相同。
默认情况下,枚举值从0开始,依次加1。不过也能为一个或几个枚举成员指定专门的值。
枚举成员是const,因此在初始化枚举成员时提供的初始值必须是常量表达式。
可以定义枚举类型的constexpr变量:
constexpr intTypes charbits = intTypes::charTyp;
switch 语句中 case 标签的值必须是常量表达式,可以用枚举成员做 case 标签。
和类一样,枚举也定义新的类型
只要enum有名字,我们就能定义并初始化该类型的成员。要想初始化enum对象或者为enum对象赋值,必须使用该类型的一个枚举成员或者该类型的另一个对象。
一个不限定作用域的枚举类型的对象或枚举成员自动地转换成整型。
枚举类型的前置声明
可以提前声明 enum,但是不限定作用域的枚举类型在声明时必须指定成员类型。
enum 的声明和定义的成员类型必须匹配。
enum class Color1; // 前置声明 Color1,成员类型默认为 int
enum uid:long long; // 前置声明 uid,必须指定成员类型
类成员指针
成员指针是指可以指向类的非静态成员的指针。一般情况下,指针指向一个对象,但是成员指针指示的是类的成员,而非类的对象。
类的静态成员不属于任何对象,因此无须特殊的指向静态成员的指针,指向静态成员的指针与普通指针没有什么区别。
数据成员指针
class MyClass {
public:int data;void Print() {std::cout << "Data: " << data << std::endl;}
};int main() {int MyClass::*memberPtr = &MyClass::data;MyClass obj;obj.*memberPtr = 42;std::cout << obj.data << std::endl;return 0;
}//`定义了一个指向 MyClass 类的 data 数据成员的类成员指针 int MyClass::*memberPtr。
//然后,创建了一个 MyClass 对象 obj,并使用类成员指针来访问和修改 data 数据成员。`
成员函数指针
class MyClass {
public:void PrintHello() {std::cout << "Hello from MyClass" << std::endl;}
};int main() {void (MyClass::*memberFunctionPtr)() = &MyClass::PrintHello;MyClass obj;(obj.*memberFunctionPtr)();return 0;
}//定义了一个指向 MyClass 类的 PrintHello 成员函数的类成员指针 void (MyClass::*memberFunctionPtr)()。
//然后,创建了一个 MyClass 对象 obj,并使用类成员指针来调用 PrintHello 成员函数。
类成员指针的语法可以看起来比较复杂,但它们提供了一种强大的机制,用于在运行时动态选择要调用的成员函数或访问的数据成员,这在某些高级编程场景中非常有用。需要注意的是,类成员指针的类型与要指向的成员的类型有关,因此需要确保它们的类型匹配。
嵌套类
一个类可以定义在另一个类的内部,称之为嵌套类。
嵌套类可以访问外部类的私有成员,并且通常用于实现一种封装和组织的方式,以将相关联的类放在一起。嵌套类的一个常见用途是作为外部类的辅助类,用于实现某些功能。
嵌套类是一个独立的类,与外层类基本没什么关系。特别是,外层类的对象和嵌套类的对象是相互独立的。在嵌套类的对象中不包含任何外层类定义的成员:类似的,在外层类的对象中也不包含任何嵌套类定义的成员。
嵌套类的名字在外层类作用域中是可见的,在外层类作用域之外不可见。和其他嵌套的名字一样,嵌套类的名字不会和别的作用域中的同一个名字冲突。
#include <iostream>class OuterClass {
public:// 外部类的构造函数OuterClass(int value) : data(value) {}// 嵌套类的定义class NestedClass {public:NestedClass(int nestedValue) : nestedData(nestedValue) {}void Display() {std::cout << "Nested Data: " << nestedData << std::endl;}private:int nestedData;};void AccessNestedClass() {NestedClass nested(42);nested.Display();}private:int data;
};int main() {OuterClass outer(10);outer.AccessNestedClass();return 0;
}//定义了一个外部类 OuterClass 和一个嵌套类 NestedClass。嵌套类 NestedClass 可以访问外部类 OuterClass 的私有成员,例如 data。在 AccessNestedClass 成员函数中,创建了一个 NestedClass 的对象并访问了它的成员函数。
在嵌套类在其外层类之外完成真正的定义之前,它都是一个不完全类型.
union:一种节省空间的类
联合(union)是一种特殊的类。一个union可以有多个数据成员,但是在任意时刻只有一个数据成员可以有值。
1.union 不能含有引用类型的成员;
2.union 不能含有虚函数。因为,union既不能继承自其他类,也不能作为基类使用。
在C++11新标准中,含有构造函数或析构函数的类类型也可以作为union的成员类型。union可以为其成员指定public、protected和private等保护标记。默认情况下,union 的成员都是公有的,与struct相同。
定义 union 类型
union 用来定义一组类型不同的互斥值。
// MyUnion 类型的对象只有一个成员,该成员的类型可能是下列类型中的任意一种
union MyUnion {int intValue;float floatValue;char charValue;
};
使用 union 类型
默认情况下union 是未初始化的。
可以使用花括号内的初始值来初始化一个 union,像聚合类一样。
提供的初始值被用于初始化 union 的第一个成员。
使用通用的成员访问运算符来访问一个 union 对象的成员。
为 union 的一个数据成员赋值会令其他成员变为未定义状态。
MyUnion u;
u.intValue = 42;std::cout << "intValue: " << u.intValue << std::endl;
std::cout << "floatValue: " << u.floatValue << std::endl; // 未定义行为
std::cout << "charValue: " << u.charValue << std::endl; // 未定义行为
匿名 union
匿名union 是一个未命名的union,并且在右花括号和分号之间没有任何声明。
在匿名union的定义所在的作用域内该union的成员都是可以直接访问的,因此匿名union不能包含受保护的成员或私有成员,也不能定义成员函数。
含有类类型成员的 union
C++ 允许在联合(union)中包含类类型的成员。这样的联合可以存储不同类类型的对象,但要注意管理它们的生命周期以避免资源泄漏。
#include <iostream>class A {
public:A(int value) : data(value) {}void Print() {std::cout << "A: " << data << std::endl;}
private:int data;
};class B {
public:B(float value) : data(value) {}void Print() {std::cout << "B: " << data << std::endl;}
private:float data;
};union MyUnion {A aObj;B bObj;
};int main() {MyUnion u;u.aObj = A(42);u.aObj.Print();u.bObj = B(3.14f);u.bObj.Print();// 在联合中切换到不同的类对象类型u.aObj = A(100);u.aObj.Print();return 0;
}//定义了两个类 A 和 B,它们分别具有不同的数据成员和成员函数。
//然后,创建了一个联合 MyUnion,它包含两个类类型的成员 aObj 和 bObj。//首先将 aObj 设置为 A 类对象,并调用其 Print 成员函数来显示数据。
//然后将 bObj 设置为 B 类对象,并调用其 Print 成员函数。
//请注意,每次切换联合的成员对象时,上一个成员对象的析构函数会被调用,因此需要小心管理对象的生命周期。
使用类类型的成员的联合通常要更加谨慎,因为需要确保在切换成员对象时正确管理资源和对象的状态。联合通常用于特定的底层编程场景,如需要在不同的类对象之间共享内存或进行位操作时。在一般情况下,最好使用结构体或类来更安全地组织数据。
局部类
定义在某个函数内部的类称为局部类。局部类定义的类型只在定义它的作用域内可见。
局部类的所有成员(包括函数在内)都必须完整定义在类的内部。在局部类中也不允许声明静态数据成员。
局部类不能使用函数作用域中的变量。
局部类只能访问外层作用域定义的类型名、静态变量以及枚举成员。如果局部类定义在某个函数内部,则该函数的普通局部变量不能被该局部类使用。
外层函数对局部类的私有成员没有任何访问特权。当然,局部类可以将外层函数声明为友元;或者更常见的情况是局部类将其成员声明成公有的。
嵌套的局部类
局部类中可以再嵌套一个类,此时嵌套类的定义可以出现在局部类之外,不过必须定义在与局部类相同的作用域中。
局部类中的嵌套类也是一个局部类,必须遵循局部类的各种规定。
固有的不可移植的特性
不可移植的特性是指因机器而异的特性。
位域
类可以将其(非静态)数据成员定义成位域,在一个位域中含有一定数量的二进制位。当一个程序需要向其他程序或硬件设备传递二进制数据时,通常会用到位域。
位域在内存中的布局是与机器相关的。
位域的类型必须是整型或枚举类型。因为带符号位域的行为是由具体实现确定的,所以在通常情况下我们使用无符号类型保存一个位域。
位域的声明形式是在成员名字之后紧跟一个冒号以及一个常量表达式,该表达式用于指定成员所占的二进制位数:
#include <iostream>struct Flags {unsigned int flag1 : 1; // 1位unsigned int flag2 : 2; // 2位unsigned int flag3 : 3; // 3位
};int main() {Flags flags;flags.flag1 = 1; // 设置第1位为1flags.flag2 = 2; // 设置第2、3位为10flags.flag3 = 5; // 设置第4、5、6位为101std::cout << "flag1: " << flags.flag1 << std::endl;std::cout << "flag2: " << flags.flag2 << std::endl;std::cout << "flag3: " << flags.flag3 << std::endl;return 0;
}//定义了一个结构体 Flags,它包含了三个位域成员 flag1、flag2 和 flag3,它们分别具有1位、2位和3位。
//可以使用赋值操作符来设置每个位域的值,并使用位域名称来访问它们。
- 位域成员的位数通常不能超过基础整数类型(如 int)的位数。例如,在大多数平台上,int 是32位,所以位域成员的位数应小于或等于32。
- 位域成员的符号(有符号或无符号)由编译器决定。如果你需要确切的符号性,可以使用 signed 或 unsigned 修饰符。
- 不同编译器的位域布局规则可能不同,所以在跨平台开发时要小心使用位域。
- 位域的可移植性较差,因为它们受限于底层硬件和编译器的实现。如果需要高度可移植的代码,最好避免使用位域。
- 位域通常用于表示标志或状态位,以节省内存。但要小心位域的复杂性和可移植性,确保在需要时使用位域。
- 取地址运算符(&)不能作用于位域,因此任何指针都无法指向类的位域。
volatile 限定符
关键字 volatile 告诉编译器不要对它所修饰的对象进行优化。
volatile int display_register; //该int值可能发生改变
volatile Task *curr_task; //curr_task指向一个volatile对象
volatile int iax[max_size]; //iax的每个元素都是volatile
volatile screen bitmapBuf; //bitmapBuf的每个成员都是volatile
const和 volatile限定符互相没什么影响,某种类型可能既是 const的也是volatile的,此时它同时具有二者的属性。
也可以将成员函数定义为 volatile,只有 volatile 的成员函数才能被 volatile 的对象调用。
可以声明volatile指针、指向volatile对象的指针以及指向volatile对象的volatile指针:
volatile int v; //v是一个volatile int
int *volatile vip; //vip是一个volatile指针,它指向int
volatile int *ivp; //ivp是一个指针,它指向一个volatile int
volatile int *volatile vivp; //vivp是一个volatile指针,它指向一个volatile intint *ip = &v;
合成的拷贝对 volatile 对象无效
不能使用合成的拷贝/移动构造函数及赋值运算符初始化 volatile 对象或从 volatile 对象赋值。
因为合成的成员接受的形参类型是(非volatile)常量引用,不能把一个非 volatile引用绑定到一个volatile对象上。
如果一个类希望拷贝、移动或赋值它的 volatile对象,则该类必须自定义拷贝或移动操作。
class Foo {
public:Foo(const volatile Foo&); // 从一个 volatile 对象进行拷贝Foo& operator=(const volatile Foo&); // 将一个 volatile 对象赋值给一个非 volatile 对象Foo& operator=(const volatile Foo&) volatile; // 将一个 volatile 对象赋值给一个 volatile 对象
}
链接指示:extern “C”
其他语言中的函数名字也必须在C++中进行声明,并且该声明必须指定返回类型和形参列表。对于其他语言编写的函数来说,编译器检查其调用的方式与处理普通C++函数的方式相同,但是生成的代码有所区别。
C++使用链接指示指出任意非C++函数所用的语言。
要想把C++代码和其他语言(包括C语言)编写的代码放在一起使用,要求我们必须有权访问该语言的编译器,并且这个编译器与当前的C++编译器是兼容的。
声明非 C++ 函数
链接指示可以有两种形式:单个的或复合的。链接指示不能出现在类定义或函数定义的内部。同样的链接指示必须在函数的每个声明中都出现。
//可能出现在C++头文件<cstring>中的链接指示
//单语句链接指示
extern "C" size_t strlen (const char *);
//复合语句链接指示
extern "C"{int strcmp (const char*, const char*);char *strcat (char* , const char*);
}
链接指示的第一种形式包含一个关键字extern,后面是一个字符串字面值常量以及一个“普通的”函数声明。
链接指示与头文件
可以令链接指示后面跟上花括号括起来的若干函数的声明,从而一次性建立多个链接。花括号的作用是将适用于该链接指示的多个声明聚合在一起,否则花括号就会被忽略,花括号中声明的函数名字就是可见的,就好像在花括号之外声明的一样。
多重声明的形式可以应用于整个头文件。
//复合语句链接指示
extern "C"{#include <string.h> //操作C风格字符串的C函数
}
当一个#include指示被放置在复合链接指示的花括号中时,头文件中的所有普通函数声明都被认为是由链接指示的语言编写的。链接指示可以嵌套,因此如果头文件包含带自带链接指示的函数,则该函数的链接不受影响。
重要术语
匿名union 未命名的union,不能用于定义对象。匿名union的成员也是外层作用域的成员。匿名union不能包含成员函数,也不能包含私有成员或受保护的成员。
位域 特殊的类成员,该成员含有一个整型值以指定为其分配的二进制位数。如果可能的话,在类中连续定义的位域将被压缩在一个普通的整数值当中。
dynamic_cast 是一个运算符,执行从基类向派生类的带检查的强制类型转换。当基类中至少含有一个虚函数时,该运算符负责检查指针或引用所绑定的对象的动态类型。如果对象类型与目标类型(或其派生类)一致,则类型转换完成。否则,指针转换将返回一个值为0的指针;引用转换将抛出一个异常。
链接指示 支持C++程序调用其他语言编写的函数的一种机制。所有编译器都应支持调用C++和C函数,至于是否支持其他语言则由编译器决定。
不可移植 固有的与机器有关的特性,当程序转移到其他机器或编译器上时需要修改代码。
相关文章:
C++(21):特殊工具与技术
控制内存分配 某些应用程序对内存分配有特殊需求,无法直接应用标准内存管理机制。需要自定义内存分配的细节。 重载 new 和 delete void* operator new(std::size_t size) {// 自定义内存分配逻辑void* ptr std::malloc(size);if (!ptr) {throw std::bad_alloc(…...
go读取yaml,json,ini等配置文件
实际项目中,要读取一些json等配置文件。今天就来说一说,Golang 是如何读取YAML,JSON,INI等配置文件的。 一. go读取json配置文件 JSON 应该比较熟悉,它是一种轻量级的数据交换格式。层次结构简洁清晰 ,易于阅读和编写࿰…...
一、安装GoLang环境和开发工具
一、安装GoLang环境 GoLang中国镜像站 下载后对应的环境包以后,一路下一步就好了,安装路径的话,尽量就安装到默认的文件目录下。 二、配置Go的环境变量 右击此电脑–>属性–>高级系统设置–>环境变量,打开环境变量设置…...
条款40:对并发使用std::atomic,对特种内存使用valatile
可怜的volatile。被误解到如此地步。它甚至不应该出现在本章中,因为它与并发程序设计毫无关系。但是在其他程序设计语言中(Java和C#),它还是会对并发程序设计有些用处。甚至在C++中,一些编译器也已经把volatile投入到染缸,使得它的语义显得可以用于并发软件中(但是仅可用…...
Navicat使用HTTP通道服务器进行连接mysql数据库(超简单三分钟完成),centos安装nginx和php,docker安装nginx+php合并版
序言 因为数据库服务器在外网是不能直接连接访问的,但是可以访问网站,网站后台就能访问数据库,所以在此之前,访问数据库的数据是一件非常麻烦的事情,在平时和运维的交流中发现,他们会使用ssh通道进行连接访…...
图:有向无环图(DAG)
1.有向无环图的定义 有向无环图:若一个有向图中不存在环,则称为有向无环图。 简称DAG图(Directed Acyclic Graph) 顶点中不可能出现重复的操作数。 2.有向无环图的应用 1.描述算数表达式 用有向无环图描述算术表达式。 解题步骤: 把各个操作数不重…...
Python入门教程 - 基本语法 (一)
目录 一、注释 二、Python的六种数据类型 三、字符串、数字 控制台输出练习 四、变量及基本运算 五、type()语句查看数据的类型 六、字符串的3种不同定义方式 七、数据类型之间的转换 八、标识符命名规则规范 九、算数运算符 十、赋值运算符 十一、字符串扩展 11.1…...
使用PAM保障开发运营安全
硬编码凭据和 DevOps 系统中缺乏凭据安全性是组织的巨大漏洞。以明文形式访问凭据的恶意内部人员可以在 IT 中建立和扩展其立足点 基础设施,构成巨大的数据被盗风险。 什么是PAM 特权访问管理 (PAM) 是指一组 IT 安全管理原则,可…...
《Go 语言第一课》课程学习笔记(十二)
函数 Go 函数与函数声明 在 Go 语言中,函数是唯一一种基于特定输入,实现特定任务并可返回任务执行结果的代码块(Go 语言中的方法本质上也是函数)。在 Go 中,我们定义一个函数的最常用方式就是使用函数声明。 第一部…...
【深入浅出C#】章节10: 最佳实践和性能优化:编码规范和代码风格
编码规范和代码风格之所以重要,是因为它们直接影响到软件开发的质量、可维护性、可读性和协作效率。编码规范和代码风格是编程中的关键要素,它们有助于编写高质量、可维护和易读的代码,提高团队协作效率,减少错误,降低…...
LNMP架构:搭建Discuz论坛
文章目录 1. 编译安装Nginx1.1 前置准备1.2 编译安装1.3 添加nginx系统服务 2.编译安装MySql2.1 前置准备2.2 编译安装2.3 修改mysql 配置文件2.4 设置路径环境变量2.5 初始化数据库2.6 添加musql系统服务2.7 修改MySql登录密码 3. 编译安装PHP3.1 前置准备3.2 编译安装3.3 复制…...
详解Numpy(基于jupyter notebook)
详解Numpy(基于jupyter notebook) 1.创建数组2.数据类型3.数组切片和索引4.Numpy的广播与数组操作5.数组合并与通用函数6.其他通用函数 1.创建数组 #引入numpy包,以后np就代表numpy import numpy as npanp.arange(10,30,2)#10为起点…...
nvm集合node版本,解决新版本jeecgboot3.5.3前端启动失败问题
jeecgboot前端3.5.3页面如下 使用之前的pnpm启动会报错,pnpm是node进行安装的,查询后发现,vue3版本的页面至少需要node16版本,我之前的版本只有15.5,适用于vue2 那么我将先前的node15.5版本删除,然后安装…...
Windows命令行初步:更改配色、提示符以及编码方式
文章目录 启动和退出窗口标题和提示符命令行颜色更改编码 启动和退出 按下winR,调出运行窗口,输入cmd就可以进入命令行了。在Win10以前的系统种,如果在命令行中再输入一个cmd,就会再打开一个命令行。但最近的Win11版本中…...
uniapp onLoad生命周期 uni.$on接受参数无法改变data数据解决办法
问题阐述: a: uni.$emit(name,data)uni.navigateTo({url:b})b:onload(){ uni.$on(name,(res)>{ this.nameres console.log(this.name) })}用以上写法来跨页面传参会发现在b页面,虽然能够接受到参数但是赋值到data时候没生效,虽然控制台能…...
Android Camera开发入门(4):USB/UVC Camera的使用
Android Camera开发入门(4):USB/UVC Camera的使用 本文基于开源项目https://github.com/saki4510t/UVCCamera之上进行二次封装和使用 在前几篇文章中,我们介绍了Camera到CameraX的基础功能应用,同时附上了相关代码,需要的源码的大佬们可以滑到最底部获取。 本篇我们一起…...
Java网络爬虫——jsoup快速上手,爬取京东数据。同时解决‘京东安全’防爬问题
文章目录 介绍jsoup使用1.解析url,获取前端代码2.解决京东安全界面跳转3.获取每一组的数据4.获取商品数据的具体信息4.最终代码 介绍 网络爬虫,就是在浏览器上,代替人类爬取数据,Java网络爬虫就是通过Java编写爬虫代码࿰…...
外观模式:简化复杂子系统的访问与使用
文章目录 1. 简介2. 外观模式的基本结构3. 外观模式的实现步骤4. 外观模式的应用与实例4.1 图形界面库的外观模式应用4.2 文件压缩与解压缩的外观模式应用4.3 订单处理系统的外观模式应用 5. 外观模式的优缺点5.1 优点5.2 缺点 6. 总结 1. 简介 外观模式是一种结构型设计模式&…...
代码随想录day38|509. 斐波那契数70. 爬楼梯746. 使用最小花费爬楼梯
509. 斐波那契数 class Solution:def fib(self, n: int) -> int:#dp含义,递推公式,dp初始化,遍历顺序,打印dpif n 0:return 0dp [0] * (n1)dp[0]0dp[1]1for i in range(2,n1):dp[i] dp[i-1] dp[i-2]return dp[n] 70. 爬楼梯…...
UE5 C++ UGameInstance 功能、作用及应用
# UE5 C UGameInstance 功能及作用 网上有很多文章介绍,例如在游戏中只有一个实例,换关卡不会丢失等。暂时省略。 # UE5 C UGameInstance 应用 ## 应用一,UE5 C UGameInstance 里监听player创建事件 UWebSocketGameInstance.h里的定义 …...
强化学习在并行机构人形机器人控制中的应用
1. 项目概述在机器人控制领域,强化学习(RL)正逐渐成为解决复杂动力学系统问题的有力工具。然而,当面对具有并行驱动机构的人形机器人时,传统RL训练方法往往面临一个关键挑战:大多数仿真环境无法准确模拟闭环运动链(Closed Kinemat…...
Taotoken的TokenPlan套餐如何实现更经济的模型调用
🚀 告别海外账号与网络限制!稳定直连全球优质大模型,限时半价接入中。 👉 点击领取海量免费额度 Taotoken的TokenPlan套餐如何实现更经济的模型调用 1. 理解TokenPlan的计费模式 在模型应用开发过程中,成本的可预测性…...
DeepSeek基准测试避坑手册:92%开发者忽略的4大陷阱——硬件配置偏差、tokenizer不一致、batch size幻觉、温度值污染
更多请点击: https://codechina.net 第一章:DeepSeek基准测试避坑手册:92%开发者忽略的4大陷阱——硬件配置偏差、tokenizer不一致、batch size幻觉、温度值污染 硬件配置偏差:GPU显存与计算精度的隐性干扰 在A100(8…...
串口通信粘包问题:成因深度解析与项目实战解决方案
在嵌入式开发、工业工控、上位机下位机交互项目中,串口(RS232/RS485)是最基础、最常用的通信方式。绝大多数开发者都遇到过这样的问题:串口接收的数据偶尔错乱、解析报错、数据拼接异常,单次接收的数据时而半包、时而多…...
SAP-ABAP:变量、常量、结构与内表声明(10篇博客合集) 第五篇:声明时的键值设计技巧:结构与内表的主键、非主键配置指南
变量、常量、结构与内表声明(10篇博客合集) 第五篇:声明时的键值设计技巧:结构与内表的主键、非主键配置指南如果把内表比作一张内存中的“数据库表”,那么键就是这张表的索引甚至主键。键的设计直接决定了数据的唯一性…...
光轮智能 谢晨 访谈总结机器人仿真数据产业
光轮智能 谢晨 访谈总结机器人仿真关于创始人关于数据数据金字塔数据痛点仿真数据的重要性仿真数据的质量b站链接地址公司官网关于创始人 清华物理;哥伦比亚金融;英伟达智驾仿真;小鹏智驾仿真;现为光轮智能CEO 关于数据 数据的…...
从游戏引擎到仿真平台:手把手教你用AirSim+UE4搭建你的第一个无人机/自动驾驶仿真环境
从游戏引擎到仿真平台:构建AirSimUE4无人机与自动驾驶仿真环境实战指南当游戏引擎遇上机器人算法测试,会碰撞出怎样的火花?微软开源的AirSim项目将虚幻引擎(Unreal Engine)从游戏开发领域引入到自动驾驶和无人机研究的…...
Unity项目DrawCall降不下来?试试用Mesh Baker合并贴图集,保姆级图文教程
Unity性能优化实战:用Mesh Baker合并贴图集降低DrawCall全流程解析当你的Unity项目帧率开始卡顿,Profiler里DrawCall数字居高不下时,合并贴图集往往是解决问题的关键一步。本文将以一个实际项目为例,带你从零开始使用Mesh Baker的…...
告别杂乱!用FileMenu Tools 8.4.2一键清理Windows 11右键菜单(附隐藏技巧)
Windows 11右键菜单精简指南:用FileMenu Tools打造高效工作流每次在文件上点击右键时,那个缓慢弹出的冗长菜单是否让你感到烦躁?随着安装的软件越来越多,Windows的右键菜单往往会变得臃肿不堪,严重影响工作效率。今天&…...
告别手动复制!用这个自定义编辑器脚本一键备份/克隆Unity Terrain Data
告别手动复制!用这个自定义编辑器脚本一键备份/克隆Unity Terrain Data在Unity关卡设计和技术美术的工作流中,地形数据的灵活复用往往意味着反复的手动操作——导出高度图、备份材质参数、复制植被分布,每个环节都可能成为效率瓶颈。想象这样…...
