ATL布幔之下的秘密(1)

python——体育收入排行2012-2019 请分析文件并读取数据,完成以下功能: 1. 某年的收入排名前若干名选手 如果输入n是2012-19之间的年份,则可以输入整数k,输出n年的前k名排名信息。如果k大于当年排名运动员人数,则输出当年所有的信息。每行一名运动员信息,以' | '间隔该行各项信息。 2. 某年属于某个运动项目的所有运动员信息,和收入总和 如果输入的是'sport'(不区分大小写),则可以继续输入一个年份201*,输出一个当年排行中的运动项目菜单,按照字符串升序排列,格式见示例,再输入运动项目对应的数字来输出该项运动当 阅读详情

ATL布幔之下的秘密(1)

作者:Zeeshan Amjad
译者:李马 (home.nuc.edu.cn/~titilima )
原文出处: http://www.codeproject.com/atl/atl_underthehood_.asp

介绍
   在本系列的教程中,我要讨论一些ATL的内部工作方式以及它所使用的技术。
   在讨论的开始,让我们先看看一个程序的内存分布。首先,编写一个简单的程序,它没有任何的数据成员,你可以看看它的内存结构。
程序1.

#include <iostream>
using namespace std;

class Class {
};

int main() {
  Class objClass;
  cout << "Size of object is = " << sizeof(objClass) << endl;
  cout << "Address of object is = " << &objClass << endl;
  return 0;
}
这个程序的输出为:
Size of object is = 1
Address of object is = 0012FF7C
现在,如果我们向类中添加一些数据成员,那么这个类的大小就会是各个成员的大小之和。对于模板,也依然是这样:
程序2.
#include <iostream>
using namespace std;

template <typename T>
class CPoint {
public:
  T m_x;
  T m_y;
};

int main() {
  CPoint<int> objPoint;
  cout << "Size of object is = " << sizeof(objPoint) << endl;
  cout << "Address of object is = " << &objPoint << endl;
  return 0;
}
现在程序的输出为:
Size of object is = 8
Address of object is = 0012FF78
那么,再向程序中添加继承。现在我们使Point3D类继承自Point类,然后来看看程序的内存结构:

程序3.
#include <iostream>
using namespace std;

template <typename T>
class CPoint {
public:
  T m_x;
  T m_y;
};

template <typename T>
class CPoint3D : public CPoint<T> {
public:
  T m_z;
};

int main() {
  CPoint<int> objPoint;
  cout << "Size of object Point is = " << sizeof(objPoint) << endl;
  cout << "Address of object Point is = " << &objPoint << endl;

  CPoint3D<int> objPoint3D;
  cout << "Size of object Point3D is = " << sizeof(objPoint3D) << endl;
  cout << "Address of object Point3D is = " << &objPoint3D << endl;

  return 0;
}
程序的输出为:
Size of object Point is = 8
Address of object Point is = 0012FF78
Size of object Point3D is = 12
Address of object Point3D is = 0012FF6C
这一程序演示了派生类的内存结构,它表明派生类的对象所占据的内存为它本身的数据成员和它基类的成员之和。
当虚函数加入到这个派对中的时候,一切就变得都有意思了。请看下面的程序:

程序4.
#include <iostream>
using namespace std;

class Class {
public:
  virtual void fun() { cout << "Class::fun" << endl; }
};

int main() {
  Class objClass;
  cout << "Size of Class = " << sizeof(objClass) << endl;
  cout << "Address of Class = " << &objClass << endl;
  return 0;
}
程序的输出为:
Size of Class = 4
Address of Class = 0012FF7C
并且,在我们添加了多于一个的虚函数之后,会变得更加有趣:

程序5.
#include <iostream>
using namespace std;

class Class {
public:
  virtual void fun1() { cout << "Class::fun1" << endl; }
  virtual void fun2() { cout << "Class::fun2" << endl; }
  virtual void fun3() { cout << "Class::fun3" << endl; }
};

int main() {
  Class objClass;
  cout << "Size of Class = " << sizeof(objClass) << endl;
  cout << "Address of Class = " << &objClass << endl;
  return 0;
}
这个程序的输出和前一个程序一模一样,让我们再做一个实验来更好地弄懂这件事吧。

程序6.
#include <iostream>
using namespace std;

