仅从形式上看,C程序就是由各种声明和定义组成的。它们是程序的骨架和外表,不仅定义了数据(变量),还定义了行为(函数)。规范中的纯语言部分,声明和定义亦花去了最多的篇幅。完全说清定义的语法比较困难,这里也只是个人的理解。

1. 标识属性

  对C编译器而言,标识(identifier)包括对象名、函数名、复合类型及枚举tag、typedef类型名、label和枚举常量。标识的各种属性构成了C的复杂功能,理清这些概念对C的高级使用尤其重要。

  域(scope)可以看做是标识的活动范围,一个编译单元中该范围是层次结构的。最外层当然就是整个编译单元(file scope),它其中包含了各种块(block scope)和子块,这些构成了域的层次结构。这里的块是个宽泛的概念(非规范定义),包括类型定义、函数声明和函数定义。每一个域中的按照标识类型又可以划分为不同的名字空间(name space),同一名字空间的标识不可重名(冲突)。一般情况下,一个父域中的标识可以在子域中活动,但当子域中有与其冲突的标识时,该标识即覆盖父域的标识,使其不可见。大部分标识的可见范围是从其完整定义到其所处域的结尾,tag的可见范围是从其标识结束即开始的,枚举常量从其定义结束可见,label的可见范围是整个函数(function scope)。另外,函数定义不可以出现在块域中,而函数声明、结构和联合则可以。

typedef struct S {         // file scope
struct S* p; // S can be used, p block scope
struct T {int i;} t; // ok
} S; // file scope
enum {
RED, // file scope
BLACK = RED // ok
};
void f(int i); // f file scope, i block scope
int a; // file scope void f(int i) // i block scope
{
LABEL: // function scope
goto LABEL;
int m; // ok in C99
int a; // ok, cover global a
void fun(void) {} // illegal
extern void fun(void); // ok
struct T {int i;} t; // ok
}

  名字空间可分为:label、tag、其它(非规范定义)。这样的分类是按照标识在语法出现的位置决定的,label总是后面跟冒号,tag前面总有关键字struct、union或enum,所以他们总是可以区分出来的。而其它标识可能出现在相同语境中,重名会造成歧义。

typedef struct S {int i;} S; // ok
struct T {
int T; // ok
} S; // name conflict
enum {
S, // name conflict
T // ok
}
void F(void)
{
F: // ok
int F; // name conflict
}

  域的概念一般出现在编译过程中,而链接属性(linkage)出现在链接过程中,它只针对对象和函数。所有extern修饰的对象和函数都有外部链接(external linkage)的属性,所有文件域的标识都有链接属性,其中含有static修饰符的是内部链接(internal linkage),不含static的是外部链接,其它所有标识皆没有链接属性。外部链接的标识会存储在编译结果中,链接过程负责把标识替换为真实地址,而内部链接的标识符外部不可用。对于未初始化的外部链接对象,如果含有extern则仅是声明,不分配空间,否则是定义。

struct S {int i;};           // no linkage
extern int a[]; // external, declare only
static int b; // intenal
extern int m = ; // define
int n; // define
static void f1(void) {} // internal void f2(void) // external
{
extern void fun(void); // external, declare only
static int m; // no linkage
int n; // no linkage
}

  存储类型(storage class)表示运行时对象的存储方法,分为静态存储(static)、动态存储(dynamic)和自动存储(aotomatic)。所有文件域和有static标识符的对象都是静态存储的,它们在程序生命周期中一直存在。动态存储是通过库函数产生的对象,它们存储在特殊的区域(堆),由用户负责申请和释放。块域的对象在进入块时分配空间(新规范支持在块中间定义),在退出块时释放空间,唯一的例外是变长数组在定义时才分配空间。自动存储的对象默认由auto修饰,也可以由register修饰,它建议把对象存放在寄存器中。但其实现代编译器的优化可以做到这一点,所以register已经过时。

int m;                      // static
static int n; // static
register int r; // illegal void f(void)
{ // alloc a
n = m;
static int s; // static
auto int a; // automatic, the same as int a
{ // alloc b
a = s;
register int b[]; // automatic
int c[a]; // alloc c
} // free b, c
} // free a

  C中对对象有一些限定符(qualifier),它们一般起到限制和优化的作用。const所修饰的对象不可通过相应变量直接修改,所以只能在定义时初始化,编译器可能把它当常量使用。volatile所修饰的对象可能被外部改变(系统时间),提醒编译器每次使用时从内存加载。restrict修饰符只作用于指针(受限指针),表示在该指针生命周期内,所指对象(可以是多个)只能直接或间接通过该指针修改,编译器可以对此进行优化。受限指针可以由子域中的另一个受限指针“接管”,而没有未定义行为。

