接泛型五
20.8表达式和语句
某些表达式和语句的操作针对泛型进行了修改。这一节将介绍这些改变。
20.8.1默认值表达式
默认值表达式被用于获得一个类型的默认值(§5.2)。通常一个默认值表达式被用于类型参数,因为如果类型参数的是一个值类型或引用类型,它可能不是已经的。(不存在从null类型到类型参数的转换。)
primary-no-array-creation-expression:(基本无数组创建表达式:)
…
default-value-expression(默认值表达式)
default-value-expression:(默认值表达式:)
primary-expression . default (基本表达式 .default)
predefined-type . default(预定义类型. default)
如果一个基本表达式在一个默认值表达式中被使用,并且这个基本表达式不能分为一个类型,那么将会出现一个编译时错误。然而在§7.5.4.1中描述的规则也适用于形成E.default的构件。
如果默认值表达式的左边针对一个引用类型在运行时被计算,结果是将null转换到那个类型。如果默认值表达式的左边针对一个值类型在运行时被计算,其结果是值类型的默认值(§4.1.2)。
如果类型是一个具有类约束的引用类型或类型参数,默认值表达式是一个常量表达式(§7.15)。此外,如果类型是下列值之一,默认值表达式是一个常量表达式:sbyte、byte、 short、ushort、 int 、 uint 、long 、ulong 、 char 、float 、double 、decimal 或bool。
20.8.2对象创建表达式
对象常见表达式的类型可以是一个类型参数。当类型参数被作为对象创建表达式中的类型而指定时,下面两个条件必须具备,否则将会出现编译时错误
实参列表必须删除
必须为类型参数而指定new()形式的构造函数约束
通过创建一个类型参数被绑定到的运行时类型的实例,并调用该类型的默认构造函数,就可以执行对象创建表达式。运行时类型可以是引用或者值类型。
20.8.3运算符的类型
typeof运算符可以用于类型参数。其结果是被绑定到类型参数的运行时类型的System.Type对象。typeof运算符也可以被用于构造类型。
class X <T>
{
public static void PrintTypes()
{
Console.WriteLine(typeof(T).FullName);
Console.WriteLine(typeof(X<X<T>>).FullName);
}
}
class M
{
static void Main()
{
X<int>.PrintTypes();
}
}
先前的程序将打印如下。
System.Int32
X<X<Sytem.Int32>>
Typeof运算符不能用于没有指定类型实参的泛型类型声明的名字。
class X<T>{…}
class M
{
static void Main()
{
Type t = typeof(X); //错误,X需要类型实参
}
}
20.8.4引用相等运算符
如果T由一个类约束而约束,引用类型相等运算符可以被用于比较类型参数T的值。
引用类型相等运算符的用法可以让类型参数T的实参很容易地与其他为null的实参进行比较,即使T没有类约束也如此。在运行时,如果T是一个值类型,比较的结果将是false。
下面的例子检查一个非约束类型参数类型的实参是否是null。
class C<T>
{
void F(T x)
{
if(x==null) thow new ArgumentNullException();
…
}
}
即使是T可以表示一个值类型,x==null构件也是允许的,并且当T是值类型时,其结果被简单的定义为false。
20.8.5 is运算符
在开放类型上的is运算符操作遵循通常的规则(§7.9.9)。如果e或T的编译时类型是一个开放类型,那么在运行时对于e和T将总是执行动态类型检查。
20.8.6as运算符
只要T有一个类约束,类型参数T可被用在as运算符的右边。这种限制是需要的,因为值null可能被作为运算符的结果返回。
class X
{
public T F<T>(object o) where T:Attribute
{
return o as T; //ok,T有一个类约束
}
public T G<T>(object o)
{
return o as T; //错误,T没有约束
}
}
在as运算符(§7.9.10)的当前规范中,对于表达式e as T最后一点表明,如果从e的编译时类型到T,不存在有效的显式引用转换,将会出现编译时错误。对于泛型,这条规则稍微作了修改。如果E的编译时类型或T是一个开放类型,在这种情况下将不会出现编译时错误;相反,运行时检查将会执行。
20.8.7异常语句
对于开放类型,throw(§8.9.5)和try(§8.10)的通常规则是适用的。
只要类型参数具有System.Exeption异常(或子类具有)作为类约束,那么throw语句可以被用作其类型有一个类型参数给定的表达式。
只要类型参数System.Exception(或子类子类具有)作为类约束,那么在catch语句中的命名的类型可能是一个类型参数。
20.8.8 lock语句
lock语句可以被用作其类型由一个类型参数给定的表达式。如果表达式的运行时类型是一个值类型,lock将没有效果(因为对于装箱值不能有任何其他的引用)。
20.8.9 using 语句
using 语句(§8.13)遵循通常的规则:表达式必须被隐式的转换到System.IDisposable。如果类型参数通过System.IDisposable而约束,那么该类型的表达式可以使用using 语句。
20.8.10 foreach语句
给定如下形式的foreach语句
foreach(ElementType element in collection) statement
如果集合表达式是一个没有实现集合模式的类型,但为每个类型T实现了构造接口System.Collections.Generic.IEnumerable<T>,那么foreach语句的扩展是
IEnumerator<T> enumerator = ((IEnuemrable<T>)(collection).GetEnumerator();
try
{
where (enumerator.MoveNext()){
ElementType element = (ElementType)enumerator.Current;
statement;
}
}
finally{
enumerator.Dispose();
}
20.9查找规则修订
泛型修改了用于查找和绑定名字的某些基本规则。下面几节在考虑泛型的情况下,重新叙述了所有的基本名字查找规则。
20.9.1命名空间和类型名字
如下内容可替换§3.8。
在C#程序中有几个上下文需要指定命名空间或者类型名字。任何形式的名字都可以由一个或多个由“.”标记分隔的标识符号组成。
namespace-name:(命名空间名字:)
namespace-or-type-name(命名空间或类型名字)
type-name:(类型名字:)
namespace-or-type-name(命名空间或类型名字)
namespace-or-type-name:(命名空间或类型名字:)
identifier type-argument-list opt(标识符 类型实参列表可选)
namespace-or-type-name . identifier type-argument-list opt( 命名空间或类型名字. 标识符 类型实参列表可选)
命名空间名字是引用命名空间的命名空间名字或类型名字(namespace-or-type-name)。见下面所描述的决策,命名空间名字的命名空间或类型名字必须引用一个命名空间,否则出现编译时错误。在一个命名空间名字中不能有类型实参(只有类型可以具有类型实参)。
类型名字是一个引用类型的命名空间或类型名字(namespace-or-type-name)。见下面所描述的决策,类型名字的命名空间或类型名字必须引用一个类型,否则出现编译时错误。
命名空间或类型名字的意义按如下确定。
如果命名空间或类型名字是I或I<A1,…,AN>的形式,其中I 是一个单一标识符,并且<A1,…,AN>是一个可选类型实参列表。
- 如果命名空间或类型名字出现在泛型方法声明之内,并且该声明包括一个由I给定名字的没有指定类型实参列表的类型参数,那么命名空间或类型名字引用该类型参数。
- 否则,如果命名空间或类型名字出现在类型声明之内,那么对于类型T的每个实例(<A1,…,AN>20.1.2),以那个类型声明的实例类型开始,并继续采用每个封闭类或结构类型声明(如果有的话)
u 如果没有指定包含由I给定名字,并且没有类型实参列表的类型参数T的声明,那么命名空间或类型名字引用那个类型参数。
u 否则,如果I是T中可访问成员的名字,并且如果那个成员是一个带有匹配类型参数数量的类型,那么命名空间或类型名字引用类型T.I或者类型T.I<A1,…AN>。注意当确定命名空间或类型名字的意义时,无类型成员(常数、字段、方法、属性、索引器、运算符、实例构造函数、析构函数和静态构造函数)和带有不同数量的类型参数成员将会被忽略。
- 否则,对于在以命名空间或类型名字出现的命名空间开始的每个命名空间N,并继续使用每个封闭命名空间(如果有的话),然后以全局命名空间结束,下面的步骤将会被计算,直到该实体被定位。
u 如果I 是在N中的命名空间中的名字,并且没有指定类型实参列表,那么命名空间或类型名字引用该个命名空间。
u 否则,如果I是在N中带有匹配类型参数数量的可访问类型的名字,那么命名空间或类型名字引用使用给定类型实参构造的类型。
u 否则,如果命名空间或类型名字出现的位置,由N的命名空间声明所封闭
- 如果命名空间声明包含一个由I 给定名字的using 别名指令,而I带有导入命名空间或类型,并且没有指定参数列表,那么命名空间或类型名字引用该命名空间或者类型
- 否则,如果由命名空间声明的using命名空间指示符导入的命名空间,恰好包含带有I 给定名字的,匹配类型参数数量的类型,那么命名空间或类型名字引用由给定类型实参构造的这个类型。
- 否则,如果由命名空间声明的using 命名空间指示符导入的命名空间包含多个带有I给定名字的,匹配类型参数数量的类型,那么命名空间或者类型名字是模糊的,并且将导致错误。
- 否则,命名空间或类型名字是未定义的,并且出现编译时错误。
l 否则,命名空间或者类型名字是N.I或者N.I<A1,…,AN>的形式,这里N是一个命名空间或类型名字,I是一个标识符,并且<A1,…,AN>是一个可选类型实参列表。N 被作为命名空间或类型名字而首先决策。如果N的决策失败,将会出现一个编译时错误。否则N.I或者N.I<A1,…,AN>将按如下方式决策。
- 如果N引用一个命名空间,并且如果I是内嵌在N中的命名空间名字,并且没有指定类型实参列表,那么命名空间或类型名字引用该内嵌命名空间。
- 否则,如果N引用一个命名空间,并且I是在带有匹配类型参数数量的N中的可访问类型的名字,那么命名空间或类型名字引用由给定类型实参构造的那个类型。
- 否则,如果N引用类或结构类型,并且I是内嵌在带有与类型参数匹配的N中的可访问类型的名字,那么命名空间或者类型名字引用由给定实参构造的那个类型。
- 否则,N.I是一个无效的命名空间名字,并且会出现编译时错误。
20.9.2成员查找
下面内容可替换§7.3
成员查找是一种根据在上下文中的意义来确定类型的处理过程。在一个表达式中,成员查找可以作为计算一个简单名字或成员访问(§20.9.4)而出现。
在类型T中的名字N的成员查找按如下规则确定。
首先一组名为N的可访问成员被确定。
- 如果T是一个类型参数,那么在每个作为T的类约束或接口约束而指定的类型,连同在object中的N的命名成员的集合中,这个集合就是命名的可访问成员的联合。
- 否则,这个集合由T中的N的所有命名可访问成员组成,包括继承成员和在object中的N的命名可访问成员。如果T 是一个构造类型,成员的集合通过替换§20.5.4中描述的类型实参而得到。包括override修饰符的成员将从集合中排出。
接着,通过其他成员而被隐藏的成员将从这个集合中删除。对于在集合中的每个成员S.M,S是M在其中被声明的类型,下面的规则可适用
- 如果M是一个常数、字段、属性、事件或枚举成员,那么在S的所有基类中声明的成员都将从这个集合删除。
- 如果M是一个类型声明,那么在 S基类中的所有非类型声明都从集合被删除,并且,作为S 在基类型中声明的M的所有带有相同数量类型参数的类型声明,都将从该集合中删除。
- 如果M是一个方法,那么在S的基类中声明的所有非方法成员,都将从这个集合删除,并且,作为S 在基类型中声明的M的带有相同签名的所有方法都将从这个集合中删除。
接着,通过类成员隐藏的接口成员将从该集合中删除。这一步,只有当T是一个类型参数,并且T有类约束和多个接口约束时才有效。对于在集合中的每个成员S.M,其中S是M在其中声明的类型,如果S是一个类声明而不是object的话,下面的规则适用
- 如果M是一个常数、字段、属性、事件、枚举成员或类型声明,那么在接口声明中声明的所有成员都将从这个集合中删除。
- 如果M是一个方法,那么在接口类型中声明的所有非方法成员都将从这个集合中删除,并且,作为S 在接口中声明的M的带有相同签名的所有方法都将从这个集合中删除。
最后,在删除隐藏成员之后,查找的结果将被确定
- 如果由单一成员组成的集合,不是类型、方法,那么这个成员就是查找的结果。
- 否则,如果集合只包含方法,那么这组方法就是查找的结果。
- 否则,如果集合只包含类型声明,那么这组类型声明在成员查找的结果中。
- 否则,查找是不明确的,将会出现编译时错误。
对于类型,而不是类型参数和接口的成员查找来说,在接口中的成员查找是严格的单继承(在继承链中的每个接口恰好有零个或一个直接基接口),查找规则的影响只是派生成员隐藏带有相同名字和签名的基类成员。这种单继承查找是很明确的。成员查找中可能引起的模糊性来自于§13.2.5中描述的多重继承接口
20.9.3简单名字
如下内容可替换§7.5.2。
简单名字由一个标识符,其后可跟随可选的类型参数列表。
simple-name:(简单名字:)
identifier type-argument-list opt(标识符 类型实参列表可选)
对于I或I<A1,…,AN>形式的简单名字,这里I 是一个标识符,I<A1,…,AN>是一个可选类型实参列表,可以被按如下方式计算和分类。
如果简单名字出现在块内,并且如果块的局部变量声明空间包含由I给定名字的局部变量或参数,那么,这个简单名字引用该局部变量和参数,并且作为一个变量而被分类。如果类型实参列表被指定,将会出现编译时错误。
如果简单名字出现在泛型方法声明体之内,并且如果该声明包含由I给定名字的类型参数,那么简单名字引用那个类型参数,如果只定了类型实参列表被,将会出现编译时错误。
否则,对于以直接封闭类、结构或枚举声明的实例类型开始的类型T的每个实例,继续采用每个封闭外部类或结构声明的实例类型(如果有的话)。
- 如果T的声明包括由 I给定名字的类型参数,那么,简单名字引用该类型参数。如果类型实参列表被指定,将会出现编译时错误。
- 否则,如果在T 中I的成员查找产生一个匹配
u 如果T是直接封闭类或结构类型的实例类型, 并且查找标识一个或多个方法,结果将是一个带有与this表达式关联的方法组。如果类型实参列表被指定,它被用在一个泛型方法调用中(§20.6.3)。
u 如果T是直接封闭类或结构类型的实例类型,如果查找标识一个实例成员,并且引用出现在一个实例构造函数、实例方法或一个实例访问器的块之内,其结果将和this.I形式的成员访问相似。如果类型实参被指定,将出现编译时错误。
u 否则,结果与T.I或T.I<A1,…,AN>形式的成员访问相似。在这种情况下,引用实例成员的简单名字将是一个编译时错误。
否则,对于带有每个简单名字出现在其中的命名空间的命名空间N,继续采用每个封闭命名空间(如果有的话),并以全局命名空间结束,下面的步骤将被计算,直到一个实体被定位。
- 如果I是在N 中的一个命名空间的名字,并且没有指定类型实参列表,那么简单名字将引用该命名空间。
- 否则,如果I是在带有匹配类型参数数量的N中的一个可访问类型的名字,那么简单类型引用由给定类型实参构造的那个类型。
u 如果命名空间声明包含一个关联由I给定名字的using 别名指令,这里I是一个导入命名空间或类型,并且没有指定类型实参列表,那么简单名字引用该命名空间或类型。
u 否则,如果由命名空间声明的using 命名空间指令导入的命名空间,恰好包含一个由I给定名字的,匹配类型参数数量的类型,那么简单名字引用由给定类型实参构造的类型。
u 否则,如果由命名空间声明的using命名空间指令导入的命名空间,包含多个由I给定名字,匹配类型参数数量的类型,那么简单名字是不明确的,将导致编译时错误
l 否则,由简单名字给定的名字是未定义的,将导致编译时错误。
20.9.4成员访问
如下内容可代替§7.5.4。
成员访问由基本表达式或预定义类型,紧接一个“.”标记,再紧接一个标识符,然后接着可选的类型实参列表而组成。
member-access:(成员访问:)
primary-expression . identifier type-argument-list opt(基本表达式. 标识符 类型实参列表可选)
predefined-type . identifier type-argument-list opt(预定义类型. 标识符 类型实参列表可选)预定义类型:下列之一
bool byte char decimal double float int long
object sbyte short string uint ulong ushort
对于E.I或E.I<A1,…,AN>形式的成员访问,这里E是一个基本表达式或预定义类型,I是一个标识符,并且<A1,…,AN>是一个可选类型实参列表,将按如下方式被计算和分类。
如果E 是一个命名空间,I是E 中嵌套命名空间的名字,并且没有指定类型实参,那么结果就是这个命名空间。
如果E是一个命名空间,I是在E中可访问类型的名字,E 匹配类型参数的数量,那么结果就是由给定类型实参构造的类型。
如果E是一个预定义类型或作为一个类型而被分类的基本表达式,如果E 不是类型参数,并且如果在E中I的成员查找产生一个匹配,那么E.I被计算,并按如下分类。
- 如果I标识一个或多个类型声明,那么使用相同数量的(可能是零)类型参数来确定该类型声明,就像是在类型实参中提供的那样。结果就是由给定类型实参而构造的类型。如果类型声明不匹配类型参数的数量,将出现编译时错误。
- 如果I标识一个或多个方法,那么结果是没有关联实例表达式的方法组。如果类型实参列表被指定,它将在泛型方法调用中被使用(§20.6.3)。
- 如果I 标识一个静态属性、静态字段、静态事件、常数或一个枚举成员,并且如果类型实参列表被指定,将出现编译时错误。
- 如果I标识一个静态属性,那么结果是带有无关联实例表达式的属性访问。
- 如果I标识一个静态字段
u 如果字段是只读的,并且引用发生在类或结构的静态构造函数之外,字段将在这里被声明。那么结果是一个值,也就是在E中静态字段I的值。
u 否则,结果是一个变量,也就是在E中的静态字段I。
- 如果I标识一个静态事件
u 如果引用发生在事件被声明的类或者结构中,并且事件被声明时没有使用事件访问器声明(event-accessor-declaration)(§10.7),那么E.I就好像I是一个静态字段一样被处理。
u 否则,结果是一个无关联的实例表达式的事件访问。
- 如果I标识一个常数,那么结果是值,也就是常数的值。
- 如果I标识一个枚举成员,那么结果是一个值,也就是枚举成员的值。
- 否则,E.I是一个无效成员引用,并将导致编译时错误。
如果E是一个属性访问、索引器访问、变量或值,其类型是T,并且在T中I的成员查找产生一个匹配,那么E.I按如下方式被计算和分类。
- 首先,如果E是一个属性或索引器访问,那么属性或索引器访问的值将被获得(§7.1.1),并且E被重分类为值。
- 如果I标识一个或多个方法,那么结果是一个带有关联的E的实例表达式的方法组。如果类型实参列表被指定,它将在泛型方法调用中被使用(§20.6.3)。
- 如果I标识一个实例属性、实例字段或实例事件,并且如果类型实参列表被指定,将产生编译时错误。
- 如果I标识一个实例属性,那么结果是一个带有关联的E 的实例表达式。
- 如果T是一个类类型并且I 标识一个类类型的实例字段
u 如果E的值是null,那么将会抛出System.NullReferenceException。
u 否则,如果该字段是只读的,并且引用出现在字段声明的类的实例构造函数之外,那么结果是值,也就是由E引用的对象中 I的值。
u 否则,结果是变量,也就是由E引用的对象中字段I。
- 如果T是一个结构类型,并且I 标识该结构类型的实例字段
u 如果E是一个值,或者如果字段是只读的,并且引用出现在字段声明的结构的实例构造函数之外,那么结果是一个值,也就是由E给定的结构实例中字段I的值。
u 否则,结果是一个变量,也就是由E给定结构实例中的字段I;
- 如果I标识一个实例事件
u 如果引用出现在该事件被声明的类或结构之内,并且事件被声明时没有使用事件访问器声明,那么E.I就好像I是一个实例字段一样被处理。
u 否则,结果是一个带有关联的E的实例表达式。
否则,E.I是一个无效成员引用,将导致编译时错误。
20.9.5方法调用
如下内容可替换§7.5.5.1中描述方法调用的编译时处理部分。
对于M(A)形式的方法调用的编译时处理,其中M是一个方法组(可能包含一个类型实参列表),A是可选实参列表,由如下步骤组成。
方法调用的候选集合被构造。对于每个与方法组M关联的方法F
- 如果F是非泛型的,当如下成立时F是候选项
u M没有类型实参列表,并且
u 对于A(§7.4.2.1) ,F是适用的。
- 如果F是泛型,并且M没有类型实参列表,当如下成立时,F是候选项
u 类型推断成功(§20.6.4),对于调用推断出类型实参的列表,并且
u 一旦推断的类型实参替换了对应方法类型参数,F的参数列表对于A是适用的,并且
u 在替换类型实参后,F 的参数列表,与适用的可能以其扩展形式(§7.4.2.1)在相同的类型中作为F而声明的非泛型方法是不同的。
- 如果F是泛型,并且M包括一个类型实参列表,当如下成立时,F是候选项
u F和在类型实参列表中提供的一样,具有相同数量的方法类型参数,并且
u 一旦,类型实参替换对应的方法类型参数,F的参数列表对于A 是可适用的(§7.4.2.1)。
候选方法的集合被缩减到只包含从深度派生类型而来的方法:对于在集合中的每个C.F方法,C是F在其中声明的类型,在C的基类型中声明的所有方法都被从集合中删除。
如果候选方法的结果集合是空的,那么没有可适用的方法存在,并且会出现编译时错误。如果候选方法并不是在同一类型中声明的,方法调用将是不明确的,并且会出现编译时错误(这后一种情况,只可能出现在对于一个在具有多重直接基接口的接口中的方法的调用,如§13.2.5的描述)。
候选方法集合的最佳方法使用重载决策规则(§7.4.2)标识。如果一个单一的最佳方法不能被标识,该方法调用是不明确的,并产生编译时错误。当执行重载决策时,泛型方法的参数在将对应的方法类型参数替换为类型实参(提供的或推断的)之后将被考虑。
被选择的最佳方法的最后验证被执行
- 方法在方法组的上下文中是有效的:如果方法是一个静态方法,方法组必须通过类型源自于简单名字或成员访问。如果该最佳方法是一个实例方法,方法组必须通过一个变量或值或者基类访问(base-access)源自于简单名字或成员访问。如果这些需求都不满足,那么将会出现编译时错误。
- 如果该最佳方法是一个泛型方法,类型实参(提供的或推断的)将被针对声明在泛型方法之上的约束作出检查。如果任何类型实参不满足对应类型参数的约束,将产生一个编译时错误。
一旦方法根据前面的步骤被选择和验证,实际的运行时调用将根据§7.4.中的函数成员调用规则而被处理。
20.9.6委托创建表达式
如下内容可替换§7.5.10.3中委托创建表达式的编译时处理部分。
对于new D(E)形式的委托创建表达式的编译时处理,其中D 是一个委托类型,E是一个表达式,由如下步骤组成。
如果E是一个方法组
- 对应于E(A)形式的方法调用,一个单一的方法调用将被选择。
u D的参数类型和修饰符(ref或out)被用作实参类型和实参列表A的修饰符。
u 在适用的测试和类型推断中,转换不予考虑。在隐式转换足够的实例中,类型要求是同一的。
u 重载决策步骤不会执行。相反,候选的集合必须恰好包含一个与D兼容的方法(接着使用类型实参替换类型参数),并且这个方法将变成新创建委托所引用的方法。如果没有匹配的方法存在,或有多个匹配的方法存在,将发生编译时错误。
- 如果被选择的方法是一个实例方法,与E关联的实例表达式确定委托的目标对象。
- 结果是一个D类型的值,也就是引用选择的方法和目标对象的新创建委托。
否则,E是一个委托类型的值
- D和E必须兼容;否则出现编译时错误。
- 结果是D类型的值,也就是像E一样引用相同的调用列表的新创建的委托。
否则,委托创建表达式是无效的,并且出现编译时错误。
20.10右移语法改变
泛型使用“<”和“>”字符分隔类型参数和类型实参(与C++的模板语法相似)。构造类型有时可嵌套,如List<Nullable<int>>,但使用这种构件有一些微妙的语法问题:词法将组合这个构件的最后两个标记“>>”(右移运算符),而不是产生句法需要的两个“>”标记。尽管一个可能的解决方案是在两个“>>”中放入空格,这也是很尴尬而令人迷惑的,并没有以任何方式增加程序的简洁性。
为了让这些中性的构件维持简单的词法,“>>”和“>>=”标记从词法中删除了,取代的是右移和右移赋值产生式。
运算符或标点:之一
{ } [ ] ( ) . , : ;
+ - * / % & | ^ ! ~
= < > ? ++ -- && || == ->
!= <= >= += -= *= /= %= &= |=
^= << <<=
right-shift:(右移:)
> >
right-shift-assignment:(右移赋值)
> >=
不像在句法中的其他产生式,在右移和右移赋值产生式的标记之间不允许任何种类的字符存在(甚至是空格)。
下面的产生式被使用右移或右移赋值进行了修改。
shift-expression:(移位表达式:)
additive-expression(附加表达式)
shift-expression << additive-expression(移位表达式 << 附加表达式)
shift-expression right-shift additive-expression(移位表达式 right-shift 附加表达式)
assignment-operator:(赋值运算符:)
=
+=
-=
*=
/=
%=
&=
|=
^=
<<=
right-shift-assignment
overloadable-binary-operator:(可重载二元运算符:)
+
-
*
/
%
&
|
^
<<
right-shift
==
!=
>
<
>=
<=
(泛型完)
****************************
终于把这一章贴完了,哎,手都酸了,有问题的地方希望大家多多提出来,千万
……