class CPoint {
public:
  int m_ix;
  int m_iy;
  virtual ~CPoint() { } // 译注:原文此处有分号,我将其去掉,下皆同
};

int main() {
CPoint objPoint;
cout << "Size of Class = " << sizeof(objPoint) << endl;
cout << "Address of Class = " << &objPoint << endl;
return 0;
}
程序的输出为:
Size of Class = 12
Address of Class = 0012FF68
这些程序的输出表明,当你向类中添加了虚函数之后,那么它的尺寸就会增加一个int的大小。在Visual C++中也就是增加4个字节。这就意味着这个类中有三个整数大小的位置,一个是x,一个是y,另一个是处理虚函数之用的虚函数表指针。首先,让我们来看看这个新的位置,也就是这个位于对象首部(或末尾)的虚函数表指针。要这么做的话,我们需要直接存取对象所占用的内存。我们可以使用神奇的指针技术,即用一个指向int的指针来存储一个对象的地址。

程序7.
#include <iostream>
using namespace std;

class CPoint {
public:
  int m_ix;
  int m_iy;
  CPoint(const int p_ix = 0, const int p_iy = 0) : 
    m_ix(p_ix), m_iy(p_iy) {
  }
  int getX() const {
    return m_ix;
  }
  int getY() const {
    return m_iy;
  }
  virtual ~CPoint() { }
};

int main() {
  CPoint objPoint(5, 10);

  int* pInt = (int*)&objPoint;
  *(pInt+0) = 100; // 企图改变x的值
  *(pInt+1) = 200; // 企图改变y的值

  cout << "X = " << objPoint.getX() << endl;
  cout << "Y = " << objPoint.getY() << endl;

  return 0;
}
这个程序中最重要的东西是:
int* pInt = (int*)&objPoint;
  *(pInt+0) = 100; // 企图改变x的值
  *(pInt+1) = 200; // 企图改变y的值
其中,我们在把对象的地址存入一个整型指针之后,就可以把这个对象看作一个整型的指针了。程序的输出为:
X = 200
Y = 10
当然,这并不是我们想要的结果,它表明200存储在了m_ix数据成员的位置。这就意味着m_ix,也就是第一个成员变量,是起始于内存中第二个位置的,而不是第一个。换句话说,第一个成员是虚函数表指针,其它的才是对象的数据成员。那么,只需要修改以下的两行:
int* pInt = (int*)&objPoint;
*(pInt+1) = 100; // 企图改变x的值
*(pInt+2) = 200; // 企图改变y的值
这样我们就会获得想要的结果了,以下为完整程序:

程序8.
#include <iostream>
using namespace std;

class CPoint {
public:
  int m_ix;
  int m_iy;
  CPoint(const int p_ix = 0, const int p_iy = 0) : 
    m_ix(p_ix), m_iy(p_iy) { 
  }
  int getX() const {
    return m_ix;
  }
  int getY() const {
    return m_iy;
  }
  virtual ~CPoint() { }
};

int main() {
  CPoint objPoint(5, 10);

  int* pInt = (int*)&objPoint;
  *(pInt+1) = 100; // 企图改变x的值
  *(pInt+2) = 200; // 企图改变y的值

  cout << "X = " << objPoint.getX() << endl;
  cout << "Y = " << objPoint.getY() << endl;

  return 0;
}
并且,程序的输出为:
X = 100
Y = 200
下图清楚地示范了当我们向类中添加了虚函数之后,虚函数表指针就会被添加在内存结构中的第一个位置。 


现在问题出现了:虚函数表指针中到底存储了什么?那么看看下面的程序:

程序9.
#include <iostream>
using namespace std;

class Class {
  virtual void fun() { cout << "Class::fun" << endl; }
};

int main() {
  Class objClass;

  cout << "Address of virtual pointer " << (int*)(&objClass+0) << endl;
  cout << "Value at virtual pointer " << (int*)*(int*)(&objClass+0) << endl;
  return 0;
}
程序的输出为:
Address of virtual pointer 0012FF7C
Value at virtual pointer 0046C060
虚函表数指针存储了一个虚函数表的地址。并且,虚函数表存储了类中所有虚函数的地址。换句话说,虚函数表是一个数组,这个数组的元素为虚函数指针的地址。让我们看看下面这个程序来考虑吧。

程序10.
#include <iostream>
using namespace std;