const int c = ;
const int *pc;
int *pi;
volatile int v = ;
const volatile long long sys_tick; // system time
retrict int n; // illegal c = ; // illegal
pc = &c; // ok
pi = &c; // illegal
pi = (int*)&c; // ok
*pc = ; // illegal
*pi = ; // ok
v; // may be not 0 void f(restrict int* p)
{
restrict int *q = p; // undefind
{
restrict int *s = p; // ok
}
}

  函数修饰符(function specifier)作用于函数,包括inline和_Noreturn,它们不能用于main函数。inline建议编译器优化函数代码,它一般在编译单元内部使用,但也可以外部访问(对外是函数形式)。_Noreturn表示函数不会返回,一般直接结束程序(比如abort)。对齐修饰符(alignment specifier)限定对象的对齐方式(已介绍过),当作用于组合类型时,它对每一个成员其作用。

// file 1
extern void f(void);
_Noreturn void fun(void)
{
f(); // function call
return; // illegal
} // file 2
inline void f(void) {}
_Noreturn int main(void) // illegal
{
f(); // inline
abort();
}

2. 一般声明

  首先说明一下,规范中的“声明”包括了定义,下文中一般名词性的“声明”包含定义,其它都是我们常用的单纯“声明”。

  结构(联合)的定义一般叫模板,结构、联合和枚举的类型名叫tag。结构(联合)可以声明(incomplete type),可用于头文件中隐藏具体细节,亦可用于结构(联合)间的互相应用。结(联合)在花括号之后成为完整类型,相互赋值时空隙部分并不赋值。可以有长度为0位域,它结束当前word,但不可以有名字。当成员是结构(联合)且没有变量名,它称为匿名结构(联合),其成员成为上层结构(联合)的成员。枚举不可以声明,它的定义中可有一个多余的逗号。枚举常量的类型为int型,枚举变量为整型(基于实现),枚举变量的值在调试时可显示为枚举常量(比普通整型好)。

// head file
struct S; // declaration
void f(struct S* p); // ok
void f(struct S s); // illegal struct S2;
struct S1 {
struct S2 *ps2; // ok
struct S1 s1; // illegal
} s1; // ok
struct S2 {struct S1 *ps1;}; struct S {
char c;
int a : ;
int b : ; // illegal
int : ; // end the word
} s1, s2;
s1 = s2; // not the same as memcpy struct S {
struct T {
int m;
struct {int n}; // anonymous
}; // anonymous
int m, n; // name conflict
} s;
s.m + s.n; // ok enum E; // illegal
enum E {ONE,}; // ok
enum E e = ONE;

  函数和栈结构可以及时释放临时变量,提高内存利用率。函数可以递归调用(包括main),但会消耗更多的内存和时间。函数返回类型的修饰符会被忽略,只需有函数修饰符,函数不返回数组或函数。函数本地参数叫形参(parameter),调用者传递的值叫实参(argument),该定义同样适用于宏。参数列表由逗号隔开是来源于旧规范(见示例),其实分号做分隔符更好。函数参数格式上可以是数组或函数(提示作用),但它们等价于对应的指针形式,register是唯一可以使用的存储类型修饰符。C支持变长参数列表,仅需在最后一个参数后加三个点,之后不可再有参数定义。必须要有显式参数,用来获得变长参数列表的起始地址。标准库<stdarg.h>提供了使用变长参数的方法,但三个点本身不需要库支持。值得一提的是,变长参数列表只能由调用者自己清理(calling convention),而windows中默认是函数清理栈(__stdcall),需要使用__cdecl修饰符支持变长参数列表。函数原型(prototype)可协助进行编译时类型检查,仍支持旧规范中的空参数,它表示参数不确定(在定义中表示没有参数)。函数原型中的参数名可与定义中不同,甚至可以没有参数名。函数参数和函数体属于同一个block,注意名字冲突。

