我正在尝试使自己熟悉Ada中的面向对象。几个月前,您的网站帮助我解决了另一个O-O问题,希望您愿意再次提供帮助。
情况:我有一个抽象类型“ token”和2个派生类型“ otoken”和“ vtoken”。我想将2个派生类型放在同一数组中,并让它们正确调度。
我的教科书建议将数组声明为包含指向令牌类的指针,这使我不得不遍历所有点。下面是我程序的精简版本,但由于编译器说我的调度调用“含糊不清”,因此无法编译
---------------------------------------------------------------------------------
--------------------------------------------
-- Tokensamp.ads
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Command_Line; use Ada.Command_Line;
package tokensamp is
type token is abstract tagged record
x: integer;
end record;
type otoken is new token with record
y: integer;
end record;
type vtoken is new token with record
z: integer;
end record;
type potoken is access otoken;
type pvtoken is access vtoken;
end tokensamp;
------------------------------------------------------------------------------------------------------
-- Parsesamp.ads:
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Command_Line; use Ada.Command_Line;
with tokensamp;
package parsesamp is
function rootOfTree( t: tokensamp.pvtoken) return integer;
function rootOfTree( t: tokensamp.potoken) return integer;
end parsesamp;
-------------------------------------------
-- parsesamp.adb:
package body parsesamp is
function rootOfTree( t: tokensamp.pvtoken) return integer is
begin
return t.z * 2;
end rootOfTree;
function rootOfTree( t: tokensamp.potoken) return integer is
begin
return t.y * 2;
end rootOfTree;
result: integer;
type tarray is array (1..2) of access tokensamp.token'class ;
tl: tarray;
begin
for i in 1..2 loop
result := rootOfTree( tl(i) );
end loop;
end parsesamp;
-------------------------------------------------------------
当我使用GNAT Ada 95编译器进行编译时,收到错误消息:
C:\GNAT\2018\bin\ceblang>gnatmake parsesamp.adb
gcc -c parsesamp.adb
parsesamp.adb:25:27: ambiguous expression (cannot resolve "rootOfTree")
parsesamp.adb:25:27: possible interpretation at parsesamp.ads:9
parsesamp.adb:25:27: possible interpretation at parsesamp.ads:8
gnatmake: "parsesamp.adb" compilation error
换句话说,它无法将这两个功能识别为备用调度呼叫。如果您能提出建议,我将不胜感激,因为我已经坚持了好几天。
答案 0 :(得分:1)
您的困惑似乎包括使用软件包和在Ada中定义分派操作的方式。 必须在定义标记数据类型的同一包中定义调度操作,但必须先定义其他类型。
mean_function<-function(x){
mean(x)
}
程序包规范定义了标记的类型Token及其调度操作Root_Of_Tree。记录类型令牌包含一个名为X的整数数据元素。 包的主体是:
testset "number with 1-5" <-raw[number"number 1-5",]
我已使用子程序包定义Otoken和Vtoken类型。
package Tokens is
type token is tagged private;
function Root_Of_Tree(T : Token) return Integer;
type Token_Access is access all Token'Class;
type Token_Array is array (Positive range <>) of Token_Access;
private
type Token is tagged record
X : Integer := 1;
end record;
end Tokens;
Tokens.OTokens的主体为:
package body Tokens is
------------------
-- Root_Of_Tree --
------------------
function Root_Of_Tree (T : Token) return Integer is
begin
return T.X;
end Root_Of_Tree;
end Tokens;
令牌规范。VToken是:
package Tokens.OTokens is
type Otoken is new Token with private;
function Root_Of_Tree(T : Otoken) return Integer;
private
type Otoken is new Token with record
Y : Integer := 2;
end record;
end Tokens.OTokens;
主体Token.Vtokens为:
package body Tokens.OTokens is
------------------
-- Root_Of_Tree --
------------------
function Root_Of_Tree (T : Otoken) return Integer is
begin
return T.Y * 2;
end Root_Of_Tree;
end Tokens.OTokens;
创建包含一个otoken和一个vtoken的数组的主要过程是:
package tokens.vtokens is
type vtoken is new token with private;
function Root_Of_Tree(T : vtoken) return Integer;
private
type vtoken is new token with record
Z : Integer := 3;
end record;
end tokens.vtokens;
请记住,OToken类型包含两个字段X和Y。VToken类型包含两个字段X和Z。 主要过程的输出为:
package body tokens.vtokens is
------------------
-- Root_Of_Tree --
------------------
function Root_Of_Tree (T : vtoken) return Integer is
begin
return T.Z * 2;
end Root_Of_Tree;
end tokens.vtokens;
答案 1 :(得分:1)
首先,您需要将rootOfTree
声明为抽象操作
之token
:
type token is abstract tagged record
x: integer;
end record;
function rootOfTree( t: tokensamp.token) return Integer is abstract;
(必须在token
之前声明原始操作
冻结,基本上是在声明使用之前
派生类型)。
然后声明otoken
和vtoken
的原始操作;他们
必须在与它们相应类型相同的包中声明为
是原始的,即可以分派给。
type otoken is new token with record
y: integer;
end record;
type vtoken is new token with record
z: integer;
end record;
function rootOfTree( t: tokensamp.vtoken) return integer;
function rootOfTree( t: tokensamp.otoken) return integer;
(在每个参数之后立即声明它们会更正常 类型,但由于两者都不冻结,所以可以)。
请注意,rootOfTree
操作均不采用访问类型
参数。
您可能不需要考虑potoken
,pvtoken
在此处声明类级指针:
type ptoken is access token'class;
然后,您需要为package tokensamp
声明一个正文,其中包含
这两个具体rootOfTree
的实现。
考虑到parsesamp
,您既不能在这里声明rootOfTree
。
你可以写
result := tokensamp.rootOfTree (t1(i).all);
({t1(i)
是指向类范围的指针,.all
是类范围的值,并且
tokensamp.rootOfTree
是可调度的操作,因此这是一个
调度电话)
..或者您可以使用更漂亮的速记
result := t1(i).rootOfTree;
答案 2 :(得分:0)
作为吉姆·罗杰斯(Jim Rogers)和西蒙·赖特(Simon Wright)给出的答案的补充,如果您要使用Ada 2012,则可以考虑使用不定的持有人来构造数组(另请参见RM A.18.18和Ada 2012 Rationale, section 8.5 )
如基本原理所述,持有人是可以容纳(和管理)单个对象实例的容器。将对象作为参数传递给To_Holder
子程序时(请参见下面的示例),将其复制到堆实例中,然后在不再需要该对象时将其销毁(例如,当替换它或持有人出局时)范围)。因此,就像直接使用access
类型时那样,持有人容器使您不必手动管理内存。
(性能)成本是复制传递到To_Holder
程序的对象。您可以在保持器之间“移动”对象(使用在保持器包中定义的Move
子程序),但是不能将对象“移动”到保持器中。您只能将其复制到支架中。
token.ads (规范)
package Tokens is
-- Abstract Root Type.
type Token is abstract tagged private;
function Root_Of_Tree (T : Token) return Integer is abstract;
-- First derived type.
type OToken is new Token with private;
function Root_Of_Tree (T : OToken) return Integer;
function Create_OToken (X, Y : Integer) return OToken;
-- Second derived type.
type VToken is new Token with private;
function Root_Of_Tree (T : VToken) return Integer;
function Create_VToken (X, Z : Integer) return VToken;
private
type Token is abstract tagged record
X : Integer;
end record;
type OToken is new Token with record
Y : Integer;
end record;
type VToken is new Token with record
Z : Integer;
end record;
end Tokens;
tokens.adb (正文)
package body Tokens is
function Root_Of_Tree (T : OToken) return Integer is
begin
return T.X + 2 * T.Y;
end Root_Of_Tree;
function Create_OToken (X, Y : Integer) return OToken is
begin
return OToken'(X, Y);
end Create_OToken;
function Root_Of_Tree (T : VToken) return Integer is
begin
return T.X + 3 * T.Z;
end Root_Of_Tree;
function Create_VToken (X, Z : Integer) return VToken is
begin
return VToken'(X, Z);
end Create_VToken;
end Tokens;
main.adb
with Ada.Text_IO; use Ada.Text_IO;
with Tokens; use Tokens;
with Ada.Containers.Indefinite_Holders;
procedure Main is
package Token_Holder is
new Ada.Containers.Indefinite_Holders (Token'Class);
use Token_Holder;
type Token_Array is array (Integer range <>) of Holder;
Tokens : Token_Array :=
(To_Holder (Create_OToken (1, 2)),
To_Holder (Create_OToken (5, 4)),
To_Holder (Create_VToken (1, 2)),
To_Holder (Create_VToken (5, 4)));
begin
for T of Tokens loop
Put_Line (Integer'Image (T.Element.Root_Of_Tree));
end loop;
end Main;
运行valgrind
表示程序终止时没有剩余的内存分配:
$ valgrind ./main
==1392== Memcheck, a memory error detector
==1392== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==1392== Using Valgrind-3.12.0.SVN and LibVEX; rerun with -h for copyright info
==1392== Command: ./main
==1392==
5
13
7
17
==1392==
==1392== HEAP SUMMARY:
==1392== in use at exit: 0 bytes in 0 blocks
==1392== total heap usage: 8 allocs, 8 frees, 160 bytes allocated
==1392==
==1392== All heap blocks were freed -- no leaks are possible
==1392==
==1392== For counts of detected and suppressed errors, rerun with: -v
==1392== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
注意[已更新]:有8个分配,而数组仅包含4个元素/持有者。这是因为如何为support atomic increment/decrements的平台(例如Linux)实现持有者。对于这些平台,实现在内部创建另一个“共享持有人”,以支持写时复制策略(请参见source)。对于不支持原子增量/减量的平台,实现会更简单(请参见source),并且只会显示4种分配。