class Class {
  virtual void fun() { cout << "Class::fun" << endl; }
};

typedef void (*Fun)(void);

int main() {
  Class objClass;

  cout << "Address of virtual pointer " << (int*)(&objClass+0) << endl;
  cout << "Value at virtual pointer i.e. Address of virtual table " 
     << (int*)*(int*)(&objClass+0) << endl;
  cout << "Value at first entry of virtual table " 
     << (int*)*(int*)*(int*)(&objClass+0) << endl;
  cout << endl << "Executing virtual function" << endl << endl;
  Fun pFun = (Fun)*(int*)*(int*)(&objClass+0);
  pFun();
  return 0;
}
这个程序中有一些使用了类型转换的间接调用,其中最重要的一行是:
Fun pFun = (Fun)*(int*)*(int*)(&objClass+0);
在这里,Fun为一个由typedef定义的函数指针类型:
typedef void (*Fun)(void);
让我们来剖析一下这个冗长的间接调用。(int*)(&objClass+0)给出了虚函数表指针的地址,这个虚函数表指针是类的第一个入口,我们将它转换为了int*。要获得这个地址的值,我们需要使用间接调用运算符(也就是*),然后将它再次转换为int*,也就是(int*)*(int*)(&objClass+0)。这就会给出虚函数表的第一个入口。要获得这个位置的值,也就是获得类中第一个虚函数的地址,我们需要再次使用间接调用运算符,并将其转换为合适的函数指针类型,所以
Fun pFun = (Fun)*(int*)*(int*)(&objClass+0);
就表示从虚函数表的第一个入口获得值,作Fun的类型转换,并将其存入pFun之中。 

那么,当类中含有多于一个的虚函数,又会怎么样呢?现在我们希望存取虚函数表中的第二个虚函数,请看下面的程序:

程序11.
#include <iostream>
using namespace std;

class Class {
  virtual void f() { cout << "Class::f" << endl; }
  virtual void g() { cout << "Class::g" << endl; }
};

int main() {
  Class objClass;

  cout << "Address of virtual pointer " << (int*)(&objClass+0) << endl;
  cout << "Value at virtual pointer i.e. Address of virtual table " 
    << (int*)*(int*)(&objClass+0) << endl;

  cout << endl << "Information about VTable" << endl << endl;
  cout << "Value at 1st entry of VTable " 
    << (int*)*((int*)*(int*)(&objClass+0)+0) << endl;
  cout << "Value at 2nd entry of VTable " 
    << (int*)*((int*)*(int*)(&objClass+0)+1) << endl;

  return 0;
}
程序的输出为:
Address of virtual pointer 0012FF7C
Value at virtual pointer i.e. Address of virtual table 0046C0EC

Information about VTable

Value at 1st entry of VTable 0040100A
Value at 2nd entry of VTable 0040129E

那么,有一个问题很自然地出现了——编译器是如何知道虚函数表的长度的呢?答案是:虚函数表的最后一个入口为NULL。你可以把程序改一改来考虑这个问题。

程序12.
#include <iostream>
using namespace std;

class Class {
  virtual void f() { cout << "Class::f" << endl; }
  virtual void g() { cout << "Class::g" << endl; }
};

int main() {
  Class objClass;

  cout << "Address of virtual pointer " << (int*)(&objClass+0) << endl;
  cout << "Value at virtual pointer i.e. Address of virtual table " 
     << (int*)*(int*)(&objClass+0) << endl;

  cout << endl << "Information about VTable" << endl << endl;
  cout << "Value at 1st entry of VTable " 
     << (int*)*((int*)*(int*)(&objClass+0)+0) << endl;
  cout << "Value at 2nd entry of VTable " 
     << (int*)*((int*)*(int*)(&objClass+0)+1) << endl;
  cout << "Value at 3rd entry of VTable " 
     << (int*)*((int*)*(int*)(&objClass+0)+2) << endl;
  cout << "Value at 4th entry of VTable " 
     << (int*)*((int*)*(int*)(&objClass+0)+3) << endl;

  return 0;
}
程序的输出为:
Address of virtual pointer 0012FF7C
Value at virtual pointer i.e. Address of virtual table 0046C134

Information about VTable