int main();                              // old C, not know parameter
int main() // old C, no parameter
{
static s = ;
if ( == s) return ;
else return main() + s--; // ok
} int fun(x, y) int x; int y; {return ;} // old C
void fun(int x, y); // illegal
void fun(int); // ok
void fun(register int m); // ok
void fun (int a) {int a;} // name conflict const int f(int a[]) {return ;} // the same as int f(int* a)
void g(int f(int*)) {} // the same as void g(int (*f)(int*))
int m = f((int[]){, ,}); // ok
g(f); // ok void fun(...); // illegal
void fun(int, ..., int); // illegal
void fun(int, ...); // ok, no need <stdarg.h>
void __cdecl fun(int, ...); // necessary in windows

  新规范中数组参数可以有更丰富的形式,array_specifier是数组参数方括号里的内容。本节的语法仍只是说明性的,非规范定义,除特别说明方括号表示可选。

array_specifier:
[type_qulifier_list] [assignment_exp]
static [type_qulifier_list] assignment_exp
type_qulifier_list static assignment_exp
[type_qulifier_list] *

当然这里的数组是指第一维的,它会转换成指针,指针的类型限定符可以写到方括号里。这里的表达式(不可以是逗号表达式)一般没有实际意义,static修饰符被复用,用来提醒调用者数组长度至少为表达式的值。对高维数组的其它维,方括号里仅可以是表达式或星号,其中星号仅用于函数原型。

void f(int a[, ]);            // illegal
void f(int a[const ]); // the same as const int* a
void f(int a[const static ]); // ok
void f(int a[static const ]); // ok
void f(int a[static const]); // illegal
void f(int a[const *]); // ok
void f(int a[*][*]); // ok
void f(int n, int a[n][n]); // ok
void f(int a[*][*]) {} // illegal

  一般数组长度必须为常整型,const变量也不行。新规范中支持变长数组(VLA,variable length arrary),VLA定义时数组长度是整型表达式(非常数)。VLA只能在块域,它在每次定义时确定长度并分配空间,但一旦确定长度,在生命周期内不会改变。VLA和任何数组是类型兼容的,其指针可互相赋值。指向VLA的指针一般叫VM(variably modified),它也必须在块域并且无连接。VM可以是静态存储,而VLA则不可以,VM和VLA都不可以出现在结构或联合里。

int const n = ;
int a[n]; // illegal, static VLA
extern (*p)[n]; // illegal, linkage VM
int b[];
struct S {
int a[n]; // illegal, VLA in struct
int (*p)[n]; // illegal, VM in struct
}; int main(void)
{
int m = ;
int a[m++]; // ok, auto VLM
sizeof(a); // sizeof(int) * 2
static int b[n]; // illegal, static VLA
extern int b[n]; // illegal, linkage VLA
extern (*p)[n]; // illegal, linkage VM
static (*p)[n] = &b; // ok, no linkage block VM
}

3. 复杂声明

declare:
declare_specifier [init_declarator_list] declare_specifier:
storage_class_specifier [declare_specifier]
alignment_specifier [declare_specifier]
function_specifier [declare_specifier]
type_qualifier [declare_specifier]
type_specifier [declare_specifier] init_declarator:
declarator [= initializer] storage_class_specifier:
one of {typedef, extern, static, auto, register}
alignment_specifier:
_Alignas(type or int_const)
function_specifier:
one of {inline, _Noreturn}
type_qualifier:
one of {const, volatile, restrict}
type_specifier:
one of {void, char, short, int, long, float, double, signed, unsigned, _Bool, _Complex}
struct_union_specifier
enum_specifier
typedef_name

声明语句由声明修饰(declare_specifier)和声明列表(init_declarator_list)组成,其中声明列表用逗号作分割符。一个完整的声明可以粗略分为三个部分(非规范定义):属性、类型和扩展(非规范定义)。扩展部分包含附加操作、声明对象名和初始化,附加操作是对声明对象的类型补充(见下段)。属性包括存储类型(storage_class_specifier)、对齐(alignment_specifier)和函数性质(function_specifile),它们所修饰的是最终声明对象。声明中只能有一个存储类型,typedef在使用形式上与存储类型一致,所以也统一到该类中。类型包括类型限定(type_qualifier)和类型修饰(type_specifier),它们都可以看做是类型的一部分,所修饰的是整个扩展部分(非声明对象,也不互相修饰)。类型限定可以组合使用,类型修饰要么是定义中第一类的组合,要么是后三者之一(四类不可组合使用)。从定义中可以看出,所有属性和类型的的顺序是随意的,但建议按习惯的顺序使用。

