Delphi是市场上最好的RAD工具,但是现在C++占据着主导地位,有时针对一个问题很难找到Delphi或Pascal的解决方案.可是却可能找到了一个相关的C++类.本文描述几种在Delphi代码中使用C++类的方法.

Delphi is one of the greatest RAD tools on the market, but it in this currently C++-dominated world, it can sometimes be hard to find a Delphi or Pascal solution to your problem. There is, however, a chance you'll find a C++ class instead. This article describes several ways that enable you to use C++ classes from your Delphi code.

坏消息是:不能直接在Delphi代码中引入C++类.Delphi连接器不能将C++的对象文件连接到应用程序中.可以连接C对象文件,但这不是本文的主题(请见another article).你需要使用C++编译器创建DLL,然后在DLL中使用这些类.

First the bad news: you won't be able to directly link the classes into your Delphi code. The Delphi linker can't link C++ object files into your application. You can link C object files, but that is the subject ofanother article. You'll need access to a C++ compiler that can create DLLs, and use the classes from the DLL.

不同的对象内部格式

Delphi和C++接口最大不同之处在于其对象的内部格式.所有的Delphi对象从TObject类继承,在堆中创建.C++的类更像Delphi中带方法的结构体,可以静态或动态创建.他们与Borland和Turbo Pascal遗留在Delphi中的object类型很相似.

The greatest difficulty in interfacing Delphi and C++ is, that their object structures differ. All Delphi classes descend from TObject, and are created on the heap. C++ classes are more like Delphi records with methods, and can be created statically or dynamically. They are similar to the legacy object types, carried over into Delphi from Borland and Turbo Pascal.

幸运的是他们的区别不太大.唯一的不同是Delphi类和C++类的内存布局,Delphi类的第一个域是指向虚拟方法表(VMT)的指针.C++类没有像Tobject这样的通用基类,因此不会总存在虚方法(C++术语中叫做虚成员函数),所以VMT域可能不存在或在对象首地址的其他偏移量处.但是你可以强制C++类有一个与Tobject相同偏移量的VMT指针,只需要简单的令其包括一个非数据域(成员),并创建至少一个虚方法(虚成员函数).

Fortunately there is not such a big difference internally. The only thing that distinguishes the memory layout of a Delphi class and a C++ class is, that the Delphi class always has a pointer to the Virtual Method Table (VMT), as its first field. C++ classes don't have a common ancestor like TObject, and don't always have virtual methods (or virtual member functions, as they are called in C++-speak), so the VMT field can be missing or at a different offset in the object. But you can force the C++ class to have one, at the same offset as TObject, by simply telling it to contain no data fields (members), and by makingat least one method (member function) virtual.

导入方法

另一个问题是从Dll中导入函数.这里有两种基本方法:第一个是将C++类退化为C函数集合,每个函数的第一个参数为指向类的对象实例的指针;第二种方式是使用虚拟,抽象方法.

Another problem is exporting methods from a DLL. There are basically two ways: the first one "flattens" the C++ class into a set of C functions, which all take an object as first parameter; the second one uses virtual, abstract methods.

假设你有如下简单的C++对象:一个控制台(Console)类,使用conio.h中的函数实现简单的控制台功能.在构造函数中保存当前屏幕信息,并在析构过程中恢复回来.

Say you have the following simple C++ object: a Console class, that uses the functions in conio.h to achieve simple console functionality. On creation, it saves the current screen and, on destruction, restores it again.

enum TextColors

{

tcBLACK,

tcBLUE,

tcGREEN,

tcCYAN,

tcRED,

tcMAGENTA,

tcBROWN,

tcLIGHTGRAY,

tcDARKGRAY,

tcLIGHTBLUE,

tcLIGHTGREEN,

tcLIGHTCYAN,

tcLIGHTRED,

tcLIGHTMAGENTA,

tcYELLOW,

tcWHITE

};

class Console

{

private:

text_info oldState;

char *screenBuffer;

public:

Console(void);

virtual ~Console(void);

void reset(void);

void clearScreen(void);

void gotoXY(int x, int y);

void textColor(TextColors newColor);

void textAttribute(int newAttribute);

void textBackground(int newBackground);

int readKey(void);

bool keyPressed(void);

void write(const char *text);

void writeLn(const char *text);

};