Value at 1st entry of VTable 0040100A
Value at 2nd entry of VTable 0040129E
Value at 3rd entry of VTable 00000000
Value at 4th entry of VTable 73616C43
这个程序的输出示范了虚函数表的最后一个入口为NULL。就让我们来用已有的知识来调用虚函数吧:
 

程序13.
#include <iostream>
using namespace std;

class Class {
  virtual void f() { cout << "Class::f" << endl; }
  virtual void g() { cout << "Class::g" << endl; }
};

typedef void(*Fun)(void);

int main() {
  Class objClass;

  Fun pFun = NULL;

  // 调用第一个虚函数
  pFun = (Fun)*((int*)*(int*)(&objClass+0)+0);
  pFun();

  // 调用第二个虚函数
  pFun = (Fun)*((int*)*(int*)(&objClass+0)+1);
  pFun();

  return 0;
}
程序的输出为:
Class::f
Class::g
现在我们来看看多重继承的情况。先看一个多重继承最简单的情况:

程序14.
#include <iostream>
using namespace std;

class Base1 {
public:
  virtual void f() { }
};

class Base2 {
public:
  virtual void f() { }
};

class Base3 {
public:
  virtual void f() { }
};

class Drive : public Base1, public Base2, public Base3 {
};

int main() {
  Drive objDrive;
  cout << "Size is = " << sizeof(objDrive) << endl;
  return 0;
}
程序的输出为:
Size is = 12

这个程序示范了当你从多个基类继承一个类的时候,这个派生类就会拥有所有基类的虚函数表指针。
 


那么,当派生类也有虚函数表指针的时候会发生什么事情呢?让我们看看下面的程序来弄懂关于带有虚函数的多重继承的概念吧。

程序15.

#include <iostream>
using namespace std;

class Base1 {
  virtual void f() { cout << "Base1::f" << endl; }
  virtual void g() { cout << "Base1::g" << endl; }
};

class Base2 {
  virtual void f() { cout << "Base2::f" << endl; }
  virtual void g() { cout << "Base2::g" << endl; }
};

class Base3 {
  virtual void f() { cout << "Base3::f" << endl; }
  virtual void g() { cout << "Base3::g" << endl; }
};

class Drive : public Base1, public Base2, public Base3 {
public:
  virtual void fd() { cout << "Drive::fd" << endl; }
  virtual void gd() { cout << "Drive::gd" << endl; }
};

typedef void(*Fun)(void);

int main() {
  Drive objDrive;

  Fun pFun = NULL;

  // 调用Base1的第一个虚函数
  pFun = (Fun)*((int*)*(int*)((int*)&objDrive+0)+0);
  pFun();

  // 调用Base1的第二个虚函数
  pFun = (Fun)*((int*)*(int*)((int*)&objDrive+0)+1);
  pFun();

  // 调用Base2的第一个虚函数
  pFun = (Fun)*((int*)*(int*)((int*)&objDrive+1)+0);
  pFun();

  // 调用Base2的第二个虚函数
  pFun = (Fun)*((int*)*(int*)((int*)&objDrive+1)+1);
  pFun();

  // 调用Base3的第一个虚函数
  pFun = (Fun)*((int*)*(int*)((int*)&objDrive+2)+0);
  pFun();

  // 调用Base3的第二个虚函数
  pFun = (Fun)*((int*)*(int*)((int*)&objDrive+2)+1);
  pFun();

  // 调用派生类的第一个虚函数
  pFun = (Fun)*((int*)*(int*)((int*)&objDrive+0)+2);
  pFun();

  // 调用派生类的第二个虚函数
  pFun = (Fun)*((int*)*(int*)((int*)&objDrive+0)+3);
  pFun();

  return 0;
}
程序的输出为:
Base1::f
Base1::g
Base2::f
Base2::f
Base3::f
Base3::f
Drive::fd
Drive::gd

我们可以通过使用static_cast获得派生类虚函数表指针的偏移量,请看以下程序:

程序16.
#include <iostream>
using namespace std;

class Base1 {
public:
  virtual void f() { }
};

class Base2 {
public:
  virtual void f() { }
};

class Base3 {
public:
  virtual void f() { }
};

class Drive : public Base1, public Base2, public Base3 {
};

// 任意的非0值,因为0乘任何数都得0
#define SOME_VALUE 1