typedef static int INT;                  // illegal, 2 storage_class_specifier
typedef int INT; // ok
const restrict int *p; // ok, 2 type_qualifier, all apply on *p, not p
const struct S {int i;} s1;
struct S s2;
s1.i = ; // illegal, s1 is const
s2.i = ; // ok, S is not const
unsigned INT a; // illegal, 2 type_specifier static _Alignas() unsigned long int a; // ok, good style
static int _Alignas() a; // ok
int static a; // ok
int typedef INT; // ok
static int long unsigned a; // ok
long static int unsigned a; // ok
unsigned int long typedef UL; // ok

  以下是扩展部分的附加操作和声明对象名语法,除[array_specifier]外方括号皆表示可选。

declarator:
identifier
(declarator)
* [type_qualifier_list] declarator
declarator[array_specifier]
declarator(parameter_list)

  附加操作也是声明对象类型的一部分,包括指针、数组和函数。类型顺序和操作优先级一致,因为后缀操作有限级高,有时指针需要加括号。指针后面可以跟类型限定符,它同样与指针都是类型的一部分。扩展部分是可选的,但仅用于模板定义中。没有扩展时结构和联合必须有tag,枚举可以没有tag。

int *a[];             // array of pointer
int (*a)[]; // pointer to array
int f(void)[]; // illegal, function return array
int *a[](void); // illegal, array of function
int (*a[])(void); // ok, array of function pointer
int (*f(void))(void); // ok, function return function pointer const int *p; // *p is const
int *const p; // p is const
int *const *pp; // *pp is const
struct {int i;} s; // ok
struct S {int i;}; // ok, define
struct {int i;}; // illegal, but ok as a member
enum {ONE}; // ok

  以下是结构和联合的定义语法。

struct_union_specifier
struct_or_union [identifier] {struct_declare_list}
struct_or_union identifier struct_declare:
type_specifier_qualifier_list [struct_declarator_list]; struct_declarator:
declarator
[declarator]: int_const

  不管是tag还是整个定义,结构(联合)都是作为类型使用的。成员不可以是函数或不完全类型,当然在花括号之前定义本身也不完全。成员只能用类型,不能有属性修饰符。成员可以用列表形式,但不能初始化。

int n = ;
struct S {int i;} const static s1; // ok
struct S {
void f(void); // illegal, funtion member
int a[]; // illegal, incomplete type
struct S s; // illegal, incomplete type
int m = ; // illegal, can't init
const int a, *b; // ok
static _Alignas() int c; // illegal
};

  在有些场合只需要类型而不需要实例,比如强制转换、复合常量、函数原型。大部分场合类型只需声明中的类型和附加操作两部分,需要找到原本声明对象所在的位置来确定最终类型。大部分情况可以根据优先级找到类型起点,当出现空的圆括号时当函数看待。

(int(*)[]);          // cast to pointer to array
(int*[]){NULL, NULL}; // pointer array literal
(int(*[])()){NULL}; // functon pointer array literal
void f(int()); // convert to int(*)()
void f(int(*)[*]); // VM

  对于复杂类型,最好使用typedef重命名类型,以使定义更清晰。typedef不可与其它属性一起使用,但可以包括类型限定符。它可以重命名不完全的结构(联合),但在完整定义前仅能用其指针,不可以重命名不完全的枚举。重命名的不完全数组,可在数组定义时确定数组长度,重命名的VLA和VM在每次使用时确定数组长度。typedef仅是重命名,不改变原有定义的性质。

void (*signal(int id, void(*hdl)(int)))(int);  // from <signal.h>
typedef void (*sig_t)(int);
sig_t signal(int num, sig_t hdl); // much more clear typedef static int si; // illegal
typedef _Alignas() int ai; // illegal
typedef const int ci; // ok
typedef struct S S; // ok
typedef enum E E; // illegal typedef char Array[] ; // ok
Array a = {}, b = {, }; // sizeof(a) = 1, sizeof(b) = 2
void f(void)
{
int n = ;
typedef char VLA[n], (*VM)[n]; // ok
n++;
VLA vla; // sizeof(vla) = 2
VM vm = &vla; // ok
} typedef int T;
struct S {
T t : ; // signed or unsigned
unsigned T : ; // unsigned member named T
const T : ; // anonymous member
};

4. 初始化

initilizer:
assignment_exp
{initilizer_list}
{initilizer_list,}

  初始化包含在变量定义中,它为对象提供初始值。初始化列表含有一对花括号,以及其中由逗号分隔的初始化项,每个初始化项可以是初始化列表,也可以是表达式。由于逗号已经用作分隔符,所以初始化表达式不能是逗号表达式,列表末尾的逗号无意义。如果对象是度量值或浮点数,花括号可省略。如果对象是复合类型,也可直接互相赋值。对字符串类型,可直接用字符串赋值(可带花括号)。

