单例模式与静态类(一个类,所有方法为静态方法)是另一个非常有趣的问题,在《Java中有关单例模式的面试问题》博文中露掉了,由于单例模式和静态类都具有良好的访问性,它们之间有许多相似之处,例如,两者可以直接使用而无须创建对象,都可提交唯一实例,在一个非常高的高度上看起来它们都为是用于同样的任务。由于它们具有较多的相似性,面试官常常会问一些类似为神马使用单例模式替换静态方法?你能使用静态类替换单例模式吗?Java中单例模式与静态的区别有那些?等这样的问题,为回答这些问题,记住他们单例模式和静态方法之间基本的区别是非常重要的,前者给你一个Object,后者仅仅提供静态方法,由于对像比方法具有较强的能力,可以指导你神马时候使用单例模式与静态方法。
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000
boost::signals2::signal<std::string (const char* psz)> Translate;
boost::signals2::signal
translationInterface.Translate.connect(Translate);
uiInterface.NotifyHeaderTip.connect(boost::bind(BlockTipChanged, this, _1, _2, true));
0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
boost::thread ------------------join
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
boost::this_thread::interruption_point();
线程不是在任意时刻都可以被中断的。如果将线程中函数中的sleep()睡眠等待去掉,那么即使在主线程中调用interrupt()线程也不会被中断。
thread库预定义了若干个线程的中断点,只有当线程执行到中断点的时候才能被中断,一个线程可以拥有任意多个中断点。
thread库预定义了共9个中断点,它们都是函数,如下:
1. thread::join();2. thread::timed_join();
3. condition_variable::wait();
4. condition_variable::timed_wait();
5. condition_variable_any::wait();
6. condition_variable_any::timed_wait();
7. thread::sleep();
8. this_thread::sleep();
9. this_thread::interruption_point()
这些中断点中的前8个都是某种形式的等待函数,表明线程在阻塞等待的时候可以被中断。
而最后一个位于子名字空间this_thread的interruption_point()则是一个特殊的中断点函数,它并不等待,只是起到一个标签的作用,表示线程执行到这个函数所在的语句就可以被中断。看看下面的例子:
- namespace
- {
- boost::mutex io_mu;
- void to_interrupt(const std::string& str)
- {
- // 如果在线程外部调用了this_thread->interrupt()
- // 线程内部的以下这些检查点可以抛出boost::thread_interrupted异常
- try
- {
- boost::this_thread::disable_interruption();
- for (int i = 0; i < 5; ++i)
- {
- boost::mutex::scoped_lock lock(io_mu);
- PRINT_DEBUG(i);
- PRINT_DEBUG(std::boolalpha << boost::this_thread::interruption_enabled());
- // PRINT_DEBUG(std::boolalpha << boost::this_thread::interruption_requested());
- if (i == 2)
- {
- PRINT_DEBUG(std::boolalpha << boost::this_thread::interruption_enabled());
- boost::this_thread::interruption_point();
- PRINT_DEBUG(std::boolalpha << boost::this_thread::interruption_enabled());
- }
- }
- }
- catch (boost::thread_interrupted &)
- {
- }
- }
- }
- void test_thread_interrupt()
- {
- boost::thread t(to_interrupt, "hello");
- // 中断函数
- t.interrupt();
- t.join();
- }
- 2013-01-02 11:00:44 263 [8272] DEBUG - 0
- 2013-01-02 11:00:44 266 [8272] DEBUG - 1
- 2013-01-02 11:00:44 269 [8272] DEBUG - 2
- 2013-01-02 11:02:06 555 [5168] DEBUG - 0
- 2013-01-02 11:02:06 559 [5168] DEBUG - 1
- 2013-01-02 11:02:06 561 [5168] DEBUG - 2
- 2013-01-02 11:02:06 564 [5168] DEBUG - 3
- 2013-01-02 11:02:06 567 [5168] DEBUG - 4
缺省情况下钱程都是允许中断的,但thread库允许控制线程的中断行为。
thread 库在子名字空间this_thread提供了一组函数和类来共同完成线程的中断启用和禁用:
1. interruption_enabled(): 函数检测当前线程是否允许中断
2. interruption_requested(): 函数检测当前线程是否被要求中断
3. 类disable_interruption是一个RAII类型的对象,它在构造时关闭线程的中断,析构时自动恢复线程的中断状态。在disable_interruption 的生命期内线程始终是不可中断的,除非使用了restore_interruption 对象。
4. restore_interruption只能在disable_interruption 的作用域内使用,它在构造时临时打开线程的中断状态,在析构时又关闭中断状态。
这些中断点中的前8个都是某种形式的等待函数,表明线程在阻塞等待的时候可以被中断。
- namespace
- {
- boost::mutex io_mu;
- void to_interrupt_disable(const std::string& str)
- {
- // 默认可以中断
- assert(boost::this_thread::interruption_enabled());
- for (int i = 0; i < 10; i++)
- {
- // 关闭中断
- boost::this_thread::disable_interruption di;
- // 此时中断不可用
- PRINT_DEBUG(std::boolalpha << "interruption_enabled = " << boost::this_thread::interruption_enabled());
- // 是否有中断请求
- PRINT_DEBUG(std::boolalpha << "interruption_requested = " << boost::this_thread::interruption_requested());
- boost::mutex::scoped_lock lock(io_mu);
- PRINT_DEBUG(i);
- // 使用中断点函数,因为关闭中断,此时无效果。 中断恢复后,它才生效。
- boost::this_thread::interruption_point();
- if (i == 8)
- {
- // 临时恢复中断
- boost::this_thread::restore_interruption ri(di);
- PRINT_DEBUG(std::boolalpha << "interruption_enabled = " << boost::this_thread::interruption_enabled());
- PRINT_DEBUG(std::boolalpha << "interruption_enabled after restore = " << boost::this_thread::interruption_enabled());
- boost::this_thread::interruption_point();
- }
- }
- }
- }
- void test_thread_interrupt_disable()
- {
- boost::thread t(to_interrupt_disable, "hello");
- t.interrupt();
- t.join();
- }
- 2013-01-02 14:09:35 538 [7628] DEBUG - interruption_enabled = false
- 2013-01-02 14:09:35 544 [7628] DEBUG - interruption_requested = true
- 2013-01-02 14:09:35 551 [7628] DEBUG - 0
- 2013-01-02 14:09:35 555 [7628] DEBUG - interruption_enabled = false
- 2013-01-02 14:09:35 563 [7628] DEBUG - interruption_requested = true
- 2013-01-02 14:09:35 570 [7628] DEBUG - 1
- 2013-01-02 14:09:35 574 [7628] DEBUG - interruption_enabled = false
- 2013-01-02 14:09:35 581 [7628] DEBUG - interruption_requested = true
- 2013-01-02 14:09:35 586 [7628] DEBUG - 2
- 2013-01-02 14:09:35 589 [7628] DEBUG - interruption_enabled = false
- 2013-01-02 14:09:35 601 [7628] DEBUG - interruption_requested = true
- 2013-01-02 14:09:35 608 [7628] DEBUG - 3
- 2013-01-02 14:09:35 614 [7628] DEBUG - interruption_enabled = false
- 2013-01-02 14:09:35 621 [7628] DEBUG - interruption_requested = true
- 2013-01-02 14:09:35 627 [7628] DEBUG - 4
- 2013-01-02 14:09:35 630 [7628] DEBUG - interruption_enabled = false
- 2013-01-02 14:09:35 637 [7628] DEBUG - interruption_requested = true
- 2013-01-02 14:09:35 643 [7628] DEBUG - 5
- 2013-01-02 14:09:35 646 [7628] DEBUG - interruption_enabled = false
- 2013-01-02 14:09:35 650 [7628] DEBUG - interruption_requested = true
- 2013-01-02 14:09:35 655 [7628] DEBUG - 6
- 2013-01-02 14:09:35 659 [7628] DEBUG - interruption_enabled = false
- 2013-01-02 14:09:35 663 [7628] DEBUG - interruption_requested = true
- 2013-01-02 14:09:35 667 [7628] DEBUG - 7
- 2013-01-02 14:09:35 670 [7628] DEBUG - interruption_enabled = false
- 2013-01-02 14:09:35 679 [7628] DEBUG - interruption_requested = true
- 2013-01-02 14:09:35 685 [7628] DEBUG - 8
- 2013-01-02 14:09:35 689 [7628] DEBUG - interruption_enabled = true
- 2013-01-02 14:09:35 695 [7628] DEBUG - interruption_enabled after restore = true
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
C++并发实战2:thread::join和thread::detach
thread::join()是个简单暴力的方法,主线程等待子进程期间什么都不能做,一般情形是主线程创建thread object后做自己的工作而不是简单停留在join上。thread::join()还会清理子线程相关的内存空间,此后thread object将不再和这个子线程相关了,即thread object不再joinable了,所以join对于一个子线程来说只可以被调用一次,为了实现更精细的线程等待机制,可以使用条件变量等机制。
异常环境下join,假设主线程在一个函数f()里面创建thread object,接着f()又调用其它函数g(),那么确保在g()以任何方式下退出主线程都能join子线程。如:若g()通过异常退出,那么f()需要捕捉异常后join
- #include<iostream>
- #include<boost/thread.hpp>
- void do_something(int& i){
- i++;
- }
- class func{
- public:
- func(int& i):i_(i){}
- void operator() (){
- for(int j=0;j<100;j++)
- do_something(i_);
- }
- public:
- int& i_;
- };
- void do_something_in_current_thread(){}
- void f(){
- int local=0;
- func my_func(local);
- boost::thread t(my_func);
- try{
- do_something_in_current_thread();
- }
- catch(...){
- t.join();//确保在异常条件下join子线程
- throw;
- }
- t.join();
- }
- int main(){
- f();
- return 0;
- }
上面的方法看起来笨重,有个解决办法是采用RAII(资源获取即初始化),将一个thread object通过栈对象A管理,在栈对象A析构时调用thread::join.按照局部对象析构是构造的逆序,栈对象A析构完成后再析构thread object。如下:
- #include<iostream>
- #include<boost/noncopyable.hpp>
- #include<boost/thread.hpp>
- using namespace std;
- class thread_guard:boost::noncopyable{
- public:
- explicit thread_guard(boost::thread& t):t_(t){}
- ~thread_guard(){
- if(t_.joinable()){//检测是很有必要的,因为thread::join只能调用一次,要防止其它地方意外join了
- t_.join();
- }
- }
- //thread_guard(const thread_guard&)=delete;//c++11中这样声明表示禁用copy constructor需要-std=c++0x支持,这里采用boost::noncopyable已经禁止了拷贝和复制
- //thread_guard& operator=(const thread_guard&)=delete;
- private:
- boost::thread& t_;
- };
- void do_something(int& i){
- i++;
- }
- class func{
- public:
- func(int& i):i_(i){}
- void operator()(){
- for(int j=0;j<100;j++)
- do_something(i_);
- }
- public:
- int& i_;
- };
- void do_something_in_current_thread(){}
- void fun(){
- int local=0;
- func my_func(local);
- boost::thread t(my_func);
- thread_guard g(t);
- do_something_in_current_thread();
- }
- int main(){
- fun();
- return 0;
- }
说明:禁止拷贝和复制的原因是防止栈对象thread_guard在超出了thread object对象生命期的地方使用。如果detach一个线程则没有上述这么麻烦,必经detach后就不管子线程了。
thread::detach()后:没有直接方法与线程通信,不可能wait了,不可能有任何thread object指向这个线程。线程变成了后台进程(孤儿进程),在Linux将由init接管,在c++中由库接管。若不确定一个线程是否有thread object指向它,那么请先用thread::joinable()检测后再thread::detach()就想前面代码中的joinable检测一样。
考虑一个情形:通常我们在word中编辑文件A时,点击"新建"按钮后会出现新的窗口继续编辑文件B。这里A和B是相对独立的,A"新建"相当于开启一个线程去供B使用,其后A马上detach这个线程。代码如下:
- #include<iostream>
- #include<boost/thread.hpp>
- void open_document_and_display_gui(const std::string& filename){}//“新建”这一操作
- bool done_eaditing(){//是否完成编辑
- return true;
- }
- enum command_type{open_new_document};//用户命令,这里只有一个就是“新建”
- class user_command{//用户命令封装
- public:
- user_command():type(open_new_document){}
- public:
- command_type type;
- };
- user_command get_user_input(){//获取用户命令,这里就是获取"新建“
- return user_command();
- }
- std::string get_filename_from_user(){
- return "foo.doc";
- }
- void process_user_input(const user_command& cmd){}//处理用户的普通输入,即文档编写
- void edit_document(const std::string& filename){//假设初始时文档A执行edit_document,发现用户有”新建“命令到达,则开启一个线程去供文档B使用,且立即detach这个线程
- open_document_and_display_gui(filename);
- while(!done_eaditing()){
- user_command cmd=get_user_input();
- if(cmd.type==open_new_document){
- const std::string new_name=get_filename_from_user();
- boost::thread t(edit_document,new_name);//文档B也是采用同样的线程函数,这里可以看出thread可以接收参数,还有一个方法参考pthread_create将参数封装在一个函数对象中,然后传给thread
- t.detach();//立即执行detach,这里可以肯定thread t和线程相关,故不需要检测joinable
- }
- else
- process_user_input(cmd);//普通文档编辑
- }
- }
- int main(){
- edit_document("bar.doc");
- return 0;
- }
0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
线程状态:
在一个线程的生存期内,可以在多种状态之间转换,不同的操作系统可以实现不同的线程模型,定义许多不同的线程状态,每个状态还可以包含多个子状态,但大体来说,如下几种状态是通用的:
1)就绪:参与调度,等待被执行,一旦被调度选中,立即开始执行
2)运行:占用CPU,正在运行中
3)休眠:暂不参与调度,等待特定事件发生
4)中止:已经运行完毕,等待回收线程资源
线程环境:
线程存在于进程之中,进程内所有全局资源对于内部每个线程都是可见的。
进程内典型全局资源如下:
1)代码区:这意味着当前进程空间内所有的可见的函数代码,对于每个线程来说,也是可见的
2)静态存储区:全局变量,静态空间
3)动态存储区:堆空间
线程内典型的局部资源:
1)本地栈空间:存放本线程的函数调用栈,函数内部的局部变量等
2)部分寄存器变量:线程下一步要执行代码的指针偏移量
一个进程发起后,会首先生成一个缺省的线程,通常称这个线程为主线程,C/C++程序中,主线程就是通过main函数进入的线程,由主线程衍生的线程成为从线程,从线程也可以有自己的入口函数,相当于主线程的main函数,这个函数由用户指定。通过thread构造函数中传入函数指针实现,在指定线程入口函数时,也可以指定入口函数的参数。就像main函数有固定的格式要求一样,线程的入口函数也可以有固定的格式要求,参数通常都是void*类型,返回类型根据协议的不同也不同,pthread中是void*,winapi中是unsigned int,而且都是全局函数。
最常见的线程模型中,除主线程较为特殊之外,其他线程一旦被创建,相互之间就是对等关系,不存在隐含的层次关系。每个进程可创建的最大线程数由具体实现决定。
无论在windows中还是Posix中,主线程和子线程的默认关系是:无论子线程执行完毕与否,一旦主线程执行完毕退出,所有子线程执行都会终止。这时整个进程结束或僵死,部分线程保持一种终止执行但还未销毁的状态,而进程必须在其所有线程销毁后销毁,这时进程处于僵死状态。线程函数执行完毕退出,或以其他非常方式终止,线程进入终止态,但是为线程分配的系统资源不一定释放,可能在系统重启之前,一直都不能释放,终止态的线程,仍旧作为一个线程实体存在于操作系统中,什么时候销毁,取决于线程属性。在这种情况下,主线程和子线程通常定义以下两种关系:
1、可会合(joinable):这种关系下,主线程需要明确执行等待操作,在子线程结束后,主线程的等待操作执行完毕,子线程和主线程会合,这时主线程继续执行等待操作之后的下一步操作。主线程必须会合可会合的子线程。在主线程的线程函数内部调用子线程对象的wait函数实现,即使子线程能够在主线程之前执行完毕,进入终止态,也必须执行会合操作,否则,系统永远不会主动销毁线程,分配给该线程的系统资源也永远不会释放。
2、相分离(detached):表示子线程无需和主线程会合,也就是相分离的,这种情况下,子线程一旦进入终止状态,这种方式常用在线程数较多的情况下,有时让主线程逐个等待子线程结束,或者让主线程安排每个子线程结束的等待顺序,是很困难或不可能的,所以在并发子线程较多的情况下,这种方式也会经常使用。
在任何一个时间点上,线程是可结合(joinable)或者是可分离的(detached),一个可结合的线程能够被其他线程回收资源和杀死,在被其他线程回收之前,它的存储器资源如栈,是不释放的,相反,一个分离的线程是不能被其他线程回收或杀死的,它的存储器资源在它终止时由系统自动释放。
线程的分离状态决定一个线程以什么样的方式来终止自己,在默认的情况下,线程是非分离状态的,这种情况下,原有的线程等待创建的线程结束,只有当pthread_join函数返回时,创建的线程才算终止,释放自己占用的系统紫云啊,而分离线程没有被其他的线程所等待,自己运行结束了,线程也就终止了,马上释放系统资源。
0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
signals2基于Boost的另一个库signals,实现了线程安全的观察者模式。在signals2库中,观察者模式被称为信号/插槽(signals and slots),他是一种函数回调机制,一个信号关联了多个插槽,当信号发出时,所有关联它的插槽都会被调用。
许多成熟的软件系统都用到了这种信号/插槽机制(另一个常用的名称是事件处理机制:event/event handler),它可以很好地解耦一组互相协作的类,有的语言设置直接内建了对它的支持(如c#),signals2以库的形式为c++增加了这个重要的功能。
1、操作函数
signal最重要的操作函数是插槽管理connect()函数,它吧插槽连接到信号上,相当于为信号(事件)增加了一个处理的handler。
插槽可以是任意的可调用对象,包括函数指针、函数对象、以及它们的bind表达式和function对象,signal内部使用function作为容器来保存这些可调用对象。连接时可以指定组号也可以不指定组号,当信号发生时将依据组号的排序准则依次调用插槽函数。
如果连接成功connect()将返回一个connection,表示了信号与插槽之间的连接关系,它是一个轻量级的对象,可以处理两者间的连接,如断开、重连接、或者测试连接状态。
成员函数disconnect()可以断开插槽与信号的连接,它有两种形式:传递组号将断开该组的所有插槽,传递一个插槽对象将仅断开该插槽。函数disconnect_all_slots()可以一次性断开信号的所有插槽连接。
2、插槽的连接于使用
signal就像一个增强的function对象,它可以容纳(使用connect())多个符合模板参数中函数签名类型的函数(插槽),形成一个插槽链表,然后在信号发生时一起调用:
- #include "stdafx.h"
- #include "boost/utility/result_of.hpp"
- #include "boost/typeof/typeof.hpp"
- #include "boost/assign.hpp"
- #include "boost/ref.hpp"
- #include "boost/bind.hpp"
- #include "boost/function.hpp"
- #include "boost/signals2.hpp"
- #include "iostream"
- using namespace std;
- void slots1()
- {
- cout << "slots1 call" << endl;
- }
- void slots2()
- {
- cout << "slots2 call" << endl;
- }
- struct Hello
- {
- void operator()() const
- {
- std::cout << "Hello";
- }
- };
- int _tmain(int argc, _TCHAR* argv[])
- {
- boost::signals2::signal<void()> sig;
- sig.connect(&slots1);
- sig.connect(&slots2);
- sig();
- boost::signals2::signal<void ()> sig1;
- sig1.connect(Hello());
- sig1();
- return 0;
- }
注:编译这个程序的时候,确保已经在stdafx.h中加入#define _SCL_SECURE_NO_WARNINGS或者,在C/C++----预处理器----预处理器定义中加上了_SCL_SECURE_NO_WARNINGS,否则会引发错误(或不能正确输出),下同。
在连接插槽时省了了connect()的第二个参数connect_position,它的缺省值是at_back,表示插槽将插入到信号插槽链表的尾部,因此slot2将在slot1之后被调用。
下面是使用和组号的情况:
- #include "stdafx.h"
- #include "boost/utility/result_of.hpp"
- #include "boost/typeof/typeof.hpp"
- #include "boost/assign.hpp"
- #include "boost/ref.hpp"
- #include "boost/bind.hpp"
- #include "boost/function.hpp"
- #include "boost/signals2.hpp"
- #include "iostream"
- using namespace std;
- template<int N>
- struct Slot
- {
- void operator()()
- {
- cout << "Slot current N value is : " << N << endl;
- }
- };
- int _tmain(int argc, _TCHAR* argv[])
- {
- boost::signals2::signal<void()> sig;
- sig.connect(Slot<1>(), boost::signals2::at_back); // 最后一个被调用
- sig.connect(Slot<99>(), boost::signals2::at_front); // 第一个被调用
- sig.connect(5, Slot<55>(), boost::signals2::at_back); // 组号5的最后一个
- sig.connect(5, Slot<57>(), boost::signals2::at_front);// 组号5的第一个
- sig.connect(10, Slot<100>());// 组号10该组只有一个
- sig();
- return 0;
- }
3、信号的返回值
signal如function一样,不仅可以把输入参数转发给所以插槽,也可以传回插槽的返回值。默认情况下signal使用合并器optional_last_value<R>,它将使用optional对象返回最后被调用的插槽的返回值。
- #include "stdafx.h"
- #include "boost/utility/result_of.hpp"
- #include "boost/typeof/typeof.hpp"
- #include "boost/assign.hpp"
- #include "boost/ref.hpp"
- #include "boost/bind.hpp"
- #include "boost/function.hpp"
- #include "boost/signals2.hpp"
- #include "iostream"
- using namespace std;
- template<int N>
- struct Slot
- {
- int operator()(int x)
- {
- cout << "Slot current N * x value is : " << endl;
- return (N * x);
- }
- };
- int _tmain(int argc, _TCHAR* argv[])
- {
- boost::signals2::signal<int(int)> sig;
- sig.connect(Slot<10>());
- sig.connect(Slot<100>());
- cout << *sig(2) << endl;;
- return 0;
- }
signal的operator()调用这时需要传入一个整数参数,这个参数会被signal存储一个拷贝,然后转发给各个插槽。最后signal将返回插槽链表末尾slots<100>()的计算结果,它是一个optional对象,必须用接引用操作符*来获得值(但你可以发现Slotcurrent N * x value is是输出了两次的)。
4、合并器
大多数时候,插槽的返回值都是有意义的,需要以某种方式处理多个插槽的返回值。
signal允许用户自定义合并器来处理插槽的返回值,把多个插槽的返回值合并为一个结果返回给用户。
- #include "stdafx.h"
- #include "boost/utility/result_of.hpp"
- #include "boost/typeof/typeof.hpp"
- #include "boost/assign.hpp"
- #include "boost/ref.hpp"
- #include "boost/bind.hpp"
- #include "boost/function.hpp"
- #include "boost/signals2.hpp"
- #include "numeric"
- #include "iostream"
- using namespace std;
- template<int N>
- struct Slot
- {
- int operator()(int x)
- {
- cout << "Slot current N * x value is : " << endl;
- return (N * x);
- }
- };
- template<typename T>
- class combiner
- {
- public:
- typedef pair<T, T> result_type;
- combiner(T t = T()) : v(t)
- {
- }
- template<typename InputIterator>
- result_type operator()(InputIterator begin, InputIterator end) const
- {
- if (begin == end)
- {
- return result_type();
- }
- vector<T> vec(begin, end);
- T sum = accumulate(vec.begin(), vec.end(), v);
- T max = *max_element(vec.begin(), vec.end());
- return result_type(sum, max);
- }
- private:
- T v;
- };
- int _tmain(int argc, _TCHAR* argv[])
- {
- boost::signals2::signal<int(int), combiner<int> > sig;
- sig.connect(Slot<10>());
- sig.connect(Slot<20>());
- sig.connect(Slot<3>());
- BOOST_AUTO(x, sig(2));
- cout << x.first << ", " << x.second << endl;
- return 0;
- }
combiner类的调用操作符operator()的返回值类型可以是任意类型,完全由用户指定,不一定必须是optional或者是插槽的返回值类型。operator()的模板参数InputIterator是插槽链表的返回值迭代器,可以使用它来遍历所有插槽的返回值,进行所需的处理。
当信号被调用时,signal会自动把引用操作转换为插槽调用,将调用给定的合并器的operator()逐个处理插槽的返回值,并最终返回合并器operator()的结果。
如果我们不适用signal的缺省构造函数,而是在构造signal时传入一个合并器的实例,那么signal将使用逐个合并器(的拷贝)处理返回值。例如,下面的代码使用了一个有初值的合并器对象,累加值从100开始:
signal<int(int),combiner<int> > sig(combiner<int>(100));
5、管理信号的连接
信号与插槽的链接并不要求是永久的,当信号调用玩插槽后,有可能需要把插槽从信号中断开,再连接到其他的信号上去。
signal可以用成员函数disconnect()断开一个或一组插槽,或者使用disconnect_all_slots()断开所有插槽连接,函数empty()和num_slots()用来检测信号当前插槽的连接状态。
要断开一个插槽,插槽必须能够进行登记等价比较,对于函数对象来说就是重载一个等价语义的operator==。下面对slots<N>增加一个等价比较:
- #include "stdafx.h"
- #include "boost/utility/result_of.hpp"
- #include "boost/typeof/typeof.hpp"
- #include "boost/assign.hpp"
- #include "boost/ref.hpp"
- #include "boost/bind.hpp"
- #include "boost/function.hpp"
- #include "boost/signals2.hpp"
- #include "numeric"
- #include "iostream"
- using namespace std;
- template<int N>
- struct Slot
- {
- int operator()(int x)
- {
- cout << "Slot current N * x value is : " << endl;
- return (N * x);
- }
- };
- template<int N>
- bool operator== (const Slot<N>& a, const Slot<N>& b)
- {
- return true;
- }
- template<typename T>
- class combiner
- {
- public:
- typedef pair<T, T> result_type;
- combiner(T t = T()) : v(t)
- {
- }
- template<typename InputIterator>
- result_type operator()(InputIterator begin, InputIterator end) const
- {
- if (begin == end)
- {
- return result_type();
- }
- vector<T> vec(begin, end);
- T sum = accumulate(vec.begin(), vec.end(), v);
- T max = *max_element(vec.begin(), vec.end());
- return result_type(sum, max);
- }
- private:
- T v;
- };
- int _tmain(int argc, _TCHAR* argv[])
- {
- boost::signals2::signal<int(int)> sig;
- // assert(sig.empty());
- sig.connect(0, Slot<10>());
- sig.connect(Slot<20>());
- sig.connect(1, Slot<30>());
- assert(sig.num_slots() == 3);
- sig.disconnect(0);
- // assert(sig.num_slots() == 1);
- sig.disconnect(Slot<30>());
- // assert(sig.empty());
- sig(2);
- return 0;
- }
6、更灵活的管理信号连接
signals2库提供另外一种较为灵活的连接管理方式:使用connection对象。
每当signal使用connect()连接插槽时,他就会返回一个connection对象。connection对象像是信号与插槽连接关系的一个句柄(handle),可以管理链接:
- #include "stdafx.h"
- #include "boost/utility/result_of.hpp"
- #include "boost/typeof/typeof.hpp"
- #include "boost/assign.hpp"
- #include "boost/ref.hpp"
- #include "boost/bind.hpp"
- #include "boost/function.hpp"
- #include "boost/signals2.hpp"
- #include "numeric"
- #include "iostream"
- using namespace std;
- template<int N>
- struct Slot
- {
- int operator()(int x)
- {
- cout << "Slot current N * x value is : " << endl;
- return (N * x);
- }
- };
- template<int N>
- bool operator== (const Slot<N>& a, const Slot<N>& b)
- {
- return true;
- }
- template<typename T>
- class combiner
- {
- public:
- typedef pair<T, T> result_type;
- combiner(T t = T()) : v(t)
- {
- }
- template<typename InputIterator>
- result_type operator()(InputIterator begin, InputIterator end) const
- {
- if (begin == end)
- {
- return result_type();
- }
- vector<T> vec(begin, end);
- T sum = accumulate(vec.begin(), vec.end(), v);
- T max = *max_element(vec.begin(), vec.end());
- return result_type(sum, max);
- }
- private:
- T v;
- };
- int _tmain(int argc, _TCHAR* argv[])
- {
- boost::signals2::signal<int(int)> sig;
- boost::signals2::connection c1 = sig.connect(0, Slot<10>());
- boost::signals2::connection c2 = sig.connect(0, Slot<10>());
- boost::signals2::connection c3 = sig.connect(0, Slot<10>());
- c1.disconnect();
- assert(sig.num_slots() == 2);
- assert(!c1.connected());
- assert(c2.connected());
- return 0;
- }
另外一种连接管理对象是scoped_connection,它是connection的种类,提供类似scoped_ptr的RAII功能:插槽与信号的连接仅在作用域内生效,当离开作用域时连接就会自动断开。当需要临时连接信号时scoped_connection会非常有用:
- #include "stdafx.h"
- #include "boost/utility/result_of.hpp"
- #include "boost/typeof/typeof.hpp"
- #include "boost/assign.hpp"
- #include "boost/ref.hpp"
- #include "boost/bind.hpp"
- #include "boost/function.hpp"
- #include "boost/signals2.hpp"
- #include "numeric"
- #include "iostream"
- using namespace std;
- template<int N>
- struct Slot
- {
- int operator()(int x)
- {
- cout << "Slot current N * x value is : " << endl;
- return (N * x);
- }
- };
- template<int N>
- bool operator== (const Slot<N>& a, const Slot<N>& b)
- {
- return true;
- }
- template<typename T>
- class combiner
- {
- public:
- typedef pair<T, T> result_type;
- combiner(T t = T()) : v(t)
- {
- }
- template<typename InputIterator>
- result_type operator()(InputIterator begin, InputIterator end) const
- {
- if (begin == end)
- {
- return result_type();
- }
- vector<T> vec(begin, end);
- T sum = accumulate(vec.begin(), vec.end(), v);
- T max = *max_element(vec.begin(), vec.end());
- return result_type(sum, max);
- }
- private:
- T v;
- };
- int _tmain(int argc, _TCHAR* argv[])
- {
- boost::signals2::signal<int(int)> sig;
- sig.connect(0, Slot<10>());
- assert(sig.num_slots() == 1);
- {
- boost::signals2::scoped_connection sc = sig.connect(0, Slot<20>());
- assert(sig.num_slots() == 2);
- }
- assert(sig.num_slots() == 1);
- return 0;
- }
插槽与信号的连接一旦断开就不能再连接起来,connection不提供reconnet()这样的函数。但可以暂时地阻塞插槽与信号的连接,当信号发生时被阻塞的插槽将不会被调用,connection对象的blocked()函数可以检查插槽是否被阻塞。但被阻塞的插槽并没有断开与信号的链接,在需要的时候可以随时解除阻塞。
connection对象自身没有阻塞的功能,他需要一个辅助类shared_connection_block,它将阻塞connection对象,知道它被析构或者显式调用unblock()函数:
- #include "stdafx.h"
- #include "boost/utility/result_of.hpp"
- #include "boost/typeof/typeof.hpp"
- #include "boost/assign.hpp"
- #include "boost/ref.hpp"
- #include "boost/bind.hpp"
- #include "boost/function.hpp"
- #include "boost/signals2.hpp"
- #include "numeric"
- #include "iostream"
- using namespace std;
- template<int N>
- struct Slot
- {
- void operator()(int x)
- {
- cout << "Slot current N is : " << N << endl;
- }
- };
- template<int N>
- bool operator== (const Slot<N>& a, const Slot<N>& b)
- {
- return true;
- }
- template<typename T>
- class combiner
- {
- public:
- typedef pair<T, T> result_type;
- combiner(T t = T()) : v(t)
- {
- }
- template<typename InputIterator>
- result_type operator()(InputIterator begin, InputIterator end) const
- {
- if (begin == end)
- {
- return result_type();
- }
- vector<T> vec(begin, end);
- T sum = accumulate(vec.begin(), vec.end(), v);
- T max = *max_element(vec.begin(), vec.end());
- return result_type(sum, max);
- }
- private:
- T v;
- };
- int _tmain(int argc, _TCHAR* argv[])
- {
- boost::signals2::signal<void(int)> sig;
- boost::signals2::connection c1 = sig.connect(0, Slot<10>());
- boost::signals2::connection c2 = sig.connect(0, Slot<20>());
- assert(sig.num_slots() == 2);
- sig(2);
- cout << "begin blocking..." << endl;
- {
- boost::signals2::shared_connection_block block(c1);
- assert(sig.num_slots() == 2);
- assert(c1.blocked());
- sig(2);
- }
- cout << "end blocking.." << endl;
- assert(!c1.blocked());
- sig(2);
- return 0;
- }
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000
AAAAAAAAAAAAAAAAAAA静态类
一个类是对一个概念的描述。
一个静态类没有状态,因此就没有必要成为一个类
C++具有传值,传址,哑元功能
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
- //默认参数 与 哑元
- #include <iostream>
- using namespace std;
- /* 1
- void print(int *a,int n){
- for (int i = 0; i<n; i++) {
- cout << a[i];
- }
- cout << endl;
- }
- */
- // 2.用空格分开输出的每一个数
- /*
- void print(int *a,int n){
- for (int i = 0; i<n; i++) {
- cout << a[i] << ' ';
- }
- cout << endl;
- }
- */
- // 3.想用星号分隔开呢?
- /*
- void print(int *a,int n){
- for (int i = 0; i<n; i++) {
- cout << a[i] << '*';
- }
- cout << endl;
- }
- */
- // 4.随意用各种分隔符分开每个数 封装函数就是找出不同之处作为参数
- /*
- *
- void print(int *a,int n,char ch){
- for (int i = 0; i<n; i++) {
- cout << a[i] << ch;
- }
- cout << endl;
- }
- *
- */
- // 5.用中括号括起来输出的内容
- /*
- void print(int *a,int n,char ch){
- cout << '[';
- for (int i = 0; i<n; i++) {
- cout << a[i] << ch;
- }
- cout << ']';
- cout << endl;
- }
- */
- // 6.有时候想要中括号 有时候不想要呢?
- /*
- *
- void print(int *a,int n,char ch,bool flag){
- if (flag) {
- cout << '[';
- }
- for (int i = 0; i<n; i++) {
- cout << a[i] << ch;
- }
- if (flag) {
- cout << ']';
- }
- cout << endl;
- }
- *
- */
- #pragma 如果想要打印10000次,但是只有9999次的分隔符是空格 和需要中括号,只有一次的分隔符是星号*和不带中括号 ,如果只为了这一次特殊的,就要写9999次不特殊的 怎么办? 只需要给定默认参数即可
- // 7. C++中参数可以给默认值
- void print(int *a,int n,char ch,bool flag = true){
- if (flag) {
- cout << '[';
- }
- for (int i = 0; i<n; i++) {
- cout << a[i] << ch;
- }
- if (flag) {
- cout << ']';
- }
- cout << endl;
- }
- //8. 默认情况下 我想用空格作为分隔符
- void print2(int *a,int n,char ch = ' ',bool flag = true){
- if (flag) {
- cout << '[';
- }
- for (int i = 0; i<n; i++) {
- cout << a[i] << ch;
- }
- if (flag) {
- cout << ']';
- }
- cout << endl;
- }
- #pragma 注意哦 C++的默认值只能从右向左给
- //这个函数我没有给定最后一个参数的默认值 你会发现报错 这就说明了默认值只能从右向左给定
- //如果第四个没有给定默认值而给第三个参数默认值,那么当我传进来3个参数的时候,编译器不知道把我传的第3个给谁。。。。也就是右边的没有默认值的话 不能给左边的默认值
- /*
- void print3(int *a, int n, char ch = ' ', bool flag){
- //省略一下
- }
- */
- // 9 . 我能不能什么分隔符都不用 第8个函数能不能满足???
- //不满足
- //方法重载
- /*
- void print2(int *a,int n){
- for (int i = 0; i<n; i++) {
- cout << a[i];
- }
- cout << endl;
- }
- */
- #pragma 哑元参数 哑元参数没有实际意义,解决方法冲突 解决版本兼容(例如本来的版本里面有个函数有2个参数的 到现在这个版本这个函数只需要1个参数。如果把这个函数改成1个参数,那么就不兼容原来的了)
- //10 . 哑元参数 随便给一个类型 不用变量名
- void print2(int *a,int n,float){
- for (int i = 0; i<n; i++) {
- cout << a[i];
- }
- cout << endl;
- }
- //主函数
- int main(){
- //声明数组
- int a[] = {12,15,16,18,23,89,73,29,88,97};
- //调用函数输出遍历数组
- print(a, 10, '*', false);
- print(a, 10, '/', true);
- cout << "===== 默认值 ====" << endl;
- //7
- //可以不用给定是否需要输出中括号 因为已经给定默认值了
- print(a,10,' ');
- //另外一次特殊的可以给定 传进参数的话
- print(a, 10, '*',false);
- //8
- //没有输入最后一个参数 会默认使用给定的默认值
- print2(a, 10,'^');
- //在我输入的时候 不输入分隔符 就会自动用默认值了
- // print2(a, 10);
- //9.
- //print2(a, 10,'');
- // print2(a, 10);//这样会报错,因为第8个函数也可以只是传2个参数 所以这里就不知道用第8个还是第9个的了 那该怎么办?除了改方法名 用哑元参数
- cout << "==== 哑元参数 ====" << endl;
- //10
- print2(a, 10);//只传2个参数 那就是使用第8个默认值的函数
- //如果想要挨个打印 那就传一个哑元参数
- print2(a,10,3.14f);
- return 0;
- }
运行结果如下:
- 12*15*16*18*23*89*73*29*88*97*
- [12/15/16/18/23/89/73/29/88/97/]
- ===== 默认值 ====
- [12 15 16 18 23 89 73 29 88 97 ]
- 12*15*16*18*23*89*73*29*88*97*
- [12^15^16^18^23^89^73^29^88^97^]
- ==== 哑元参数 ====
- [12 15 16 18 23 89 73 29 88 97 ]
- 12151618238973298897
- Program ended with exit code: 0
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1. 静态类相当于一个sealed abstract类,主要的一个优点是写在栈中,安全高速稳定,而且在执行的时候,十分优先。 类可以声明为 static 的,以指示它仅包含静态成员。不能使用 new 关键字创建静态类的实例。 使用静态类来包含不与特定对象关联的方法。例如,创建一组不操作实例数据并且不与代码中的特定对象关联的方法是很常见的要求。您应该使用静态类来包含那些方法。
2. 它们仅包含静态成员。
- 1
- 2
- 3
它们不能被实例化。
它们是密封的。
它们不能包含实例构造函数)。
因此创建静态类与创建仅包含静态成员和私有构造函数的类大致一样。私有构造函数阻止类被实例化。
3. 使用静态类的优点在于,编译器能够执行检查以确保不致偶然地添加实例成员。编译器将保证不会创建此类的实例。
静态类是密封的,因此不可被继承。静态类不能包含构造函数,但仍可声明静态构造函数以分配初始值或设置某个静态状态。
4. 静态类不能有实例构造器。
静态类不能有任何实例成员。 静态类不能使用abstract或sealed修饰符。 静态类默认继承自System.Object根类,不能显式指定任何其他基类。
静态类不能指定任何接口实现。
静态类的成员不能有protected或protected internal访问保护修饰符。
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
java中的单例模式与静态类
静态类不能有实例构造器。
静态类不能有任何实例成员。
静态类不能使用abstract或sealed修饰符。
静态类默认继承自System.Object根类,不能显式指定任何其他基类。
静态类不能指定任何接口实现。
静态类的成员不能有protected或protected internal访问保护修饰符。
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
当将类的某个数据成员声明为static时,该静态数据成员只能被定义一次,而且要被同类的所有对象共享。各个对象都拥有类中每一个普通数据成员的副本,但静态数据成员只有一个实例存在,与定义了多少类对象无关。静态方法就是与该类相关的,是类的一种行为,而不是与该类的实例对象相关。
静态数据成员的用途之一是统计有多少个对象实际存在。
静态数据成员不能在类中初始化,实际上类定义只是在描述对象的蓝图,在其中指定初值是不允许的。也不能在类的构造函数中初始化该成员,因为静态数据成员为类的各个对象共享,否则每次创建一个类的对象则静态数据成员都要被重新初始化。
静态成员不可在类体内进行赋值,因为它是被所有该类的对象所共享的。你在一个对象里给它赋值,其他对象里的该成员也会发生变化。为了避免混乱,所以不可在类体内进行赋值。
静态成员的值对所有的对象是一样的。静态成员可以被初始化,但只能在类体外进行初始化。
一般形式:
数据类型类名::静态数据成员名=初值
注意:不能用参数初始化表对静态成员初始化。一般系统缺省初始为0。
静态成员是类所有的对象的共享的成员,而不是某个对象的成员。它在对象中不占用存储空间,这个属性为整个类所共有,不属于任何一个具体对象。所以静态成员不能在类的内部初始化,比如声明一个学生类,其中一个成员为学生总数,则这个变量就应当声明为静态变量,应该根据实际需求来设置成员变量。
#include "iostream"
using namespace std;
class test
{
private:
int x;
int y;
public:
static int num;
static int Getnum()
{
x+=5; // 这行代码是错误的,静态成员函数不能调用非静态数据成员,要通过类的对象来调用。
num+=15;
return num;
}
};
int test::num = 10;
int main(void)
{
test a;
cout<<test::num<<endl; //10
test::num = 20;
cout<<test::num<<endl; //20
cout<<test::Getnum()<<endl; //35
cout<<a.Getnum()<<endl; //50
system("pause");
return 0;
}
通过上例可知: x+=5; // 这行代码是错误的
静态函数成员必须通过对象名来访问非静态数据成员。
另外,静态成员函数在类外实现时候无须加static关键字,否则是错误的。
若在类的体外来实现上述的那个静态成员函数,不能加static关键字,这样写就可以了:
int test::Getnum()
{
.........
}
1、static成员的所有者是类本身和对象,但是多有对象拥有一样的静态成员。从而在定义对象是不能通过构造函数对其进行初始化。
2、静态成员不能在类定义里边初始化,只能在class body外初始化。
3、静态成员仍然遵循public,private,protected访问准则。
4、静态成员函数没有this指针,它不能返回非静态成员,因为除了对象会调用它外,类本身也可以调用。
静态成员函数可以直接访问该类的静态数据和函数成员,而访问非静态数据成员必须通过参数传递的方式得到一个对象名,然后通过对象名来访问。
class Myclass
{
private:
int a,b,c;
static int Sum; //声明静态数据成员
public:
Myclass(int a,int b,int c);
void GetSum();
};
int Myclass::Sum=0; //定义并初始化静态数据成员
Myclass::Myclass(int a,int b,int c)
{
this->a=a;
this->b=b;
this->c=c;
Sum+=a+b+c;
}
void Myclass::GetSum()
{
cout <<"Sum=" <<Sum <<endl;
}
int main(void)
{
Myclass me(10,20,30);
me.GetSum();
system("pause");
return 0;
}
由上例可知,非静态成员函数可以任意地访问静态成员函数和静态数据成员。
非静态成员函数Myclass(int a,int b,int c)和GetSum()都访问了静态数据成员Sum。
静态成员函数不能访问非静态成员函数和非静态数据成员。
关于静态成员函数,可以总结为以下几点:
出现在类体外的函数定义不能指定关键字static;
静态成员之间可以相互访问,包括静态成员函数访问静态数据成员和访问静态成员函数;
非静态成员函数可以任意地访问静态成员函数和静态数据成员;
静态成员函数不能访问非静态成员函数和非静态数据成员;
由于没有this指针的额外开销,因此静态成员函数与类的全局函数相比速度上会有少许的增长;
调用静态成员函数,可以用成员访问操作符(.)和(->)为一个类的对象或指向类对象的指针调用静态成员函数,当同一类的所有对象使用一个量时,对于这个共用的量,可以用静态数据成员变量,这个变量对于同一类的所有的对象都取相同的值。静态成员变量只能被静态成员函数调用。静态成员函数也是由同一类中的所有对象共用。只能调用静态成员变量和静态成员函数。
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
static对象如果出现在类中,那么该对象即使从未被使用到,它也会被构造以及析构。而函数中的static对象,如果该函数从未被调用,这个对象也就绝不会诞生,但是在函数每次被调用时检查对象是否需要诞生。
下面详细说说static的功能以及它的来龙去脉:
static作为编程语言里面一种重要的数据类型,它的地位在面试的过程里也是相当的高。
为什么要引入static
函数内部定义的变量,在程序执行到它的定义处时,编译器为它在栈上分配空间,大家知道,函数在栈上分配的空间在此函数执行结束时会释放掉,这样就产生了一个问题: 如果想将函数中此变量的值保存至下一次调用时,如何实现?最容易想到的方法是定义一个全局的变量,但定义为一个全局变量有许多缺点,最明显的缺点是破坏了此变量的访问范围(使得在此函数中定义的变量,不仅仅受此函数控制)。
什么时候用static
需要一个数据对象为整个类而非某个对象服务,同时又力求不破坏类的封装性,即要求此成员隐藏在类的内部,对外不可见。
static的内部机制
静态数据成员要在程序一开始运行时就必须存在。因为函数在程序运行中被调用,所以静态数据成员不能在任何函数内分配空间和初始化。
这样,它的空间分配有三个可能的地方,一是作为类的外部接口的头文件,那里有类声明;二是类定义的内部实现,那里有类的成员函数定义;三是应用程序的main()函数前的全局数据声明和定义处。
静态数据成员要实际地分配空间,故不能在类的声明中定义(只能声明数据成员)。类声明只声明一个类的“尺寸和规格”,并不进行实际的内存分配,所以在类声明中写成定义是错误的。它也不能在头文件中类声明的外部定义,因为那会造成在多个使用该类的源文件中,对其重复定义。
static被引入以告知编译器,将变量存储在程序的静态存储区而非栈上空间,静态
数据成员按定义出现的先后顺序依次初始化,注意静态成员嵌套时,要保证所嵌套的成员已经初始化了。消除时的顺序是初始化的反顺序。
static的优势
可以节省内存,因为它是所有对象所公有的,因此,对多个对象来说,静态数据成员只存储一处,供所有对象共用。静态数据成员的值对每个对象都是一样,但它的值是可以更新的。只要对静态数据成员的值更新一次,保证所有对象存取更新后的相同的值,这样可以提高时间效率。
应用格式(C++)
引用静态数据成员时,采用如下格式:
<类名>::<静态成员名>
如果静态数据成员的访问权限允许的话(即public的成员),可在程序中,按上述格式来引用静态数据成员。
注意事项
(1)类的静态成员函数是属于整个类而非类的对象,所以它没有this指针,这就导致了它仅能访问类的静态数据和静态成员函数。
(2)不能将静态成员函数定义为虚函数。(注意:如果在static函数加上virtual关键字就会报出这样子的错误error: Semantic Issue: 'virtual' can only appear on non-static member functions,大家懂了吧!
)
(3)由于静态成员声明于类中,操作于其外,所以对其取地址操作,就多少有些特殊,变量地址是指向其数据类型的指针,函数地址类型是一个“nonmember函数指针”。
(4)由于静态成员函数没有this指针,所以就差不多等同于nonmember函数,结果就产生了一个意想不到的好处:成为一个callback函数,使得我们得以将C++和C-based X Window系统结合,同时也成功的应用于线程函数身上。
(5)static并没有增加程序的时空开销,相反她还缩短了子类对父类静态成员的访问时间,节省了子类的内存空间。
(6)静态数据成员在<定义或说明>时前面加关键字static。
(7)静态数据成员是静态存储的,所以必须对它进行初始化。
(8)静态成员初始化与一般数据成员初始化不同:
初始化在类体外进行,而前面不加static,以免与一般静态变量或对象相混淆;
初始化时不加该成员的访问权限控制符private,public等;
初始化时使用作用域运算符来标明它所属类;
所以我们得出静态数据成员初始化的格式:
<数据类型><类名>::<静态数据成员名>=<值>
(9)为了防止父类的影响,可以在子类定义一个与父类相同的静态变量,以屏蔽父类的影响。这里有一点需要注意:我们说静态成员为父类和子类共享,但我们有重复定义了静态成员,这会不会引起错误呢?不会,我们的编译器采用了一种绝妙的手法:name-mangling 用以生成唯一的标志。在各通信公司的笔试面试中经常出现的考题就是static的作用及功能。
(1)面向对象的static关键字
1、静态数据成员
在类内数据成员的声明前加上关键字static,该数据成员就是类内的静态数据成员。先举一个静态数据成员的例子。
//Example 1
#include <iostream.h>
class Myclass
{
public:
Myclass(int a,int b,int c);
void GetSum();
private:
int a,b,c;
static int Sum;//声明静态数据成员
};
int Myclass::Sum=0;//定义并初始化静态数据成员
Myclass::Myclass(int a,int b,int c)
{ this->a=a;
this->b=b;
this->c=c;
Sum+=a+b+c;}
void Myclass::GetSum()
{ cout<<"Sum="<<Sum<<endl;
}
void main()
{ Myclass M(1,2,3);
M.GetSum();
Myclass N(4,5,6);
N.GetSum();
M.GetSum();}
可以看出,静态数据成员有以下特点:
对于非静态数据成员,每个类对象都有自己的拷贝。而静态数据成员被当作是类的成员。无论这个类的对象被定义了多少个,静态数据成员在程序中也只有一份拷贝,由该类型的所有对象共享访问。也就是说,静态数据成员是该类的所有对象所共有的。对该类的多个对象来说,静态数据成员只分配一次内存,供所有对象共用。所以,静态数据成员的值对每个对象都是一样的,它的值可以更新;
静态数据成员存储在全局数据区。静态数据成员定义时要分配空间,所以不能在类声明中定义。在Example 5中,语句int Myclass::Sum=0;是定义静态数据成员;
静态数据成员和普通数据成员一样遵从public,protected,private访问规则;
因为静态数据成员在全局数据区分配内存,属于本类的所有对象共享,所以,它不属于特定的类对象,在没有产生类对象时其作用域就可见,即在没有产生类的实例时,我们就可以操作它.
类的静态数据成员有两种访问形式:
<类对象名>.<静态数据成员名> 或 <类类型名>::<静态数据成员名>
如果静态数据成员的访问权限允许的话(即public的成员),可在程序中,按上述格式来引用静态数据成员 ;
静态数据成员主要用在各个对象都有相同的某项属性的时候。比如对于一个存款类,每个实例的利息都是相同的。所以,应该把利息设为存款类的静态数据成员。这有两个好处,第一,不管定义多少个存款类对象,利息数据成员都共享分配在全局数据区的内存,所以节省存储空间。第二,一旦利息需要改变时,只要改变一次,则所有存款类对象的利息全改变过来了;
同全局变量相比,使用静态数据成员有两个优势:
静态数据成员没有进入程序的全局名字空间,因此不存在与程序中其它全局名字冲突的可能性;
可以实现信息隐藏。静态数据成员可以是private成员,而全局变量不能;
2、静态成员函数
与静态数据成员一样,我们也可以创建一个静态成员函数,它为类的全部服务而不是为某一个类的具体对象服务。静态成员函数与静态数据成员一样,都是类的内部实现,属于类定义的一部分。普通的成员函数一般都隐含了一个this指针,this指针指向类的对象本身,因为普通成员函数总是具体的属于某个类的具体对象的。通常情况下,this 是缺省的。如函数fn()实际上是this->fn()。但是与普通函数相比,静态成员函数由于不是与任何的对象相联系,因此它不具有this指针。从这个意义上讲,它无法访问属于类对象的非静态数据成员,也无法访问非静态成员函数,它只能调用其余的静态成员函数。下面举个静态成员函数的例子。
//Example 2
#include <iostream.h>
class Myclass
{public:
Myclass(int a,int b,int c);
static void GetSum();/声明静态成员函数
private:
int a,b,c;
static int Sum;//声明静态数据成员
};
int Myclass::Sum=0;//定义并初始化静态数据成员
Myclass::Myclass(int a,int b,int c)
{ this->a=a;
this->b=b;
this->c=c;
Sum+=a+b+c; //非静态成员函数可以访问静态数据成员
}
void Myclass::GetSum() //静态成员函数的实现
{
// cout<<a<<endl; //错误代码,a是非静态数据成员
cout<<"Sum="<<Sum<<endl;
}
void main()
{ Myclass M(1,2,3);
M.GetSum();
Myclass N(4,5,6);
N.GetSum();
Myclass::GetSum();
}
关于静态成员函数,可以总结为以下几点:
出现在类体外的函数定义不能指定关键字static;
静态成员之间可以相互访问,包括静态成员函数访问静态数据成员和访问静态成员函数;
非静态成员函数可以任意地访问静态成员函数和静态数据成员;
静态成员函数不能访问非静态成员函数和非静态数据成员;
由于没有this指针的额外开销,因此静态成员函数与类的全局函数相比速度上会有少许的增长;
调用静态成员函数,可以用成员访问操作符(.)和(->)为一个类的对象或指向类对象的指针调用静态成员函数,也可以直接使用如下格式:
<类名>::<静态成员函数名>(<参数表>)
调用类的静态成员函数。
作用
static静态变量声明符。在声明它的程序块,子程序块或函数内部有效,值保持,在整个程序期间分配存储器空间,编译器默认值0。
是C++中很常用的修饰符,它被用来控制变量的存储方式和可见性
(2)面向过程的static关键字:功能:隐藏作用、值保持。
- 隐藏作用。当我们同时编译多个文件时,所有未加static前缀的全局变量和函数都具有全局可见性。如果加了static,就会对其它源文件隐藏。利用这一特性可以在不同的文件中定义同名函数和同名变量,而不必担心命名冲突。Static可以用作函数和变量的前缀,对于函数来讲,static的作用仅限于隐藏,而对于变量,static还有下面两个作用。
- 保持变量内容的持久。存储在静态数据区的变量会在程序刚开始运行时就完成初始化,也是唯一的一次初始化。共有两种变量存储在静态存储区:全局变量和static变量,只不过和全局变量比起来,static可以控制变量的可见范围,说到底static还是用来隐藏的。
- 默认初始化为0。其实全局变量也具备这一属性,因为全局变量也存储在静态数据区。在静态数据区,内存中所有的字节默认值都是0x00,某些时候这一特点可以减少程序员的工作量。比如初始化一个稀疏矩阵,我们可以一个一个地把所有元素都置0,然后把不是0的几个元素赋值。如果定义成静态的,就省去了一开始置0的操作。再比如要把一个字符数组当字符串来用,但又觉得每次在字符数组末尾加’\ 0’太麻烦。如果把字符串定义成静态的,就省去了这个麻烦,因为那里本来就是’\0’。
总之,首先static的最主要功能是隐藏,其次因为static变量存放在静态存储区,所以它具备持久性和默认值0。
另外,记住c++的一个哲学基础:你不应该为你并不使用的东西付出任何代价。
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
C++的static有两种用法:面向过程程序设计中的static和面向对象程序设计中的static。前者应用于普通变量和函数,不涉及类;后者主要说明static在类中的作用。
1.面向过程设计中的static
1.1静态全局变量
在全局变量前,加上关键字static,该变量就被定义成为一个静态全局变量。我们先举一个静态全局变量的例子,如下:
//Example 1
#include <iostream.h>
void fn();
static int n; //定义静态全局变量
void main()
{
n=20;
cout<<n<<endl;
fn();
}
void fn()
{
n++;
cout<<n<<endl;
}
静态全局变量有以下特点:
• 该变量在全局数据区分配内存;
• 未经初始化的静态全局变量会被程序自动初始化为0(自动变量的值是随机的,除非它被显式初始化);
• 静态全局变量在声明它的整个文件都是可见的,而在文件之外是不可见的;
静态变量都在全局数据区分配内存,包括后面将要提到的静态局部变量。对于一个完整的程序,在内存中的分布情况如下图:
代码区
全局数据区
堆区
栈区
一般程序的由new产生的动态数据存放在堆区,函数内部的自动变量存放在栈区。自动变量一般会随着函数的退出而释放空间,静态数据(即使是函数内部的静态局部变量)也存放在全局数据区。全局数据区的数据并不会因为函数的退出而释放空间。细心的读者可能会发现,Example 1中的代码中将 “static int n; //定义静态全局变量”改为“int n; //定义全局变量”。程序照样正常运行。的确,定义全局变量就可以实现变量在文件中的共享,但定义静态全局变量还有以下好处:
• 静态全局变量不能被其它文件所用;
• 其它文件中可以定义相同名字的变量,不会发生冲突;
您可以将上述示例代码改为如下:
//Example 2
//File1
#include <iostream.h>
void fn();
static int n; //定义静态全局变量
void main()
{
n=20;
cout<<n<<endl;
fn();
}
//File2
#include <iostream.h>
extern int n;
void fn()
{
n++;
cout<<n<<endl;
}
编译并运行Example 2,您就会发现上述代码可以分别通过编译,但运行时出现错误。试着将 “static int n; //定义静态全局变量”改为“int n; //定义全局变量”
再次编译运行程序,细心体会“全局变量”和"静态全局变量"的区别。
1.2.静态局部变量
在局部变量前,加上关键字static,该变量就被定义成为一个静态局部变量。 我们先举一个静态局部变量的例子,如下:
//Example 3
#include <iostream.h>
void fn();
void main()
{
fn();
fn();
fn();
}
void fn()
{
static n=10;
cout<<n<<endl;
n++;
}
通常,在函数体内定义了一个变量,每当程序运行到该语句时都会给该局部变量分配栈内存。但随着程序退出函数体,系统就会收回栈内存,局部变量也相应失效。但有时候我们需要在两次调用之间对变量的值进行保存。通常的想法是定义一个全局变量来实现。但这样一来,变量已经不再属于函数本身了,不再仅受函数的控制,给程序的维护带来不便。
静态局部变量正好可以解决这个问题。静态局部变量保存在全局数据区,而不是保存在栈中,每次的值保持到下一次调用,直到下次赋新值。
静态局部变量有以下特点:
• 该变量在全局数据区分配内存;
• 静态局部变量在程序执行到该对象的声明处时被首次初始化,即以后的函数调用不再进行初始化;
• 静态局部变量一般在声明处初始化,如果没有显式初始化,会被程序自动初始化为0;
• 它始终驻留在全局数据区,直到程序运行结束。但其作用域为局部作用域,当定义它的函数或语句块结束时,其作用域随之结束;
1.3静态函数
在函数的返回类型前加上static关键字,函数即被定义为静态函数。静态函数与普通函数不同,它只能在声明它的文件当中可见,不能被其它文件使用。
静态函数的例子:
//Example 4
#include <iostream.h>
static void fn();//声明静态函数
void main()
{
fn();
}
void fn()//定义静态函数
{
int n=10;
cout<<n<<endl;
}
定义静态函数的好处:
• 静态函数不能被其它文件所用;
• 其它文件中可以定义相同名字的函数,不会发生冲突;
二、面向对象的static关键字(类中的static关键字)
2.1静态数据成员
在类内数据成员的声明前加上关键字static,该数据成员就是类内的静态数据成员。先举一个静态数据成员的例子。
//Example 5
#include <iostream.h>
class Myclass
{
public:
Myclass(int a,int b,int c);
void GetSum();
private:
int a,b,c;
static int Sum;//声明静态数据成员
};
int Myclass::Sum=0;//定义并初始化静态数据成员
Myclass::Myclass(int a,int b,int c)
{
this->a=a;
this->b=b;
this->c=c;
Sum+=a+b+c;
}
void Myclass::GetSum()
{
cout<<"Sum="<<Sum<<endl;
}
void main()
{
Myclass M(1,2,3);
M.GetSum();
Myclass N(4,5,6);
N.GetSum();
M.GetSum();
}
可以看出,静态数据成员有以下特点:
• 对于非静态数据成员,每个类对象都有自己的拷贝。而静态数据成员被当作是类的成员。无论这个类的对象被定义了多少个,静态数据成员在程序中也只有一份拷贝,由该类型的所有对象共享访问。也就是说,静态数据成员是该类的所有对象所共有的。对该类的多个对象来说,静态数据成员只分配一次内存,供所有对象共用。所以,静态数据成员的值对每个对象都是一样的,它的值可以更新;
• 静态数据成员存储在全局数据区。静态数据成员定义时要分配空间,所以不能在类声明中定义。在Example 5中,语句int Myclass::Sum=0;是定义静态数据成员;
• 静态数据成员和普通数据成员一样遵从public,protected,private访问规则;
• 因为静态数据成员在全局数据区分配内存,属于本类的所有对象共享,所以,它不属于特定的类对象,在没有产生类对象时其作用域就可见,即在没有产生类的实例时,我们就可以操作它;
• 静态数据成员初始化与一般数据成员初始化不同。静态数据成员初始化的格式为:
<数据类型><类名>::<静态数据成员名>=<值>
• 类的静态数据成员有两种访问形式:
<类对象名>.<静态数据成员名> 或 <类类型名>::<静态数据成员名>
如果静态数据成员的访问权限允许的话(即public的成员),可在程序中,按上述格式来引用静态数据成员 ;
• 静态数据成员主要用在各个对象都有相同的某项属性的时候。比如对于一个存款类,每个实例的利息都是相同的。所以,应该把利息设为存款类的静态数据成员。这有两个好处,第一,不管定义多少个存款类对象,利息数据成员都共享分配在全局数据区的内存,所以节省存储空间。第二,一旦利息需要改变时,只要改变一次,则所有存款类对象的利息全改变过来了;
• 同全局变量相比,使用静态数据成员有两个优势:
1. 静态数据成员没有进入程序的全局名字空间,因此不存在与程序中其它全局名字冲突的可能性;
2. 可以实现信息隐藏。静态数据成员可以是private成员,而全局变量不能;
2.2静态成员函数
与静态数据成员一样,我们也可以创建一个静态成员函数,它为类的全部服务而不是为某一个类的具体对象服务。静态成员函数与静态数据成员一样,都是类的内部实现,属于类定义的一部分。普通的成员函数一般都隐含了一个this指针,this指针指向类的对象本身,因为普通成员函数总是具体的属于某个类的具体对象的。通常情况下,this是缺省的。如函数fn()实际上是this->fn()。但是与普通函数相比,静态成员函数由于不是与任何的对象相联系,因此它不具有this指针。从这个意义上讲,它无法访问属于类对象的非静态数据成员,也无法访问非静态成员函数,它只能调用其余的静态成员函数。下面举个静态成员函数的例子。
//Example 6
#include <iostream.h>
class Myclass
{
public:
Myclass(int a,int b,int c);
static void GetSum();/声明静态成员函数
private:
int a,b,c;
static int Sum;//声明静态数据成员
};
int Myclass::Sum=0;//定义并初始化静态数据成员
Myclass::Myclass(int a,int b,int c)
{
this->a=a;
this->b=b;
this->c=c;
Sum+=a+b+c; //非静态成员函数可以访问静态数据成员
}
void Myclass::GetSum() //静态成员函数的实现
{
// cout<<a<<endl; //错误代码,a是非静态数据成员
cout<<"Sum="<<Sum<<endl;
}
void main()
{
Myclass M(1,2,3);
M.GetSum();
Myclass N(4,5,6);
N.GetSum();
Myclass::GetSum();
}
关于静态成员函数,可以总结为以下几点:
• 出现在类体外的函数定义不能指定关键字static;
• 静态成员之间可以相互访问,包括静态成员函数访问静态数据成员和访问静态成员函数;
• 非静态成员函数可以任意地访问静态成员函数和静态数据成员;
• 静态成员函数不能访问非静态成员函数和非静态数据成员;
• 由于没有this指针的额外开销,因此静态成员函数与类的全局函数相比速度上会有少许的增长;
• 调用静态成员函数,可以用成员访问操作符(.)和(->)为一个类的对象或指向类对象的指针调用静态成员函数,也可以直接使用如下格式:
<类名>::<静态成员函数名>(<参数表>)
调用类的静态成员函数。
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
C++类中静态变量和静态方法使用介绍
哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈
静态成员的提出是为了解决数据共享的问
题。实现共享有许多方法,如:设置全局
性的变量或对象是一种方法。但是,全局
变量或对象是有局限性的。这一章里,我
们主要讲述类的静态成员来实现数据的共
享。
在C++中,静态成员是属于整个类的而不
是某个对象,静态成员变量只存储一份供
所有对象共用。所以在所有对象中都可以
共享它。使用静态成员变量实现多个对象
之间的数据共享不会破坏隐藏的原则,保
证了安全性还可以节省内存。
哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈
++++++++++++++++++++++++++++++++
在C++中,静态成员是属于整个类的而不是某个对象,静态成员变量只存储一份供所有对象共用。所以在所有对象中都可以共享它。使用静态成员变量实现多个对象之间的数据共享不会破坏隐藏的原则,保证了安全性还可以节省内存。
静态成员的定义或声明要加个关键static。静态成员可以通过双冒号来使用即<类名>::<静态成员名>。
在C++中类的静态成员变量和静态成员函数是个容易出错的地方,本文先通过几个例子来总结静态成员变量和成员函数使用规则,再给出一个实例来加深印象。希望阅读本文可以使读者对类的静态成员变量和成员函数有更为深刻的认识。
第一个例子,通过类名调用静态成员函数和非静态成员函数
- class Point
- {
- public:
- void init()
- {
- }
- static void output()
- {
- }
- };
- void main()
- {
- Point::init();
- Point::output();
- }
编译出错:error C2352: 'Point::init' : illegal call of non-static member function
结论1:不能通过类名来调用类的非静态成员函数。
第二个例子,通过类的对象调用静态成员函数和非静态成员函数
将上例的main()改为:
- void main()
- {
- Point pt;
- pt.init();
- pt.output();
- }
编译通过。
结论2:类的对象可以使用静态成员函数和非静态成员函数。
第三个例子,在类的静态成员函数中使用类的非静态成员
- #include <stdio.h>
- class Point
- {
- public:
- void init()
- {
- }
- static void output()
- {
- printf("%d\n", m_x);
- }
- private:
- int m_x;
- };
- void main()
- {
- Point pt;
- pt.output();
- }
编译出错:error C2597: illegal reference to data member 'Point::m_x' in a static member function
因为静态成员函数属于整个类,在类实例化对象之前就已经分配空间了,而类的非静态成员必须在类实例化对象后才有内存空间,所以这个调用就出错了,就好比没有声明一个变量却提前使用它一样。
结论3:静态成员函数中不能引用非静态成员。
第四个例子,在类的非静态成员函数中使用类的静态成员
- class Point
- {
- public:
- void init()
- {
- output();
- }
- static void output()
- {
- }
- };
- void main()
- {
- Point pt;
- pt.output();
- }
编译通过。
结论4:类的非静态成员函数可以调用用静态成员函数,但反之不能。
第五个例子,使用类的静态成员变量
- #include <stdio.h>
- class Point
- {
- public:
- Point()
- {
- m_nPointCount++;
- }
- ~Point()
- {
- m_nPointCount--;
- }
- static void output()
- {
- printf("%d\n", m_nPointCount);
- }
- private:
- static int m_nPointCount;
- };
- void main()
- {
- Point pt;
- pt.output();
- }
按Ctrl+F7编译无错误,按F7生成EXE程序时报链接错误
error LNK2001: unresolved external symbol "private: static int Point::m_nPointCount" (?m_nPointCount@Point@@0HA)
这是因为类的静态成员变量在使用前必须先初始化。
在main()函数前加上int Point::m_nPointCount = 0;
再编译链接无错误,运行程序将输出1。
结论5:类的静态成员变量必须先初始化再使用。
结合上面的五个例子,对类的静态成员变量和成员函数作个总结:
一。静态成员函数中不能调用非静态成员。
二。非静态成员函数中可以调用静态成员。因为静态成员属于类本身,在类的对象产生之前就已经存在了,所以在非静态成员函数中是可以调用静态成员的。
三。静态成员变量使用前必须先初始化(如int MyClass::m_nNumber = 0;),否则会在linker时出错。
再给一个利用类的静态成员变量和函数的例子以加深理解,这个例子建立一个学生类,每个学生类的对象将组成一个双向链表,用一个静态成员变量记录这个双向链表的表头,一个静态成员函数输出这个双向链表。
- #include <stdio.h>
- #include <string.h>
- const int MAX_NAME_SIZE = 30;
- class Student
- {
- public:
- Student(char *pszName);
- ~Student();
- public:
- static void PrintfAllStudents();
- private:
- char m_name[MAX_NAME_SIZE];
- Student *next;
- Student *prev;
- static Student *m_head;
- };
- Student::Student(char *pszName)
- {
- strcpy(this->m_name, pszName);
- //建立双向链表,新数据从链表头部插入。
- this->next = m_head;
- this->prev = NULL;
- if (m_head != NULL)
- m_head->prev = this;
- m_head = this;
- }
- Student::~Student ()//析构过程就是节点的脱离过程
- {
- if (this == m_head) //该节点就是头节点。
- {
- m_head = this->next;
- }
- else
- {
- this->prev->next = this->next;
- this->next->prev = this->prev;
- }
- }
- void Student::PrintfAllStudents()
- {
- for (Student *p = m_head; p != NULL; p = p->next)
- printf("%s\n", p->m_name);
- }
- Student* Student::m_head = NULL;
- void main()
- {
- Student studentA("AAA");
- Student studentB("BBB");
- Student studentC("CCC");
- Student studentD("DDD");
- Student student("MoreWindows");
- Student::PrintfAllStudents();
- }
程序将输出:
当然在本例还可以增加个静态成员变量来表示链表中学生个数,如果读者有兴趣,就将这个作为小练习吧。
++++++++++++++++++++++++++++++++
静态成员的定义或声明要加个关键static。静态成员可以通过双冒号来使用即<类名>::<静态成员名>。
静态数据成员
在类中,静态成员可以实现多个对象之间的数据共享,并且使用静态数据成员还不会破坏隐藏的原则,即保证了安全性。因此,静态成员是类的所有对象中共享的成员,而不是某个对象的成员。
使用静态数据成员可以节省内存,因为它是所有对象所公有的,因此,对多个对象来说,静态数据成员只存储一处,供所有对象共用。静态数据成员的值对每个对象都是一样,但它的值是可以更新的。只要对静态数据成员的值更新一次,保证所有对象存取更新后的相同的值,这样可以提高时间效率。
静态数据成员的使用方法和注意事项如下:
1、静态数据成员在定义或说明时前面加关键字static。//静态变量的定义
2、静态成员初始化与一般数据成员初始化不同。静态数据成员初始化的格式如下:
<数据类型><类名>::<静态数据成员名>=<值> //静态变量的初始化
这表明:
(1) 初始化在类体外进行,而前面不加
static,(这点需要注意)以免与一般静态
变量或对象相混淆。
(2) 初始化时不加该成员的访问权限控制符private,public等。
(3) 初始化时使用作用域运算符来标明它所属类,因此,静态数据成员是类的成
员,而不是对象的成员。
3、静态数据成员是静态存储的,它是静态生存期,必须对它进行初始化。
4、引用静态数据成员时,采用如下格式:
<类名>::<静态成员名> //静态变量的使用方式
如果静态数据成员的访问权限允许的话(即public的成员),可在程序中,按上述格式来引用静态数据成员。
下面举一例子,说明静态数据成员的应用:
1 class StaticTest 2 { 3 public: 4 StaticTest(int a, int b, int c); 5 void GetNumber(); 6 void GetSum(); 7 static void f1(StaticTest &s); 8 private: 9 int A, B, C; 10 static int Sum; 11 }; 12 13 14 15 #include "StaticTest.h" 16 #include <iostream> 17 using namespace std; 18 19 int StaticTest::Sum = 0;//静态成员在此初始化 20 21 StaticTest::StaticTest(int a, int b, int c) 22 { 23 A = a; 24 B = b; 25 C = c; 26 Sum += A + B + C; 27 } 28 29 void StaticTest::GetNumber() 30 { 31 cout << "Number = " << endl; 32 } 33 34 void StaticTest::GetSum() 35 { 36 cout << "Sum = " << Sum <<endl; 37 } 38 39 void StaticTest::f1(StaticTest &s) 40 { 41 42 cout << s.A << endl;//静态方法不能直接调用一般 成员,可以通过对象引用实现调用 43 cout << Sum <<endl; 44 } 45 46 #include "StaticTest.h" 47 #include <stdlib.h> 48 49 50 int main(void) 51 { 52 StaticTest M(3, 7, 10), N(14, 9, 11); 53 M.GetNumber(); 54 N.GetSum(); 55 M.GetNumber(); 56 N.GetSum(); 57 StaticTest::f1(M); 58 system("pause"); 59 return 0; 60 }
注意,static成员的初始化要在实现中进行,不能在头文件进行。
从输出结果可以看到Sum的值对M对象和对N对象都是相等的。这是因为在初始化M对象时,将M对象的三个int型数据成员的值求和后赋给了Sum,于是Sum保存了该值。在初始化N对象时,对将N对象的三个int型数据成员的值求和后又加到Sum已有的值上,于是Sum将保存另后的值。所以,不论是通过对象M还是通过对象N来引用的值都是一样的,即为54,s.A=3。
静态成员函数
静态成员函数和静态数据成员一样,它们都属于类的静态成员,它们都不是对象成员。因此,对静态成员的引用不需要用对象名。
在静态成员函数的实现中不能直接引用类中说明的非静态成员,可以引用类中说明的静态成员(这点非常重要)。如果静态成员函数中要引用非静态成员时,可通过对象来引用。从中可看出,调用静态成员函数使用如下格式:<类名>::<静态成员函数名>(<参数表>);
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
静态成员变量 | 静态方法 | 实例成员变量 | 实例方法 | |
静态方法 | 直接访问 | 直接访问 | 不可直接访问 | 不可直接访问 |
实例方法 | 直接访问 | 直接访问 | 直接访问 | 直接访问 |
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
C++静态类
1. 静态类相当于一个sealed abstract类,主要的一个优点是写在栈中,安全高速稳定,而且在执行的时候,十分优先。 类可以声明为 static 的,以指示它仅包含静态成员。不能使用 new 关键字创建静态类的实例。 使用静态类来包含不与特定对象关联的方法。例如,创建一组不操作实例数据并且不与代码中的特定对象关联的方法是很常见的要求。您应该使用静态类来包含那些方法。
2. 它们仅包含静态成员。
- 1
- 2
- 3
它们不能被实例化。
它们是密封的。
它们不能包含实例构造函数)。
因此创建静态类与创建仅包含静态成员和私有构造函数的类大致一样。私有构造函数阻止类被实例化。
3. 使用静态类的优点在于,编译器能够执行检查以确保不致偶然地添加实例成员。编译器将保证不会创建此类的实例。
静态类是密封的,因此不可被继承。静态类不能包含构造函数,但仍可声明静态构造函数以分配初始值或设置某个静态状态。
4. 静态类不能有实例构造器。
静态类不能有任何实例成员。 静态类不能使用abstract或sealed修饰符。 静态类默认继承自System.Object根类,不能显式指定任何其他基类。
静态类不能指定任何接口实现。
静态类的成员不能有protected或protected internal访问保护修饰符。
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
BBBBBBBBBBBBBBBBBB抽象类
A是抽象类,B是A的子类
A aa2 =null; //可以定义一个抽象类的引用
aa2=new B(); //将子类对象地址赋给抽象类的引用
aa2.f(); //用抽象类的方法访问子类的方法 .这就是多态
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
CCCCCCCCCCCCCCCCCCCCC+++SSL服务协议
DDDDDDDDDDDDDDDDD 共享变量
EEEEEEEEEEEEEEEEEEEEEEEE封装+++继承+++多态
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
回调函数
(1)概念:回调函数,顾名思义,就是使用者自己定义一个函数,使用者自己实现这个函数的程序内容,然后把这个函数作为参数传入别人(或系统)的函数中,由别人(或系统)的函数在运行时来调用的函数。函数是你实现的,但由别人(或系统)的函数在运行时通过参数传递的方式调用,这就是所谓的回调函数。简单来说,就是由别人的函数运行期间来回调你实现的函数。
(2)标准Hello World程序:
int main(int argc,char* argv[]) { printf("Hello World!\n"); return 0; }
将它修改成函数回调样式:
//定义回调函数 void PrintfText() { printf("Hello World!\n"); } //定义实现回调函数的"调用函数" void CallPrintfText(void (*callfuct)()) { callfuct(); } //在main函数中实现函数回调 int main(int argc,char* argv[]) { CallPrintfText(PrintfText); return 0; }
修改成带参的回调样式:
//定义带参回调函数 void PrintfText(char* s) { printf(s); } //定义实现带参回调函数的"调用函数" void CallPrintfText(void (*callfuct)(char*),char* s) { callfuct(s); } //在main函数中实现带参的函数回调 int main(int argc,char* argv[]) { CallPrintfText(PrintfText,"Hello World!\n"); return 0; }