不允许dllimport 静态数据成员
“CTest
“CTest
//代码如下
template
class__declspec(dllimport) CTest
{
public:
CTest();
~CTest();
};
template
CTest
{
//
}
template
CTest
{
//
}
当要使用一个类的时候必须要有其定义,有2种方式:
1、引用其头文件,即#include "xxx.h"。这是传统的方式。
2、使用导出类。
什么是…导出类?,很简单,使用__declspec(dllimport)定义的类即为导出类。例如:
class __declspec(dllimport) CTest
{
}
__declspec(dllimport)是MS特有的描述符,看名字就知道是用在DLL链接时用到的,DLL是WINDOWS的产物,就跨平台而言这样的写法是不可取的。所有通用版本的STL都不会使用这个描述符,看到使用方式2的导出类定义自然就让人费解了。
如果确实需要使用__declspec(dllimport),要注意VC规定:
数据、静态数据成员和函数可以声明,但不能定义为dllimport。
说白了就是,声明和定义分别放在.h及.cpp文件中。即__declspec(dllimport)声明放在.h头文件中,实现放在.cpp文件中。这样一处理,对于普通的函数、类就可以使用方式2所谓的…导出类?了。然而对模板却不行。这里面还有涉及到编译器不能支持对模板的分离式编译的问题。
首先说一下编译器的大致的编译原理。一个.cpp及其包括的所有.h经编译后叫做一个编译单元,即.obj文件,然后由连接器把所有的.obj连接生成一个PE可执行.exe文件。举例main.obj要调用test.obj单元里面的test()函数,此时的main.obj并没有test()函数的代码。连接器于是帮main.obj找到test.obj中test()函数的地址并链接到到main.obj中。平常大家玩反汇编的时候经常看到jmp xxx/call xxx就是连接器在做调配。
再说一下模板。模板是需要…具体化?的,编译器直到碰到使用这个模板代码的时候才会把模板编译成二进制代码。留意一下STL代码你会发现,所有模板代码全都放在一个.h文件中,为什么不分开放在.cpp文件中,因为放在.cpp文件中即成为一个编译单元,一个单元就是一个PE结构,是实在的二进制代码文件,但这个单元没有调用这个模板又哪来的编译单元,于是编译器只能罢工。有没有办法生成单元?有!在.cpp中变态地调用自己声明的模板。
明白这个道理之后也就不难理解为什么有的时候可以编译通过链接的时候却报错了,链接器找不到另一个.obj的相应地址当然报错。
现在来分析一下上面的模板代码为什么会出错,很简单:
既然使用了__declspec(dllimport)声明,却又对CTest()及~CTest()进行定义,违反VC规则“数据、静态数据成员和函数可以声明,但不能定义为dllimport。”
解决:
1、去掉__declspec(dllimport),除非你真的想生成DLL导出类,否则使之成为标准模板。
2、去掉CTest()/~CTest()类外部定义,将定义迁至类内部。为什么不能将这2个函数的定义放在.cpp文件中上面已经有解释了。
上面说的不太完美:添加以下说明:
__declspec(dllexport)
声明一个导出函数,是说这个函数要从本DLL导出。我要给别人用。一般用于dll中
省掉在DEF文件中手工定义导出哪些函数的一个方法。当然,如果你的DLL里全是C++的类的话,你无法在DEF里指定导出的函数,只能用__declspec(dllexport)导出类
__declspec(dllimport)
声明一个导入函数,是说这个函数是从别的DLL导入。我要用。一般用于使用某个dll的exe中
不使用__declspec(dllimport) 也能正确编译代码,但使用__declspec(dllimport) 使编译器可以生成更好的代码。编译器之所以能够生成更好的代码,是因为它可以确定函数是否存在于DLL 中,这使得编译器可以生成跳过间接寻址级别的代码,而这些代码通常会出现在跨DLL 边界的函数调用中。但是,必须使用__declspec(dllimport) 才能导入DLL 中使用的变量。
使用举例:
// File: SimpleDLLClass.h
#ifdef SIMPLEDLL_EXPORT
#define DLL_EXPORT __declspec(dllexport)
#else
#define DLL_EXPORT
#endif
class DLL_EXPORT SimpleDLLClass
{
public:
SimpleDLLClass();
virtual ~SimpleDLLClass();
virtual getValue() { return m_nValue;};
private:
int m_nValue;
};
// File:SimpleDLLClass.cpp
#include "SimpleDLLClass.h"
SimpleDLLClass::SimpleDLLClass()
{
m_nValue=0;
}
SimpleDLLClass::~SimpleDLLClass()
{
}
说明:
1. 在你的APP中include SimpleDLLClass.h时,如果你的APP的项目不定义SIMPLEDLL_EXPORT,则DLL_EXPORT不存在。此时APP仍可以正常运行。
// File: SimpleDLLClass.h
static int m_nValue;
// File:SimpleDLLClass.cpp
int SimpleDLLClass::m_nValue=0;
说明:
1. 如果你的APP的项目不定义SIMPLEDLL_EXPORT,则DLL_EXPORT不存在。此时APP无法LINK。原因是找不到m_nValue。(原因:静态变量m_nValue已被DLL导出,但SimpleDLLClass无法访问m_nV alue)
Workaround:
#define DLL_EXPORT __declspec(dllimport)
Conclusion:
dllimport是为了更好的处理类中的静态成员变量(或者其他...)的,如果没有静态成员变量(或者其他...),那么这个__declspec(dllimport)无所谓.
/////////////////////////
在Windows DLL编程时,可使用__declspec(dllimport)关键字导入函数或者变量。
函数的导入
当你需要使用DLL中的函数时,往往不需要显示地导入函数,编译器可自动完成。但如果你显示地导入函数,编译器会产生质量更好的代码。由于编译器确切地知道了一个函数是否在一个DLL中,它就可以产生更好的代码,不再需要间接的调用转接。
Win32的PE格式(Portable Executable Format)把所有导入地址放在一个导入地址表中。下面用一个具体实例说明使用__declspec(dllimport)导入函数和不使用的区别:
假设func是一个DLL中的函数,现在在要生成的.exe的main函数中调用func函数,并且不显示地导入func函数(即没有:__declspec(dllimport)),代码示例如下:
int main()
{
func();
}
编译器将产生类似这样的调用代码:
call func
然后,链接器把该调用翻译为类似这样的代码:
call 0x40000001 ; ox40000001是"func"的地址
并且,链接器将产生一个Thunk,形如:
0x40000001: jmp DWORD PTR __imp_func
这里的imp_func是func函数在.exe的导入地址表中的函数槽的地址。然后,加载器只需要在加载时更新.exe的导入地址表即可。
而如果使用了__declspec(dllimport)显示地导入函数,那么链接器就不会产生Thunk(如果不被要求的话),而直接产生一个间接调用。因此,下面的代码:
__declspec(dllimport) void func1(void);
void main(void)
{
func1();
}
将调用如下调用指令:
call DWORD PTR __imp_func1
因此,显示地导入函数能有效减少目标代码(因为不产生Thunk)。另外,在DLL中使用DLL外的函数也可以这样做,从而提高空间和时间效率。
变量的导入
与函数不同的是,在使用DLL中的变量时,需要显示地导入变量。使用__declspec(dllimport)关键字导入变量。若在DLL中使用.def导出变量,则应使用DATA修饰变量,而不是使用已经被遗弃的CONSTANT。因为CONSTANT可能需要使用指针间接访问变量,不确定什么时候会出问题。
//////////////
我相信写WIN32程序的人,做过DLL,都会很清楚__declspec(dllexport)的作用,它就是为了省掉在DEF文件中手工定义导出哪些函数的一个方法。当然,如果你的DLL里全是C++的类的话,你无法在DEF里指定导出的函数,只能用__declspec(dllexport)导出类。但是,MSDN文档里面,对于__declspec(dllimport)的说明让人感觉有点奇怪,先来看看MSDN里面是怎么说的:
不使用__declspec(dllimport) 也能正确编译代码,但使用__declspec(dllimport) 使编译器可以生成更好的代码。编译器之所以能够生成更好的代码,是因为它可以确定函数是否存在于DLL 中,这使得编译器可以生成跳过间接寻址级别的代码,而这些代码通常会出现在跨DLL 边界的函数调用中。但是,必须使用__declspec(dllimport) 才能导入DLL 中使用的变量。
初看起来,这段话前面的意思是,不用它也可以正常使用DLL的导出库,但最后一句话又说,必须使用__declspec(dllimport) 才能导入DLL 中使用的变量这个是什么意思??
那我就来试验一下,假定,你在DLL里只导出一个简单的类,注意,我假定你已经在项目属性中定义了SIMPLEDLL_EXPORT
SimpleDLLClass.h
#ifdef SIMPLEDLL_EXPORT#define DLL_EXPORT __declspec(dllexport)#else#define DLL_EXPORT#endifclass DLL_EXPORT SimpleDLLClass{public: SimpleDLLClass(); virtual ~SimpleDLLClass(); virtual g etValue() { return m_nValue;};private: int m_nValue;};SimpleDLLClass.cpp
#include "SimpleDLLClass.h"SimpleDLLClass::SimpleDLLClass() { m_nValue=0;}SimpleDLLClass::~SimpleDLLClass(){}然后你再使用这个DLL类,在你的APP中include SimpleDLLClass.h时,你的APP的项目不用定义SIMPLEDLL_EXPORT 所以,DLL_EXPORT 就不会存在了,这个时候,你在APP中,不会遇到问题。这正好对应MSDN上说的__declspec(dllimport)定义与否都可以正常使用。但我们也没有遇到变量不能正常使用呀。那好,我们改一下SimpleDLLClass,把它的m_nValue改成static,然后在cpp文件中加一行
int SimpleDLLClass::m_nValue=0;如果你不知道为什么要加这一行,那就回去看看C++的基础。改完之后,再去LINK一下,你的APP,看结果如何,结果是LINK告诉你找不到这个m_nValue。明明已经定义了,为什么又没有了??肯定是因为我把m_nValue定义为static的原因。但如果我一定要使用Singleton的Design Pattern的话,那这个类肯定是要有一个静态成员,每次LINK都没有,那不是完了?如果你有Platfor m SDK,用里面的Depend程序看一下,DLL中又的确是有这个m_nValue导出的呀。
再回去看看我引用MSDN的那段话的最后一句。那我们再改一下SimpleDLLClass.h,把那段改成下面的样子:
#ifdef SIMPLEDLL_EXPORT#define DLL_EXPORT __declspec(dllexport)#else#define DLL_EXPORT __declspec(dllimport)#endif再LINK,一切正常。原来dllimport是为了更好的处理类中的静态成员变量的,如果没有静态成员变量,那么这个__declspec(dllimport)无所谓。
实验八静态数据成员和静态函数成员 任务一: 1、了解多文件工程 本次实验需要创建一个工程,此工程由三个文件组成 1)头文件client.h ——类的声明 2)源文件client.cpp——成员函数的定义 3)源文件test.cpp——main()函数的定义 2、了解CLIENT类 本次实验的主角是CLIENT(客户机)类。在实际生活中,计算机网络的应用模式为client/server(客户机/服务器)模式。情况很简单,即多台客户机与一台服务器连接,服务器为客户机提供服务。 3、实验任务 1)阅读程序代码,仔细分析CLIENT类的各数据成员及函数成员,写出分析结果 2)创建多文件工程,编译并运行 3)为main()函数的各条语句增加注释 4)将数据成员ServerName改为非静态,其它类成员的静态属性不变。 修改程序代码,使客户机a连接到另一台服务器M。(b仍与N连接) 任务二: 生成一个储蓄类CK。用静态数据成员表示每个存款人的年利率lixi。类的每个对象包含一个私有数据成员cunkuan,表示当前存款额。提供一个calLiXi()成员函数,计算利息,用cunkuan乘以lixi除以12取得月息,不计复利,并将这个月息加进cunkuan中。提供设置存款额函数set()。提供一个静态成员函数modLiXi(),可以将利率lixi修改为新值。 实例化两个不同的CK对象saver1和saver2,结余分别为2000.0和3000.0。将lixi设置为3%,计算一个月后和3个月后每个存款人的结余并打印新的结果。 首先定义储蓄类CK,它包含一个私有数据成员cunkuan,数据类型为double,一个静态数据成员年利率lixi,数据类型也为double;包含一个成员函数calLiXi()和一个静态成员函数modLiXi(),其中modLiXi()应含有一个表示要更改的年利率的新值的参数。 完善程序: #include
C程序一直由下列部分组成: 1)正文段——CPU执行的机器指令部分;一个程序只有一个副本;只读,防止程序由于意外事故而修改自身指令; 2)初始化数据段(数据段)——在程序中所有赋了初值的全局变量,存放在这里。 3)非初始化数据段(bss段)——在程序中没有初始化的全局变量;内核将此段初始化为0。 4)栈——增长方向:自顶向下增长;自动变量以及每次函数调用时所需要保存的信息(返回地址;环境信息)。 5)堆——动态存储分。 在全局变量之前加上关键字static,全局变量就被定义成为一个全局静态变量。1)内存中的位置:静态存储区(静态存储区在整个程序运行期间都存在)2)初始化:未经初始化的全局静态变量会被程序自动初始化为0(自动对象的值是任意的,除非他被显示初始化) 3)作用域:全局静态变量在声明他的文件之外是不可见的。准确地讲从定义之处开始到文件结尾。 好处: 定义全局静态变量的好处: <1>不会被其他文件所访问,修改 <2>其他文件中可以使用相同名字的变量,不会发生冲突。 局部静态变量 在局部变量之前加上关键字static,局部变量就被定义成为一个局部静态变量。1)内存中的位置:静态存储区 2)初始化:未经初始化的全局静态变量会被程序自动初始化为0(自动对象的值是任意的,除非他被显示初始化) 3)作用域:作用域仍为局部作用域,当定义它的函数或者语句块结束的时候,
作用域随之结束。 注:当static用来修饰局部变量的时候,它就改变了局部变量的存储位置,从原来的栈中存放改为静态存储区。但是局部静态变量在离开作用域之后,并没有被销毁,而是仍然驻留在内存当中,直到程序结束,只不过我们不能再对他进行访问。 当static用来修饰全局变量的时候,它就改变了全局变量的作用域(在声明他的文件之外是不可见的),但是没有改变它的存放位置,还是在静态存储区中。 3.静态函数 在函数的返回类型前加上关键字static,函数就被定义成为静态函数。 函数的定义和声明默认情况下是extern的,但静态函数只是在声明他的文件当中可见,不能被其他文件所用。 定义静态函数的好处: <1>其他文件中可以定义相同名字的函数,不会发生冲突 <2>静态函数不能被其他文件所用。存储说明符auto,register,extern,static,对应两种存储期:自动存储期和静态存储期。auto和register对应自动存储期。具有自动存储期的变量在进入声明该变量的程序块时被建立,它在该程序块活动时存在,退出该程序块时撤销。 关键字extern和static用来说明具有静态存储期的变量和函数。用static声明的局部变量具有静态存储持续期(static storage duration),或静态范围(static extent)。虽然他的值在函数调用之间保持有效,但是其名字的可视性仍限制在其局部域内。静态局部对象在程序执行到该对象的声明处时被首次初始化。 由于static变量的以上特性,可实现一些特定功能。 1.统计次数功能 声明函数的一个局部变量,并设为static类型,作为一个计数器,这样函数每次被调用的时候就可以进行计数。这是统计函数被调用次数的最好的办法,因为这个变量是和函数息息相关的,而函数可能在多个不同的地方被调用,所以从调
数据成员可以分静态变量、非静态变量两种. 静态成员:静态类中的成员加入static修饰符,即是静态成员.可以直接使用类名+静态成员名访问此静态成员,因为静态成员存在于内存,非静态成员需要实例化才会分配内存,所以静态成员不能访问非静态的成员..因为静态成员存在于内存,所以非静态成员可以直接访问类中静态的成员. 非成静态员:所有没有加Static的成员都是非静态成员,当类被实例化之后,可以通过实例化的类名进行访问..非静态成员的生存期决定于该类的生存期..而静态成员则不存在生存期的概念,因为静态成员始终驻留在内容中.. 一个类中也可以包含静态成员和非静态成员,类中也包括静态构造函数和非静态构造函数.. 分两个方面来总结,第一方面主要是相对于面向过程而言,即在这方面不涉及到类,第二方面相对于面向对象而言,主要说明static在类中的作用。 一、在面向过程设计中的static关键字 1、静态全局变量 定义:在全局变量前,加上关键字static 该变量就被定义成为了一个静态全局变量。 特点: A、该变量在全局数据区分配内存。 B、初始化:如果不显式初始化,那么将被隐式初始化为0(自动变量是随机的,除非显式地初始化)。 C、访变量只在本源文件可见,严格的讲应该为定义之处开始到本文件结束。 例(摘于C++程序设计教程---钱能主编P103)://file1.cpp //Example 1 #include
n=20; cout < 一、判断题 1. C++程序中,不得使用没有定义或说明的变量。() 2.C和C++都是面向对象的。() 3.对象实际上是功能相对独立的一段程序。() 4.包含有纯虚函数的类称为抽象类。() 5.函数定义 void swap(A &x)中的形式参数是一个变量的地址。() 6.C++提供了string类型。() 7.析构函数不能设置默认参数。() 8.静态成员函数只能直接访问该类的静态数据成员。() 9.用成员函数重载运算符所需的参数个数总比它的操作数少一个。() 10.在C++的输入输出系统中,最核心的对象是流。() 11.在面向对象程序设计中,类通过消息与外界发生关系。() 12.引用是某个变量或对象的别名,建立引用时要对它初始化。() 13.当用类的一个对象去初始化该类的另一个对象时,该类的拷贝构造函数会被自动调用。() 14.拷贝构造函数可以被显示调用。() 15.一个类的析构函数只能有一个。() 16.私有派生时,基类的public成员仍被继承为派生类的public成员。() 17.设类Counter有私有变量x,它的构造函数Counter(int a){x=a;},则对象声明“Counter c1(3);”定义了三个Counter类的对象。() 18.静态成员函数不能访问静态数据成员。() 19.类中有函数声明:“friend void f(A &x);”,则函数f只能访问该类的公有成员。()20.重载“[]”运算符函数可以带多个参数。() 二、填空题 1.面向对象程序设计方法具有抽象性、封闭性、继承性和等特点。 2.声明内联函数的关键字是。 3.C++有值传递和传递两种参数传递机制。 4.运算符用于动态内存分配,运算符用于释放动态分配的内存。5.如果类A继承了类B,则类A称为类,类B称为类。 6.在C++中,要实现动态联编,必须使用基类指针调用。 7.具有纯虚函数的类称为。 8.定义模板的关键字是。 9.虚基类的作用是解决问题。 10.假定ABC是一个类,由“ABC a[5],b(3)”创建对象时该类的构造函数被调用了次。 11.面向对象程序设计方法,程序可表示为程序=_______________。 静态成员函数一般情况下只能访问静态成员变量,因为不接受隐含的this指针。另外作为类的静态成员函数,不用声明对象,便可直接调用,例如类A的静态成员函数fun(); A::fun(); 1、主要用于封装全局变量和全局函数。以避免在文件作用域内包含带外部连接的数据。 例如全局变量:int path;int para1; 解决办法:设计一个全局类,并将这些全局名称声明为静态变量,并编写静态函数来调用这些变量。 class Global{ static int s_path; static int s_para; private: Global();//不实现,避免无意中的实例化 public: //manipulators static void setPath(int path){s_path = path;} static void setPara(int para){s_para = para;} //accessors static int getPath(){return s_path;} static int getPara(){return s_para;} } 2、对自由函数的封装 在.h文件的文件作用域内避免使用自由函数(运算符函数除外);在.c文件中避免使用带有外部连接的自由函数,因此可以使用静态成员函数进行处理。 例如:int getPara();int getPath();我们可以通过声明一个结构的静态方法代替: struct SysUtil{ static int getPath(); static int getPara(); }这样,唯一有冲突危险的就是出现类名SysUtil了。 【例3.19】静态数据成员和静态成员函数使用举例。 #include return noOfStudents; } protected: static int noOfStudents; // 若写成noOfStudents=0;则非法 char name[40]; }; int Student::noOfStudents =0;// 静态数据成员在类外分配空间和初始化 void fn() { Student s1; Student s2; cout < 静态函数静态数据成员与静态成员函数为什么虚函数必须是非静态成员函数构造函数能为static吗? 2009-07-05 14:27 静态函数 用static声明的函数是静态函数。静态函数可以分为全局静态函数和类的静态成员函数。 Static关键字 在类中,用static声明的成员变量为静态成员变量,它为该类的公用变量,在第一次使用时被初始化,对于该类的所有对象来说,static成员变量只有一份。用static声明的方法是静态方法,在调用该方法时,不会将对象的引用传递给它,所以在static方法中不可访问非static的成员。 静态方法不再是针对于某个对象调用,所以不能访问非静态成员。 可以通过对象引用或类名(不需要实例化)访问静态成员 C++类静态数据成员与类静态成员函数 函数调用的结果不会访问或者修改任何对象(非static)数据成员,这样的成员声明为静态成员函数比较好。且如果static int func(....)不是出现在类中,则它不是一个静态成员函数,只是一个普通的全局函数,只不过由于static的限制,它只能在文件所在的编译单位内使用,不能在其它编译单位内使用。 静态成员函数的声明除了在类体的函数声明前加上关键字static,以及不能声明为const或者volatile之外,与非静态成员函数相同。出现在类体之外的函数定义不能制定关键字static。 静态成员函数没有this指针。 在没有讲述本章内容之前如果我们想要在一个范围内共享某一个数据,那么我们会设立全局对象,但面向对象的程序是由对象构成的,我们如何才能在类范围内共享数据呢? 这个问题便是本章的重点:声明为static的类成员或者成员函数便能在类的范围内共同享,我们把这样的成员称做静态成员和静态成员函数。 下面我们用几个实例来说明这个问题,类的成员需要保护,通常情况下为了不违背类的封装特性,我们是把类成员设置为protected(保护状态)的,但是我们为了简化代码,使要说明的问题更为直观,更容易理解,我们在此处都设置为public。 以下程序我们来做一个模拟访问的例子,在程序中,每建立一个对象我们设置的类静态成员变自动加一,代码如下: #include C++静态成员函数小结 一静态数据成员 (1) 1.静态数据成员的定义 (1) 2.静态数据成员被类的所有对象所共享(包括该类派生类的对象) (2) 3.静态数据成员可以成为成员函数的可选参数(普通数据成员则不可以) (2) 4.静态数据成员的类型可以是所属类的类型(普通数据成员则不可以) (3) 5.静态数据成员的值在const成员函数中可以被合法的改变 (3) 二静态成员函数 (3) 1.静态成员函数的地址可用普通函数指针储存(普通成员函数地址需要用类成员函数 指针来储存) (4) 2.静态成员函数不可以调用类的非静态成员 (4) 3.静态成员函数不可以同时声明为virtual、const、volatile函数 (4) 类中的静态成员真是个让人爱恨交加的特性。我决定好好总结一下静态类成员的知识点,以便自己在以后面试中,在此类问题上不在被动。 静态类成员包括静态数据成员和静态函数成员两部分。 一静态数据成员 类体中的数据成员的声明前加上static关键字,该数据成员就成为了该类的静态数据成员。和其他数据成员一样,静态数据成员也遵守public/protected/private访问规则。同时,静态数据成员还具有以下特点: 1.静态数据成员的定义 静态数据成员实际上是类域中的全局变量。所以,静态数据成员的定义(初始化)不应该被放在头文件中。其定义方式与全局变量相同。举例如下: xxx.h文件 class base{ private: static const int _i;//声明,标准c++支持有序类型在类体中初始化,但vc6不支持。 }; xxx.cpp文件 const int base::_i=10;//定义(初始化)时不受private和protected访问限制. 注:不要试图在头文件中定义(初始化)静态数据成员。在大多数的情况下,这样做会引起重复定义这样的 static变量和static函数的各自的特点 static变量大致分为三种用法 一、用于局部变量中,成为静态局部变量. 静态局部变量有两个用法,记忆功能和全局生存期. 二、用于全局变量,主要作用是限制此全局变量被其他的文件调用. 三、用于类中的成员.表示这个成员是属于这个类但是不属于类中任意特定对象 static 声明的变量. 在C语言中有两方面的特征: 1、变量会被放在程序的全局存储区中,这样可以在下一次调用的时候还可以保持原来的赋值。这一点是它与堆栈变量和堆变量的区别。 2、变量用static告知编译器,自己仅仅在变量的作用范围内可见。这一点是它与全局变量的区别。 Tips: A.若全局变量仅在单个C文件中访问,则可以将这个变量修改为静态全局变量,以降低模块间的耦合度; B.若全局变量仅由单个函数访问,则可以将这个变量改为该函数的静态局部变量,以降低模块间的耦合度; C.设计和使用访问动态全局变量、静态全局变量、静态局部变量的函数时,需要考虑重入问题; D.如果我们需要一个可重入的函数,那么,我们一定 要避免函数中使用static变量(这样的函数被称为:带“内部存储器”功能的的函数) E.函数中必须要使用static变量情况:比如当某函数的返回值为指针类型时,则必须是static的局部变量的地址作为返回值,若为auto类型,则返回为错指针。 函数前加static使得函数成为静态函数。但此处“static”的含义不是指存储方式,而是指对函数的作用域仅局限于本文件(所以又称内部函数)。使用内部函数的好处是:不同的人编写不同的函数时,不用担心自己定义的函数,是否会与其它文件中的函数同名。 扩展分析:术语static有着不寻常的历史.起初,在C中引入关键字static是为了表示退出一个块后仍然存在的局部变量。随后,static在C中有了第二种含义:用来表示不能被其它文件访问的全局变量和函数。为了避免引入新的关键字,所以仍使用static关键字来表示这第二种含义。最后,C++重用了这个关键字,并赋予它与前面不同的第三种含义:表示属于一个类而不是属于此类的任何特定对象的变量和函数(与Java中此关键字的含义相同)。 C++中为什么不能在类定义中初始化非静态成员变量,只能在构造函数中初始化? 答:首先,类究竟什么?类是一个对事物具体抽象的模型,注意,它仅是一个抽象的、慨念上的东东。从程序设计层面看,它仅是一个声明,并不代表一个具体的实例,即使它有成员函数的定义,但在内存实例这个层面,它什么也没有!什么都没有,你到那儿去初始化它的成员x呢? 而且,从类的语意来看,它表示有无限个具有相似性(不是相同性)的对象实例的抽象慨括,非静态成员变量对类来说,是一个变化的值(有无穷的解),它是类的可变部份,语言以及类的设计者不能以相同的值去初始化其可变部份(x)!但对类的不变部份,语言还是适当的允许你在类中去初始化,例如整型int及类整型(long、char等)等静态常量你还是可以在类中初始化它们的! 定义类的时候并没有分配内存,这时候赋值的话值放在哪里呢? 当用类构造对象的时候首先分配内存然后调用构造函数,这时候才可以初始化非静态成员变量. 静态成员变量定义的时候在静态存储区中就分配了内存所以可以初始化. 静态成员变量 在C++中(以及其他一些语言,如C#,Java 等面向对象的语言中)类的成员变量被声明为static(称为静态成员变量),意味着它为该类的所有实例所共享,也就是说当某个类的实例修改了该静态成员变量,其修改值为该类的其它所有实例所见。 比如在某个类A中声明一个static int number;初始化为0。这个number就能被所有A的实例共用。在A的构造函数里加上number++,在A的析构函数里加上number--。那么每生成一个A的实例,number就加一,每销毁一个A的实例,number就减一,这样,number就可以记录程序中共生成了多少个A的实例。这只是静态成员的一种用法而已。 静态成员函数调用非静态成员变量 程序最终都将在内存中执行,变量只有在内存中占有一席之地时才能被访问。 类的静态成员(变量和方法)属于类本身,在类加载的时候就会分配内存,可以通过类名直接去访问;非静态成员(变量和方法)属于类的对象,所以只有在类的对象产生(创建类的实例)时才会分配内存,然后通过类的对象(实例)去访问。 在一个类的静态成员中去访问其非静态成员之所以会出错是因为在类的非静态成员不存在的时候类的静态成员就已经存在了,访问一个内存中不存在的东西当然会出错。(访问的这个非静态成员必须是属于某个实例才行) C++会区分两种类型的成员函数:静态成员函数和非静态成员函数。这两者之间的一个重大区别是,静态成员函数不接受隐含的this自变量。所以,它就无法访问自己类的非静态成员。 静态数据成员 在类中,静态成员可以实现多个对象之间的数据共享,并且使用静态数据成员还不会破坏隐藏的原则,即保证了安全性。因此,静态成员是类的所有对象中共享的成员,而不是某个对象的成员。 使用静态数据成员可以节省内存,因为它是所有对象所公有的,因此,对多个对象来说,静态数据成员只存储一处,供所有对象共用。静态数据成员的值对每个对象都是一样,但它的值是可以更新的。只要对静态数据成员的值更新一次,保证所有对象存取更新后的相同的值,这样可以提高时间效率。 静态数据成员的使用方法和注意事项如下: 1、静态数据成员在定义或说明时前面加关键字static。 2、静态成员初始化与一般数据成员初始化不同。静态数据成员初始化的格式如下: <数据类型><类名>::<静态数据成员名>=<值> 这表明: (1) 初始化在类体外进行,而前面不加static,以免与一般静态变量或对象相混淆。 (2) 初始化时不加该成员的访问权限控制符private,public等。 (3) 初始化时使用作用域运算符来标明它所属类,因此,静态数据成员是类的成员,而不是对象的成员。 3、静态数据成员是静态存储的,它是静态生存期,必须对它进行初始化。 4、引用静态数据成员时,采用如下格式: <类名>::<静态成员名> (静态成员属于类的,不属于某个对象) 静态成员函数 静态成员函数和静态数据成员一样,它们都属于类的静态成员,它们都不是对象成员。因此,对静态成员的引用不需要用对象名。 实验四类与对象(静态成员) 一、实验目的与要求 1.掌握类的定义,成员函数的定义; 2.掌握对象的初始化及应用; 3.掌握构造函数的使用; 4.掌握析构函数的使用; 5.掌握静态的使用; 二、实验内容 1.设计一个学生类Student,它要求有以下面成员: 私有数据成员: 注册号Registration,姓名Name、数学成绩Math、英语成绩English、高级程序设计成绩Programming; 静态数据信息学生人数Count; 具有的公有的函数成员: (1)求三门课总成绩的函数Sum(); (2)求三门课平均成绩的函数Average(); (3)显示学生数据信息的函数Print(); (4)获取学生注册号的函数Get_reg_num(); (5)设置学生数据信息的函数Set_stu_inf(); (6)静态的获取学生人数函数Get_coutnt(); (7)构造函数; (8)析构函数. 为了统一格式,也方便同学们更有针对性的实现 Student类的声明为: //Student.h #include int Math; int English; int Programming; static int Count; public: Student(int ,char[],int,int,int); Student(Student &); ~Student(); int Sum(); double Average(); void Print(); int Get_reg_num(); void Set_stu_inf(int,char[],int ,int ,int); static int Get_count(); }; //shiyan4_1.cpp #include"Student.h" //对静态数据成员进行初始化,补充代码 //(1) int main() { //1.输出当前的学生数,补充代码 cout<<"当前的学生数:"< 一选择题 1、面向对象程序设计中的数据隐藏指的是( D )。 A. 输入数据必须输入保密口令 B. 数据经过加密处理 C. 对象内部数据结构上建有防火墙 D. 对象内部数据结构的不可访问性 2、下列关于类的访问权限的描述中,错误的是( D ).。 A. 说明为公有的成员可以被程序中的任何代码访问 B. 说明为私有的成员只能被类的成员和说明为友元类的成员函数访问 C.说明为保护的成员,除了能被本身的成员函数和说明为友元类的成员函数访问外,该类的派生类的成员也可以访问 D. 类的所有成员都可以被程序中的任何代码访问 3、 C 中对于类中定义的成员,其默认的访问权限为( C )。 A. Public B. Protected C. Privat D. Static 4、C++ 对C语言作了很多改进,即从面向过程变成为面向对象的主要改进是( D ) A. 增加了一些新的运算符 B. 允许函数重载,并允许设置缺省参数 C. 规定函数说明符必须用原型 D. 引进了类和对象的概念 5、已知类A中的一个成员函数的说明如下:void Set(A &a);则该函数的参数“A &a”的含义是( C )。 A. 指向A的指针为a B. 将变量a的地址赋给类A C. 类A对象引用a用作函数的形参 D. 变量A与a按位与后作函数参数 6、下列特性中,C与C++共有的是( D )。 A. 继承 B. 封装 C. 多态性 D. 函数定义不能嵌套 7、关于封装,下列说法中不正确的是( D )。 A. 通过封装,对象的全部属性和操作结合在一起,形成一个整体 B. 通过封装,一个对象的实现细节被尽可能地隐藏起来(不可见) C. 通过封装,每个对象都成为相对独立的实体 D. 通过封装,对象的属性都是不可见的 8、在一个类的定义中,包含有( C )成员的定义。 A. 数据 B. 函数 C.数据和函数 D. 数据或函数 9、在类作用域中能够通过直接使用该类的( D )成员名进行访问。 A. 私有 B. 公用 C. 保护 D. 任何 10、在关键字public后面定义的成员为类的( B )成员。 A. 私有 B. 公用 C. 保护 D. 任何 11、在关键字private后面定义的成员为类的( A )成员。 A. 私有 B. 公用 C. 保护 D. 任何 12、假定AA为一个类,a为该类公有的数据成员,x为该类的一个对象,则访问x对象中数据成员a的格式为( D )。 A. x(a) B. x[a] C. x->a D. X.a 13、假定AA为一个类,a()为该类公有的函数成员,x为该类的一个对象,则访问x对象中函数成员a()的格式为( B )。 A. x.a B. x.a() C. x->a D. x->a() 14、假定AA为一个类,a为该类公有的数据成员,px为指向该类对象的一个指针,则访问px所指对象中数据成员a的格式为( C )。 A. px(a) B. px[a] C. px->a D. Px.a 一 static的作用 1 什么是static??static 是C++中很常用的修饰符,它被用来控制变量的存储方式和可见性。 2、为什么要引入static??函数内部定义的变量,在程序执行到它的定义处时,编译器为它在栈上分配空间,大家知道,函数在栈上分配的空间在此函数执行结束时会释放掉,这样就产生了一个问题: 如果想将函数中此变量的值保存至下一次调用时,如何实现?最容易想到的方法是定义一个全局的变量,但定义为一个全局变量有许多缺点,最明显的缺点是破坏了此变量的访问范围(使得在此函数中定义的变量,不仅仅受此函数控制)。 3、什么时候用static??需要一个数据对象为整个类而非某个对象服务,同时又力求不破坏类的封装性,即要求此成员隐藏在类的内部,对外不可见。 二,类的静态成员简介 静态局部变量 ? 1.存放在静态存储区 ? 2. 在两次函数调用期间保留原值仅在函数内部可见 ? 3.可以被自动初始化为0 静态全局变量 ?作用域:仅在变量所在的文件内 类的静态成员(C++特有的语法) ?至今为止,我们使用的成员变量和成员函数都由某个特定的对象调用。但是,也许有些成员变量和成员函数,是属于整个类的。 ?静态成员变量,必须在类声明之后,用::初始化; ?静态成员的值,和某一个具体对象是无关的。 ?对于非静态成员来说,每个对象都有一个特定的值,每个对象都为自己非静态成员,在内存里保留一个位置。?而静态成员,整个类只有一个备份 ?静态成员函数的使用限制 ? 1. 静态成员函数,被类的所有对象共享。不是和某一个特定的对象。而是和整个类相关联。 ? 2.静态成员函数内部,不可以使用this指针。因为this指针是指向每一个具体对象的。所以说this指针本身就是非静态成员变量。 ? 3.静态成员函数只能访问静态成员(包括静态成员函数和静态成员变量),而不能访问非静态成员。但是非静态成员函数既可以访问非静态成员也可以访问静态成员。 ? 4.静态成员函数,也可以有public和private。 private的静态成员函数,不能在外部用类名或对象名访问。只能从类的内部被其他的静态成员函数访问。?静态成员访问方式 ?类名::静态成员名(推荐) 三,类的静态成员的深入研究 静态数据成员的使用方法和注意事项如下:?1、静态数据成员在定义或说明时前面加关键字static。?2、静态成员初始化与一般数据成员初始化不同。静态数据成员初始化的格式如下:?<数据类型><类名>::< 静态数据成员名>=<值>?这表明:?(1) 初始c++复习题
静态成员函数一般情况下只能访问静态成员变量
【例3.19】静态数据成员和静态成员函数使用举例。
静态函数 静态数据成员与静态成员函数 为什么虚函数必须是非静态成员函数 构造函数能为static吗
C++静态成员函数小结
static变量和static函数的各自的特点
(非)静态成员变量
实验四类与对象(静态数据成员)
C++自测题之三
类中有关静态成员的使用
C++习题2(构造函数和静态成员)