int main() {
  cout << (DWORD)static_cast<Base1*>((Drive*)SOME_VALUE)-SOME_VALUE << endl;
  cout << (DWORD)static_cast<Base2*>((Drive*)SOME_VALUE)-SOME_VALUE << endl;
  cout << (DWORD)static_cast<Base3*>((Drive*)SOME_VALUE)-SOME_VALUE << endl;
  return 0;
}
ATL使用了一个定义在ATLDEF.H中的宏offsetofclass来做这件事,这个宏被定义为:
#define offsetofclass(base, derived) /
       ((DWORD)(static_cast<base*>((derived*)_ATL_PACKING))-_ATL_PACKING)
这个宏返回了在派生类对象模型中基类虚函数表指针的偏移量,让我们来看看下面这个例子:

程序17.
#include <windows.h>
#include <iostream>
using namespace std;

class Base1 {
public:
  virtual void f() { }
};

class Base2 {
public:
  virtual void f() { }
};

class Base3 {
public:
  virtual void f() { }
};

class Drive : public Base1, public Base2, public Base3 {
};

#define _ATL_PACKING 8

#define offsetofclass(base, derived) /
  ((DWORD)(static_cast<base*>((derived*)_ATL_PACKING))-_ATL_PACKING)

int main() {
  cout << offsetofclass(Base1, Drive) << endl;
  cout << offsetofclass(Base2, Drive) << endl;
  cout << offsetofclass(Base3, Drive) << endl;
  return 0;
}
派生类的内存布局为:

程序的输出为:
0
4
8
这个程序的输出示范了这个宏返回指定基类的虚函数表指针偏移量。在Don Box的《COM本质论》中,它使用了一个简单的的宏,你可以修改这个程序,用Box的宏替换ATL的宏。

程序18.
#include <windows.h>
#include <iostream>
using namespace std;

class Base1 {
public:
  virtual void f() { }
};

class Base2 {
public:
  virtual void f() { }
};

class Base3 {
public:
  virtual void f() { }
};

class Drive : public Base1, public Base2, public Base3 {
};

#define BASE_OFFSET(ClassName, BaseName) /
  (DWORD(static_cast<BaseName*>(reinterpret_cast<ClassName*>/
  (0x10000000))) - 0x10000000)

int main() {
  cout << BASE_OFFSET(Drive, Base1) << endl;
  cout << BASE_OFFSET(Drive, Base2) << endl;
  cout << BASE_OFFSET(Drive, Base3) << endl;
  return 0;
}
这一程序的目的和输出与前一个程序完全相同。
   现在让我们用这个宏来做些特别的东西,事实上我们可以通过获得派生类内存结构中基类虚函数表指针的偏移量的方法来调用指定基类中的虚函数。

程序19.
#include <windows.h>
#include <iostream>
using namespace std;

class Base1 {
public:
  virtual void f() { cout << "Base1::f()" << endl; }
};

class Base2 {
public:
  virtual void f() { cout << "Base2::f()" << endl; }
};

class Base3 {
public:
  virtual void f() { cout << "Base3::f()" << endl; }
};

class Drive : public Base1, public Base2, public Base3 {
};

#define _ATL_PACKING 8

#define offsetofclass(base, derived) /
  ((DWORD)(static_cast<base*>((derived*)_ATL_PACKING))-_ATL_PACKING)

int main() {
  Drive d;

  void* pVoid = NULL;

  // 调用Base1的函数
  pVoid = (char*)&d + offsetofclass(Base1, Drive);
  ((Base1*)(pVoid))->f();

  // 调用Base2的函数
  pVoid = (char*)&d + offsetofclass(Base2, Drive);
  ((Base2*)(pVoid))->f();

  // 调用Base3的函数(译注:原文为Base1)
  pVoid = (char*)&d + offsetofclass(Base3, Drive);
  ((Base3*)(pVoid))->f();

  return 0;
}
程序的输出为:
Base1::f()
Base2::f()
Base3::f()
在本章教程中,我尝试着解释了ATL中offsetofclass宏的工作方式。我希望在下一篇文章中,继续探究ATL中其它的秘密。