int a = , ;                        // illegal
int a = {}; // the same as int a = 1
int a[] = {, , }; // only two members
struct S {int i} s1 = {}, s2 = s1; // ok
char str[] = "hi"; // the same as {"hi"}
char str[] = "hi"; // ok, sizeif(str) = 2

  组合类型的初始化列表依次初始化成员,如遇到组合类型成员则同样初始化其每个成员。位域中的匿名成员不参加初始化,联合只初始化第一个成员,其它成员若有空隙则按比特置零。若列表不足,则剩余的成员按类型置零(整数为0、浮点数为0.0、指针为空指针)。长度不定的数组以初始化列表的长度为准,否则以数组长度为准,列表长度不可以超过数组长度(字符串除外)。

typedef struct S {int a[]; int b:; int :; int c, d;} S;
typedef union U {char c; int i;} U; S s = {, , , }; // s.a[0] = 1, s.a[1] = 2, s.b = 3, s.c = 4, s.d = 0
U u = {'a', }; // illegal
U u = {'a'}; // u.c = 'a', other bits to 0
char* strs[] = {"hi"}; // strs[1] = NULL
char str[] = {'h', 'i', '\0'}; // illegal

  一对花括号对应一个当前对象,即使正在初始化成员的成员,当前对象仍然是花括号所对应的对象。对成员也可以使用初始化列表,这时的当前对象切换为该成员,一切规则以当前对象执行。当前成员切换出来时,从下一个成员继续执行。新规范还支持指定初始化(designated initilization),可以在初始化列表中指定具体成员(及其成员)初始化,此过程也进行当前对象切换,切换回来后从被指定成员(当前对象的)的下一个成员继续执行。当然指定初始化可能覆盖之前的初始化,也可改变当前成员。

typedef struct S {int a[][]; int b} S;

S s = {, , , , };                       // cur_obj not change
S s = {{, , ,}, }; // cur_obj s -> s.a -> s
S s = {{[] = {, }, [][] = }, .b = }; // ok
S s = {.a[][] = , }; // b = 2, cur_obj s -> s.a[0][0] -> s
S s = {.a = {[][] = , }}; // a[1][0] = 2, cur_obj s ->s.a -> s.a[0][0] -> s.a -> s
S w[] = {{}, }; // w[0].a[0][0] = 1, w[1].[0][0] = 2, cur_obj w -> w[0] -> w

  静态存储的对象的值存储在可执行文件的数据区,需要编译时确定,所以只能用常数初始化,自动存储的对象无此限制。静态存储变量只在运行初被初始化一次,未显示初始化的也按类型置为0。可变长数组和结构尾部的可变数组不可以初始化。初始化列表从左向右执行,但其中没有序列点,可能有不确定行为。

typedef struct S {struct S *p;} S;
typedef struct V {int i; int a[]} V;
int i = ; int a[] = {i++, i++}; // a[1] = 1 or 2
V v = {, }; // illegal
S s1, *ps = &s1; // ok, s1.p = NULL
S s2 = {ps}; // illegal
void f(void)
{
static S s3 = {&s3}; // ok, only once
S s4 = {ps}; // ok
int a[i] = {}; // illegal
}
 