这里类有10个方法,一个构造函数,一个析构函数.现在展示在Delphi中使用C++类的两种方法.

This class has 10 methods, a destructor and a constructor. I will now demonstrate the two main ways of using this C++ class from Delphi.

退化对象

要退化一个类,将其每个方法都作为简单的C函数进行导出,包括构造函数和析构函数.函数(除了构造函数和析构函数外)的第一个参数必须是指向对象实例的指针.要退化Console类,需要声明12个函数:

To "flatten" a class, you export a simple C function for each method, as well as functions for the constructor and the destructor. The first parameter of the functions (except for the "constructor" function) should be a pointer to the object. To flatten the Console class, you would declare 12 functions like this:

#include <windows.h>

#include "console.h"

typedef Console *ConsoleHandle;

// define a macro for the calling convention and export type

#define EXPORTCALL __declspec(dllexport) __stdcall

extern "C"

{

ConsoleHandle EXPORTCALL NewConsole(void)

{

return new Console();

}

void EXPORTCALL DeleteConsole(ConsoleHandle handle)

{

delete handle;

}

void EXPORTCALL ConsoleReset(ConsoleHandle handle)

{

handle->reset();

}

void EXPORTCALL ConsoleClearScreen(ConsoleHandle handle)

{

handle->clearScreen();

}

void EXPORTCALL ConsoleGotoXY(ConsoleHandle handle,

int x, int y)

{

handle->gotoXY(x, y);

}

void EXPORTCALL ConsoleTextColor(ConsoleHandle handle,

TextColors newColor)

{

handle->textColor(newColor);

}

void EXPORTCALL ConsoleTextAttribute(ConsoleHandle handle,

int newAttribute)

{

handle->textAttribute(newAttribute);

}

void EXPORTCALL ConsoleTextBackground(ConsoleHandle handle,

int newBackground)

{

handle->textBackground(newBackground);

}

int EXPORTCALL ConsoleReadKey(ConsoleHandle handle)

{

return handle->readKey();

}

bool EXPORTCALL ConsoleKeyPressed(ConsoleHandle handle)

{

return handle->keyPressed();

}

void EXPORTCALL ConsoleWrite(ConsoleHandle handle,

const char *text)

{

handle->write(text);

}

void EXPORTCALL ConsoleWriteLn(ConsoleHandle handle,

const char *text)

{

handle->writeLn(text);

}

// extern "C"

#pragma argsused

int WINAPI DllEntryPoint(HINSTANCE hinst,

unsigned long reason, void* lpReserved)

{

return 1;

}

现在编译代码得到DLL文件,可以在Delphi中以调用API的方式调用这个对象了.接口单元如下所示:

Now you only have to compile this to a DLL, and your object can be used from Delphi in the same manner as any API call. The interface unit would look like this:

unit ConsoleFlat;

interface

uses

SysUtils;

type

ConsoleHandle = Pointer; // no need to know the real type

TTextColor = (

tcBLACK, tcBLUE, tcGREEN, tcCYAN, tcRED, tcMAGENTA,

tcBROWN, tcLIGHTGRAY, tcDARKGRAY, tcLIGHTBLUE, tcLIGHTGREEN,

tcLIGHTCYAN, tcLIGHTRED, tcLIGHTMAGENTA, tcYELLOW, tcWHITE

);

function NewConsole: ConsoleHandle; stdcall;

procedure DeleteConsole(handle: ConsoleHandle); stdcall;

procedure ConsoleReset(handle: ConsoleHandle); stdcall;

procedure ConsoleClearScreen(handle: ConsoleHandle); stdcall;

procedure ConsoleGotoXY(handle: ConsoleHandle;

x, y : Integer); stdcall;

procedure ConsoleTextColor(handle: ConsoleHandle;

newColor: TTextColor); stdcall;

procedure ConsoleTextAttribute(handle: ConsoleHandle;

newAttribute: Integer); stdcall;

procedure ConsoleTextBackground(handle: ConsoleHandle;

newBackground: TTextColor); stdcall;

function ConsoleReadKey(handle: ConsoleHandle): Integer; stdcall;

function ConsoleKeyPressed(handle: ConsoleHandle): Boolean; stdcall;

procedure ConsoleWrite(handle: ConsoleHandle; text: PChar); stdcall;

procedure ConsoleWriteLn(handle: ConsoleHandle; text: PChar); stdcall;

implementation

const

DLLName = 'ConsoleFlat.dll';

function NewConsole; external DLLName;

procedure DeleteConsole; external DLLName;

procedure ConsoleReset; external DLLName;

procedure ConsoleClearScreen; external DLLName;

procedure ConsoleGotoXY; external DLLName;

procedure ConsoleTextColor; external DLLName;

procedure ConsoleTextAttribute; external DLLName;

procedure ConsoleTextBackground; external DLLName;

function ConsoleReadKey; external DLLName;

function ConsoleKeyPressed; external DLLName;

procedure ConsoleWrite; external DLLName;

procedure ConsoleWriteLn; external DLLName;

end.

现在可以随便调用了.缺点是需要使用函数,而在C++中可以使用封装好的类.而不是

This can then be used as you please. The disadvantage is, of course, that you are using functions, where the C++ user can use a class. So instead of

Console := TConsole.Create;

try

Console.TextBackground(tcRED);

Console.TextColor(tcYELLOW);

Console.ClearScreen;

Console.WriteLn('Yellow on red');

Console.ReadKey;

finally

Console.Free;

end;

必须按如下形式:

you must do the following:

Console := NewConsole;

try

ConsoleTextBackground(Console, tcRED);

ConsoleTextColor(Console, tcYELLOW);

ConsoleClearScreen(Console);

ConsoleWriteLn(Console, 'Yellow on red');

ConsoleReadKey(Console);

finally

DeleteConsole(Console);

end;

这就需要使用另一种更加灵活的方式在Delphi中使用类.

This leads to the next way of using the class from Delphi, one which is a bit more convenient.

使用纯虚类

纯虚类就是Delphi程序员所说的纯抽象类.只有虚方法而不存在数据成员.纯Delphi抽象类有相似的布局.任何类都有一个成员指向VMT.VMT是一个数组,每个类都会创建一个(而不是每个对象),包含指向类中虚方法实际实现的指针.这是实现多态的机制.函数调用被编码为跳转到类VMT中的特定索引指向的函数.因此不同的子类,有不同的VMT,可以按需实现虚方法;虚函数对应的指针在VMT中有相同的索引,但是在每个类中其指向不同的函数.

Pure virtual classes are what the Delphi programmer would call pure abstract classes. These have the advantage that they only have virtual methods, and absolutely no data members. Pure Delphi abstract classes have a similar layout. The only member of both classes is therefore a pointer to the VMT. The VMT is an array, one for each class (fortunately, not for each object), that contains pointers to the actual implementation of each virtual method for that particular class. This is how polymorphism is implemented. Function calls are only coded as jumps to the function in the VMT of the class at a specific index. So different descendant classes, which have different VMTs, can implement a virtual function differently; the function pointer found at the same index in the VMT will always be used, but it will point to different functions in each class.

现在大家可能会问:Tobject的虚拟函数呢?是不是已经在数组中填充了一系列的槽(函数指针).这是正确的,但是幸运的是,Delphi对象的VMT方式布局为:预先定义的虚方法位于VMT指向地址的负偏移量.用户定义的第一个虚方法在第一个槽上,即零偏移量的位置.

But now some of you may say: what about the virtual functions of TObject? These must probably already fill quite a few slots of the array. That is true, but fortunately, the VMT of Delphi objects is layed out in such a way, that these predefined virtual methods are all at a negative offset from the address to which the VMT pointer points. The first slot, at offset 0, contains the first user defined virtual method.

这种设计使传递所有方法(只要是虚方法)地址变得非常简单,只需获取VMT中的指针.唯一需要注意的事情就是,这里假设两种语言中定义的纯抽象类的方法声明顺序必须严格一致.这也是在Delphi3以前版本定义接口的方式.

This fortunate circumstance makes it rather simple to pass the addresses of all methods (as long as they are virtual) in one step, as one simple pointer to the VMT. The only thing you must take care of is to assure that there is a pure abstract base class defined in both languages, and that the order of declaration of the methods is exactly the same. This is actually how interfaces were defined in versions of Delphi prior to Delphi 3.

注意这种方式下,可以按Delphi类的方式使用C++类.但其还是一个C++类,因此其没有Tobject类的方法或属性,如InstanceSize 或AfterConstruction.不能尝试去调用TObject类的方法.必须按C++类的方式去使用,或作为一个轻量级的接口,但不必涉及COM函数.

Note that this way, you'll be using a C++ class as if it were a Delphi class.But it remains a C++ class, so it doesnot have theTObject methods or properties likeInstanceSize or AfterConstruction.You should not try to call them! You should really use the class as a C++ class, or as some kind of lightweight interface, without the COM functions.

如果遵守上面的忠告就可以安全的使用C++类了.

Only if you follow this advice, you will be able to use the C++ class safely.

这里有个问题.如果要导出的类不是一个纯虚类,而是一个正常的类,拥有数据成员和非虚函数.有两种可能,如果有源码,可以为类定义一个抽象祖先,然后将待导出的类作为其子类:

There is one problem. The class you want to export is not a pure virtual class, it is a normal class, with a data member and non-virtual functions. There are two possiblities. If you have the source code, you can define your own abstract ancestor of the class, and then make your class a descendant of it:

#define STDCALL __stdcall

class AbstractConsole

{

public:

virtual void STDCALL reset(void) = 0;

virtual void STDCALL clearScreen(void) = 0;

virtual void STDCALL gotoXY(int x, int y) = 0;

virtual void STDCALL textColor(TextColors newColor) = 0;

virtual void STDCALL textAttribute(int newAttribute) = 0;

virtual void STDCALL textBackground(int newBackground) = 0;

virtual int STDCALL readKey(void) = 0;

virtual bool STDCALL keyPressed(void) = 0;

virtual void STDCALL write(const char *text) = 0;

virtual void STDCALL writeLn(const char *text)= 0;

virtual void STDCALL free(void) = 0;

};

#ifndef DLLCODE

#define EXTERN __declspec(dllimport) __stdcall

#else

#define EXTERN __declspec(dllexport) __stdcall

#endif

extern "C" AbstractConsole* EXTERN NewConsole(void);

注意:一些编译器,如Microsoft Visual C++,并不总是将成员函数默认编译为cdecl,而是使用其自己的非标准调用约定.因此最好将所有成员函数编译为stdcall.

NOTE: some compilers, like Microsoft Visual C++, don't always compile member functions ascdecl by default, but use their own, non-standard calling convention instead. So it is best to compile all member functions asstdcall.

现在简单的修改Console类声明的第一行代码,并重新编译.

Now you could simply change the first line of the declaration of your original Console class, and recompile.

class Console : public AbstractConsole

// etc...

不需要再次声明Console类的所有虚方法.在C++中子类拥有基类中相同签名的虚方法,并将自动重写这些函数.因此原来的类中成员方法将自动变为虚方法.

There is no need to declare all the functions of Console virtual. In C++, in descendant classes, a member function with the same signature as a virtual member function as the ancestor class, will automatically override that function. So the member functions of our original class will automatically become virtual.

可是,通常不可能或不希望将所有类的方法声明为虚方法,或修改祖先类.这种情况下必须使用多继承或聚合的方式使用Console类的功能.

However, often it is not possible, or desired, to make all functions of a class virtual, or to change the ancestor. In that case you will have to use multiple inheritance or aggregation to use the functionality of Console.

#include "console.h"

#include "aconsole.h"

class ConcreteConsole : public AbstractConsole, private Console

{

void reset(void);

void clearScreen(void);

// etc...

void free(void);

};

然后函数简单的调用Console的函数来实现任务.如下是摘录的实现代码.

And the functions simply call the Console functions to perform their tasks. Below is an excerpt of the implementing code.

#include "console.h"

#include "cconsole.h"

void ConcreteConsole::reset(void)

{

Console::reset();

}

void ConcreteConsole::clearScreen(void)

{

Console::clearScreen();

}

// etc...

void ConcreteConsole::free(void)

{

if (this)

delete this;

}

当然现在还是需要导出函数的.如果你选择了继承等方式,使Console 类继承于AbstractConsole ,形式如下:

Of course there is still a need for an export function. If you chose to change the inheritance, i.e. decided to let the Console class inherit from AbstractConsole, it will look like this:

AbstractConsole* EXTERNCALL NewConsole()

{

return new Console();

}

如果使用聚合和包装的方式,形式如下:

If, on the other hand, you decided to use aggregation and a wrapper, it would look like:

AbstractConsole* EXTERNCALL NewConsole()

{

return new ConcreteConsole();

}

Delphi类不必知道AbstractConsole 或 ConcreteConsole,可以简单的按需要调用Tconsole或其他对象.导入单元如下:

The Delphi class doesn't have to know about AbstractConsole or ConcreteConsole, and can simply be called TConsole, or whatever you like. The import unit will look like this:

unit ConsoleDLL;

interface

type

TTextColor = (

tcBLACK, tcBLUE, tcGREEN, tcCYAN, tcRED, tcMAGENTA,

tcBROWN, tcLIGHTGRAY, tcDARKGRAY, tcLIGHTBLUE, tcLIGHTGREEN,

tcLIGHTCYAN, tcLIGHTRED, tcLIGHTMAGENTA, tcYELLOW, tcWHITE

);

TConsole = class

procedure Reset; virtual; cdecl; abstract;

procedure ClearScreen; virtual; cdecl; abstract;

procedure GotoXY(x, y : Integer); virtual; cdecl; abstract;

procedure TextColor(newColor: TTextColor); virtual; cdecl; abstract;

procedure TextAttribute(newAttribute: Integer);

virtual; cdecl; abstract;

procedure TextBackground(newBackground: TTextColor);

virtual; cdecl; abstract;

function ReadKey: Integer; virtual; cdecl; abstract;

function KeyPressed: Boolean; virtual; cdecl; abstract;

procedure Write(text: PChar); virtual; cdecl; abstract;

procedure WriteLn(text: PChar); virtual; cdecl; abstract;

procedure Free; virtual; cdecl; abstract;

end;

function NewConsole: TConsole; stdcall;

implementation

function NewConsole; external 'ConsoleDLL.dll';

end.

这就是使用DLL中的C++类Console需要做的全部工作.按上述方法就可以很简单的进行调用了.

This is all you need to use the C++ Console class from the DLL. It can be used in a similar fashion as I described above:

Console := NewConsole;

try

Console.TextBackground(tcRED);

Console.TextColor(tcYELLOW);

Console.ClearScreen;

Console.WriteLn('Yellow on red');

Console.ReadKey;

finally

Console.Free;

end;

结论

虽然经常说C++类不能在Delphi中调用,如本文所述这只有部分正确.但是上述的两种方式中,哪种导入C++类的方式更好呢?

Although it is often said that C++ classes can't be used in Delphi, this is only partly true, as this article demonstrates. But which of the two ways of importing C++ classes is preferrable?

第二种方式使用虚类,当然更容易调用.使用方式上与Delphi中的类差不多.但是不能从这个类中继承并添加自己的函数,因为无法调用基类的构造函数,也不能调用任何Tobject的函数,或依赖于他们的代码. NewConsole 总是返回一个ConcreteConsole 对象(或Console,如果改变继承方式).同时更接近于C++的运行机制.

The second way, using virtual classes, is of course much more convenient to use. It is almost as if your class was written in Delphi. But you cannot inherit from the class and add your own functionality, since you can't call the inherited constructor.You can't call any of the TObject functions either, or call code that relies on them. NewConsole will always return a ConcreteConsole (or a Console, if you changed the inheritance). Also, it is a bit more work on the C++ side.

退化类的方式缺少便利性,但少了一个层次的间接调用(因为调用虚方法总需要一次额外的间接调用),提高了运行速度.

The "flat" variety is less convenient, but has one level of indirection less (since calling virtual functions is also an extra level of indirection), and that makes it a bit faster.

当然,两种方式,都可以写一个Delphi类来包装抽象类或导入函数.但会导致其他的间接调用.

Of course, with both approaches, you could write a Delphi class that wraps either the abstract class, or the functions. But that would introduce yet another level of indirection.

DLL的C++源码和Delphi的Demo小程序可以在Downloads页面下载.

The C++ source code for both DLLs and small demo programs in Delphi can be downloaded from theDownloads page.

Rudy Velthuis

http://rvelthuis.de/downloads.html#cppobjszip

http://blog.csdn.net/henreash/article/details/7352335

在Delphi中使用C++对象(两种方法,但都要改造C++提供的DLL)的更多相关文章

  1. php获取数组中重复数据的两种方法

    分享下php获取数组中重复数据的两种方法. 1,利用php提供的函数,array_unique和array_diff_assoc来实现 <?php function FetchRepeatMem ...

  2. [转]Qt中定时器使用的两种方法

    Qt中定时器的使用有两种方法,一种是使用QObject类提供的定时器,还有一种就是使用QTimer类. 其精确度一般依赖于操作系统和硬件,但一般支持20ms.下面将分别介绍两种方法来使用定时器. 方法 ...

  3. python中执行shell的两种方法总结

    这篇文章主要介绍了python中执行shell的两种方法,有两种方法可以在Python中执行SHELL程序,方法一是使用Python的commands包,方法二则是使用subprocess包,这两个包 ...

  4. vue中使用echarts的两种方法

    在vue中使用echarts有两种方法一.第一种方法1.通过npm获取echarts npm install echarts --save 2.在vue项目中引入echarts 在 main.js 中 ...

  5. Ajax中解析Json的两种方法详解

    eval();  //此方法不推荐 JSON.parse();  //推荐方法 一.两种方法的区别 我们先初始化一个json格式的对象: var jsonDate = '{ "name&qu ...

  6. Ajax中解析Json的两种方法

    eval(); //此方法不推荐 JSON.parse(); //推荐方法 一.两种方法的区别 我们先初始化一个json格式的对象: var jsonDate = '{ "name" ...

  7. 在C++中定义常量的两种方法的比较

    常量是定以后,在程序运行中不能被改变的标识符.C++中定义常量可以用#define .const 这两种方法.例如:#define PRICE 10 //定义单价常量10const int PRICE ...

  8. MySQL中删除数据的两种方法

    转自:http://blog.csdn.net/apache6/article/details/2778878 1. 在MySQL中有两种方法可以删除数据: 一种是delete语句,另一种是trunc ...

  9. Endnote在latex中的应用的两种方法

    从endnote中向latex文档批量插入参考文献的两种方法 一.若是latex模板中参考文献编写的命令是: \begin{thebibliography} \bibitem{lab1}LIU M L ...

随机推荐

  1. 【译】在Asp.Net中操作PDF - iTextSharp - 利用列进行排版

    原文 [译]在Asp.Net中操作PDF - iTextSharp - 利用列进行排版 在使用iTextSharp通过ASP.Net生成PDF的系列文章中,前面的文章已经讲述了iTextSharp所涵 ...

  2. Could not resolve archetype org.apache.maven.archetypes:maven-archetype-quickstart

    之前都是命令行创建,今天用eclipse装m2eclipse的时候装完后创建项目的时候报错: Could not resolve archetype org.apache.maven.archetyp ...

  3. html submit 登录

    <!doctype html> <html lang="en"> <head> <meta name="Generator&qu ...

  4. Java GUI使用exe4j打包exe文件

    exe4j下载地址:http://blog.csdn.net/cciii/article/details/17083531 1.  在MyEclipse将java项目打包成可执行jar文件.项目结构如 ...

  5. QT之深入理解QThread

    QT之深入理解QThread       理解QThread之前需要了解下QThread类,QThread拥有的资源如下(摘录于QT 5.1 帮助文档):       在以上资源中,本文重点关注槽:s ...

  6. linux-0.11抠代码-bootsect

    //bootfun.s .global asm_message .global asm_memmove .global asm_readsector .global asm_checkLBA .cod ...

  7. POJ 3892 RSA Factorization

    题目地址:http://poj.org/problem?id=3892 题目大意:RSA分解. 这儿的N比较大,要用高精度,如果一般的肯定分解不了,但是这儿有一个限制 |q-kp|<=10000 ...

  8. c++实现atoi()和itoa()函数(字符串和整数转化)

    (0) c++类型所占的字节和表示范围 c 语言里 类型转换那些事儿(补码 反码) 应届生面试准备之道 最值得学习阅读的10个C语言开源项目代码 一:起因 (1)字符串类型转化为整数型(Integer ...

  9. 把VS2010的智能代码提示和注解从英文变成中文

    最近安装了个高级点的VS2010,起初还没留意.今天无意发现提示信息只能提示英文.....头大oooo. 我以为是个别现象,于是GG了下,发现有很多盆友都有这种. 记录下来了,以后省事儿: 访问MS的 ...

  10. Theano学习笔记(二)——逻辑回归函数解析

    有了前面的准备,能够用Theano实现一个逻辑回归程序.逻辑回归是典型的有监督学习. 为了形象.这里我们如果分类任务是区分人与狗的照片. 首先是生成随机数对象 importnumpy importth ...