数据分析案例-2024 年全电动汽车数据集可视化分析 本数据集来源于Kaggle,原始数据集共有177866条数据,17个变量。该数据集显示了目前通过华盛顿州许可部 (DOL) 注册的电池电动汽车 (BEV) 和插电式混合动力电动汽车 (PHEV)。电池电动汽车 (BEV) 是一种全电动汽车,使用一个或多个电池来存储电能,为电机提供动力,并通过将车辆插入电源进行充电。插电式混合动力汽车 (PHEV) 是一种使用一个或多个电池为电动机提供动力的车辆;使用另一种燃料,例如汽油或柴油,为内燃机或其他推进源提供动力;并通过将车辆插入电源来充电。 阅读详情

相关推荐

ARC学习(6)arc 编译器overlap 地址重叠方式使用

摘要:本文介绍了ARC编译器中使用overlap地址重叠的方法。

张一西的博客 718

C++类对应的内存结构

提示1:对“内存结构”表示有疑问或不解的,先参考:http://blog.youkuaiyun.com/guogangj/archive/2007/05/25/1625199.aspx,本文使用的表示方法和VC6的Memory视图一致,即:左上表示低位。 提示2:下文提到的“类大小”严格上来说是该类经过实例化的对象的大小。当然了,光研究长度的话,两者差别不大,因为:CClassA objA,s

*无名小卒* 1万+

实施文档

流程管理 2008-04-23 09:44:00 874 收藏 3 分类专栏: 企业管理杂谈 文章标签: 文档 文档管理 项目管理 工作 配置管理 工具 项目实施文档是用来记录、描述、展示实施过程中一系列信息的处理过程,通过书面或图示的形式对项目活动过程或结果进行描述、定义、规定及报告。例如分阶段实施计划文档,标准业务流程文档,标准编码及数据文档,标准参数设置文档,功能操作指南文档。这些文档将会伴随着ERP实施的各个阶段逐渐充实、完善,也同时记载了整个实施的过程和成果。 作为实施顾问人员,在实施过程

qq_52337172的博客 1062

C++类对象的内存结构

本文全文转载于:https://blog.youkuaiyun.com/fenxinzi557/article/details/51995911 (1) 无虚函数继承 (2)有一个虚函数继承 (3)全部虚函数都继承 (4)多重继承 (6)菱形继承 (7)单一虚继承 (8)菱形虚继承(最重要) 结论: 在VS中查看类对象的内存布局的方法:右击源文件——>属性—...

蓬莱道人的博客 5320

COMSOL仿真避坑指南:列管反应器模拟中多孔介质与化学反应设置的5个常见错误

本文详细解析了COMSOL仿真中列管反应器模拟的5个常见错误,包括多孔介质模型选择、化学反应接口配置、对流扩散方程处理、薄层热阻设置及网格独立性验证。特别针对多孔介质与化学反应设置的误区,提供了实用的判断标准和修正方法,帮助工程师避免计算偏差,提升仿真精度。

weixin_29597551的博客 321

三大主流交易接口实战指南:XTP、CTP与数字货币API的实盘接入解析

本文深入解析了量化交易系统实现实盘交易的关键环节,聚焦于三大主流交易接口的实战接入。详细介绍了证券领域的XTP、期货领域的CTP以及数字货币交易所API的核心特性、代码封装要点与风控实践,并提供了多市场接口整合的统一架构方案,为开发者打通量化交易的“最后一公里”提供了一份接地气的实战指南。

win55的博客 1376

C++类的存储及类对象内存结构

本文分两部分,前半部分讲类的存储后半部分讲类的内存结构。 C++类的存储 c++中最重要的就是类,那么一个类的对象,它在内存中如何存储的?它占 内存中多少个字节? 首先确定类的构成: 1,数据成员:可以是内置类型,类类型。 2,函数成员:虚函数,非虚函数 1)数据成员 内置类型对齐原则 内置类型就是常用的:char,short,long,int,float,double. 这些内

fenxinzi557的博客 2万+

grafana使用方法

1、点击仪表盘2、新建仪表盘3、选择数据源4、选择查询的条件5、美化。

2403_87359664的博客 323

内存(三)--类的实例化占用

转)提示2:下文提到的“类大小”严格上来说是该类经过实例化的对象的大小。当然了,光研究长度的话,两者差别不大,因为:CClassA objA,sizeof(CClassA)和sizeof(objA)得到的结果都是一样的。 一、真空类class CNull{}; 长度:1内存结构:?? 评注:长度其实为0,这个字节作为内容没有意义,可能每次都不一样。 