【C】 05 - 声明和定义的更多相关文章

  1. C++中重定义的问题——问题的实质是声明和定义的关系以及分离式编译的原理

    这里的问题实质是我们在头文件中直接定义全局变量或者函数,却分别在主函数和对应的cpp文件中包含了两次,于是在编译的时候这个变量或者函数被定义了两次,问题就出现了,因此,我们应该形成一种编码风格,即: ...

  2. C\C++中声明与定义的区别

    声明和定义是完全同的概念,声明是告诉编译器"这个函数或者变量可以在哪找到,它的模样像什么".而定义则是告诉编译器,"在这里建立变量或函数",并且为它们分配内存空 ...

  3. 变量声明和定义及extern 转载

    在讨论全局变量之前我们先要明白几个基本的概念: 1. 编译单元(模块):    在IDE开发工具大行其道的今天,对于编译的一些概念很多人已经不再清楚了,很多程序员最怕的就是处理连接错误(LINK ER ...

  4. C/C++中的声明与定义

    含义 声明(Declaration), 用于告诉编译器被声明的函数/变量的存在, 及它们的类型/调用格式信息, 以检查是否被正确调用. 声明不分配内存空间. 定义(Definition), 用于告诉编 ...

  5. 浅谈声明与定义的区别 分类: C/C++ 2015-06-01 15:08 157人阅读 评论(4) 收藏

    以下代码使用平台是VS2012. 清楚明白声明与定义是一名合格的程序猿的基本要求. 本人认为,C++编码过程中谈及"声明"和"定义"是因为我们要使用一个变量.类 ...

  6. switch语句下的变量声明和定义

    switch语句下的变量声明和定义的问题: switch...case...语句中存在声明和定义会出现一些问题.这个由switch语法特性决定的, switch中每个case都是平等的层次,区别于一般 ...

  7. [转载]C++声明和定义的区别

    <C++Primer>第四版 2.3.5节中这么说到: ①变量定义:用于为变量分配存储空间,还可为变量指定初始值.程序中,变量有且仅有一个定义. ②变量声明:用于向程序表明变量的类型和名字 ...

  8. C/C++:[2]enum-枚举量声明、定义和使用

    C/C++:[2]enum-枚举量声明.定义和使用 转自:http://jingyan.baidu.com/article/e75aca85526c1b142edac6d9.html 众所周知,C/C ...

  9. 声明、定义 in C++

    序 声明和定义是我们使用的基础,但是对于声明和定义的概念,我们不甚了了,也就是说感觉好像是这样,但是真要详细说明就说不上来. 有博主对于声明和定义有以下描述:          1.需要建立存储空间的 ...

随机推荐

  1. org/objectweb/asm/Type异常解决办法

    关于java.lang.NoClassDefFoundError: org/objectweb/asm/Type 调试SPRING MVC(或者整合SSH)的时候遇到了org/objectweb/as ...

  2. linux下mysql函数的详细案列

    MYSQL * STDCALL mysql_real_connect(MYSQL *mysql, const char *host, const char *user, const char *pas ...

  3. 加强型无穷集合:InfiniteList<T>,可指定遍历方向和偏移量,只要集合有元素并且偏移量不为 0,将永远遍历下去。

    主类: public class InfiniteList<T> : IEnumerable<T> { public List<T> SourceList { ge ...

  4. JAVA中怎么处理高并发的情况

    一.背景综述 并发就是可以使用多个线程或进程,同时处理(就是并发)不同的操作. 高并发的时候就是有很多用户在访问,导致系统数据不正确.糗事数据的现象.对于一些大型网站,比如门户网站,在面对大量用户访问 ...

  5. 【必备】史上最全的浏览器 CSS & JS Hack 手册(转)

    浏览器渲染页面的方式各不相同,甚至同一浏览器的不同版本(“杰出代表”是 IE)也有差异.因此,浏览器兼容成为前端开发人员的必备技能.如果有一份浏览器 Hack 手册,那查询起来就方便多了.这篇文章就向 ...

  6. WCF初探-10:WCF客户端调用服务

    创建WCF 服务客户端应用程序需要执行下列步骤: 获取服务终结点的服务协定.绑定以及地址信息 使用该信息创建 WCF 客户端 调用操作 关闭该 WCF 客户端对象 WCF客户端调用服务存在以下特点: ...

  7. win7(64)位下WinDbg64调试VMware10下的win7(32位)

    win7(64)位下WinDbg64调试VMware10下的win7(32位) 一 Windbg32位还是64位的选择 参考文档<Windbg 32位版本和64位版本的选择> http:/ ...

  8. Client默认用户及登录密码(转)

    Client默认用户及登录密码 SAP系统(如ERP.CRM等)安装完成,初始化状态下有若干个客户端(Client).如果是生产系统,一般只有000.001.066等三个Client:如果是IDES系 ...

  9. Quartus ii 12.0 和ModelSim 10.1 SE安装及连接

    quartus ii 10.0后就没有自带的仿真软件,每次写完一个VerilogHDL都想简单仿真一下,结果发现没有了自带仿真软件.这时候就需要第三方仿真软件ModelSim 10.1 SE. Qua ...

  10. NodeJs和ReactJs单元测试工具——Jest

    Jest——Painless JavaScript UnitTesting 特点 适应性强 默认使用Jasmine断言 模块化的 可扩展的 可配置的 沙箱式且快速 虚拟化JS环境,模拟浏览器 并行运行 ...