suiyueranxin的专栏 604

FLUX.1 LoRa 微调

微调的好处:可以让你突破换脸这种输入图片的场景约束,微调好的模型可以让你真正的文生图。换脸只能根据输入的图片和视频来生成内容。

szmars的博客 3621

TypeScript版LangChain环境配置全攻略:从零搭建AI应用开发环境

大语言模型(LLM)作为当前AI应用开发的核心技术,通过API接口为开发者提供了强大的自然语言处理能力。其工作原理基于Transformer架构,通过预训练和微调实现对人类语言的深度理解与生成。在工程实践中,开发者需要将LLM与外部工具、数据源和业务逻辑有效集成,这正是LangChain框架的技术价值所在——它提供了标准化的组件和接口,简化了复杂AI应用的构建流程。应用场景涵盖智能客服、内容生成、数据分析助手等多个领域。本文聚焦于TypeScript/JavaScript生态下的LangChain开发,详细

weixin_29055699的博客 253

C++类对象模型之内存布局

1、C++类对象的内存布局      在C++的类对象中,有两种类的成员变量:static和非static,有三种成员函数:static、非static和virtual。那么,它们在C++的内存中是如何分布的呢?       C++程序的内存格局通常分为四个区:全局数据区(data area),代码区(code area),栈区(stack ...

Twoton的博客 1523

4-都2026年了,嵌入式发展前景怎么样,还适合入行吗?

本文系统分析了嵌入式行业的发展前景,指出到2026年嵌入式将迎来由"万物智联"驱动的黄金周期。文章回顾了嵌入式技术的四个演进阶段,从专用计算机到AIoT时代,强调其技术积累的延续性。作者从人才流向、产业智能化转型、高校新增专业等角度论证嵌入式行业的蓬勃发展,并提出"铁三角"模型分析其驱动力和护城河。最后给出职业发展路径建议,认为嵌入式适合长期主义者,在智能汽车、边缘计算等领域有广阔前景。文章建议读者抓住产业升级机遇,选择这一稳定且有复利效应的技术赛道。

指尖动听知识库 389

ATL布幔下的秘密之窗口类的秘密

介绍   很多人认为ATL只是用来编写COM组件的,其实你也可以使用ATL中的窗口类来创建基于窗口的应用程序。虽然你可以将基于MFC的程序转换为ATL,但是ATL中对于UI(译注:用户界面)组件的支持太少了。所以,这就要求你需要自己编写很多代码。例如,在ATL中没有文档/视图,所以在你想使用它的时候就需要自己实现了。在本篇中,我们将要探究一些关于窗口类的秘密,以及ATL技术实现的秘密。WTL(Wi

★果冻★的专栏 1514

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

ATL布幔下的秘密之模板技术

 介绍   如果你是个模板的高手,你就可以将ATL的学习作为一种享受。在这一节中,我将要尝试解释一些ATL使用的模板技术。我不能保证你读完本节后能成为一个模板高手,只能是尽我所能让你在读完本文后能够更轻松地理解ATL的源码。   程序35. #include <iostream>using namespace std;template

★果冻★的专栏 1377

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

ATL布幔之下的秘密(5)

原作者:Zeeshan Amjad原文链接:http://www.codeproject.com/atl/atl_underthehood_5.asp点这里下载本文配套源代码介绍  很多人认为ATL只是用来编写COM组件的,其实你也可以使用ATL中的窗口类来创建基于窗口的应用程序。虽然你可以将基于MFC的程序转换为ATL,但是ATL中对于UI(译注:用户界面)组件的支持太少了。所以,这就要求你需要

马说@优快云 2805

ATL布幔之下的秘密(2)

原作者:Zeeshan Amjad原文链接:http://www.codeproject.com/atl/atl_underthehood_2.asp介绍  在本系列的教程中,我要讨论一些ATL的内部工作方式以及它所使用的技术,这是本系列的第二篇文章。  现在让我们来探究一些虚函数背后更加有趣的资料。为了和上文保持一致,在本文的讨论中我将使用相同的顺序,程序的序号从20开始。  让我们看看下面这个

马说@优快云 2646
上一篇: ATL 的对象向导类型
下一篇: ATL布幔之下的秘密(2)
cpusoft
博客等级 码龄22年 8粉丝 32原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值