博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Visual Studio 11增强支持的标准 C++11 介绍
阅读量:5060 次
发布时间:2019-06-12

本文共 15122 字,大约阅读时间需要 50 分钟。

Visual Studio 11增强支持的标准 C + + 11

现在支持此预览的 Visual Studio 的 STL 中的新头文件可以进行多线程编程和异步操作管理。

<thread>,<future>,<atomic>,<time>,<mutex>,<condition_variable>,<ratio>,<filesystem>

头文件<thread>作为其名称来创建和操作线程

  1. 1.thread t([]()
  2. 2. {
  3. 3. cout << "ThreadID : " << std::this_thread::get_id() << endl;
  4. 4. });
  5. 5. t.join();

1.thread t([]()

 2.    {   

 3.     cout <<  "ThreadID : " << std::this_thread::get_id() << endl;       

 4.    });       

 5.    t.join();

这是传递给线程的类的构造函数的一种方法,而不是在这里我们使用Lambda 表达式中引入C + + 11Join ()方法,这是一个调用阻塞,使主线程等待,直到线程完成他的工作。如果要解耦变量的类型线程,线程在 Windows 那里 调用 的detach()方法,这样做违背计划的detach()方法,不会影响与线程句柄关联的窗口 (CloseHandle)。因此可能是使用变量的 t 型线,旧 Windows API 通过检索的本机句柄,但代码将成为便携式少得多。

  1. 1.WaitForSingleObject(t.native_handle()._Hnd ,INFINITE);
  2. 2. t.detach();

1.WaitForSingleObject(t.native_handle()._Hnd ,INFINITE);

 2.    t.detach();

在线程, join ()方法是实质相同,上述代码 (在 Windows 平台) 。

很可能也与要检索的可用使用hardware_concurrency()方法的虚拟处理器数目的线程 ,

  1. unsigned numLogicalProc=t.hardware_concurrency();

unsigned numLogicalProc=t.hardware_concurrency();

操作的线程,总是会对同步与保护的关键地区。头<mutex>提供这种排斥同步对象相互示例的效果

注意,使用锁来总是对性能的影响 !

  1. std::this_thread::sleep_for (chrono::seconds(1));
  2. 5. for(int i=0;i<10;i++)
  3. 6. {
  4. 7. m.lock();
  5. 8. cout << "ThreadID : " << std::this_thread::get_id() << ":" << i << endl;
  6. 9. m.unlock ();
  7. 10. }
  8. 11.});
  9. 12.thread t2([&m]()

10. 13.{

11. 14. std::this_thread::sleep_for (chrono::seconds(1));

12. 15. for(int i=0;i<10;i++)

13. 16. {

14. 17. m.lock ();

15. 18. cout << "ThreadID : " << std::this_thread::get_id() << ":" << i << endl;

16. 19. m.unlock();

17. 20. }

18. 21.});

19. 22.t1.join();

20. 23.t2.join();

 std::this_thread::sleep_for (chrono::seconds(1));

 5.    for(int i=0;i<10;i++)

 6.    {   

 7.        m.lock();

 8.            cout <<  "ThreadID : " << std::this_thread::get_id() << ":" << i << endl;       

 9.        m.unlock ();

 10.    }

 11.});       

 12.thread t2([&m]()

 13.{        

 14.    std::this_thread::sleep_for (chrono::seconds(1));

 15.    for(int i=0;i<10;i++)

 16.    {       

 17.        m.lock ();

 18.            cout <<  "ThreadID : " << std::this_thread::get_id() << ":" << i << endl;       

 19.        m.unlock();

 20.    }

 21.});

 22.t1.join();   

 23.t2.join();   

注意this_thread命名空间以检索当前线程的标识号或时间类结合创建点的介绍.

它也是执行的可以控制对生产者/消费者下面的示例使用头文件<condition_variable>,作为多个线程流。

注意到我们使消费者和生产者为互斥体,我们转向方法wait()变量的类型condition_variable_any (它可能还使用condition_variable unique_lock <mutex>型,后者互斥体直接传递到类型unique_lock的初始化过程中未报告的状态。非终止状态指示可以获得互斥体。)

  1. 1.mutex lockBuffer;
  2. 2.volatile BOOL ArretDemande=FALSE;
  3. 3.queue<long> buffer;
  4. 4.condition_variable_any cndNotifierConsommateurs;
  5. 5.condition_variable_any cndNotifierProducteur;
  6. 6.
  7. 7.thread ThreadConsommateur([&]()
  8. 8.{
  9. 9.

10. 10. while(true)

11. 11. {

12. 12.

13. 13. lockBuffer.lock ();

14. 14. while(buffer.empty () && ArretDemande==FALSE)

15. 15. {

16. 16. cndNotifierConsommateurs.wait(lockBuffer);

17. 17. }

18. 18. if (ArretDemande==TRUE && buffer.empty ())

19. 19. {

20. 20. lockBuffer.unlock();

21. 21. cndNotifierProducteur.notify_one ();

22. 22. break;

23. 23. }

24. 24.

25. 25. long element=buffer.front();

26. 26. buffer.pop ();

27. 27. cout << "Consommation element :" << element << " Taille de la file :" << buffer.size() << endl;

28. 28.

29. 29. lockBuffer.unlock ();

30. 30. cndNotifierProducteur.notify_one ();

31. 31. }

32. 32.

33. 33.});

34. 34.

35. 35.thread ThreadProducteur([&]()

36. 36.{

37. 37. //Operation atomic sur un long

38. 38. std::atomic<long> interlock;

39. 39. interlock=1;

40. 40. while(true)

41. 41. {

42. 42. Simule une charge

43. 43. std::this_thread::sleep_for (chrono::milliseconds (15));

44. 44. long element=interlock.fetch_add (1);

45. 45. lockBuffer.lock ();

46. 46. while(buffer.size()==10 && ArretDemande ==FALSE)

47. 47. {

48. 48.

49. 49. cndNotifierProducteur.wait (lockBuffer);

50. 50. }

51. 51. if (ArretDemande==TRUE)

52. 52. {

53. 53.

54. 54. lockBuffer.unlock ();

55. 55. cndNotifierConsommateurs.notify_one ();

56. 56. break;

57. 57. }

58. 58. buffer.push(element);

59. 59. cout << "Production unlement :" << element << " Taille de la file :" << buffer.size() << endl;

60. 60. lockBuffer.unlock ();

61. 61. cndNotifierConsommateurs.notify_one ();

62. 62. }

63. 63.

64. 64.});

65. 65.

66. 66.

67. 67.std::cout << "Pour arreter pressez [ENTREZ]" << std::endl;

68. 68.getchar();

69. 69.

70. 70.std::cout << "Arret demande" << endl;

71. 71.ArretDemande=TRUE;

72. 72.

73. 73.ThreadProducteur.join();

74. 74.ThreadConsommateur.join();

1.mutex lockBuffer;

 2.volatile BOOL ArretDemande=FALSE;

 3.queue<long> buffer;       

 4.condition_variable_any cndNotifierConsommateurs;

 5.condition_variable_any cndNotifierProducteur;   

 6.

 7.thread ThreadConsommateur([&]()

 8.{

 9.   

 10.    while(true)

 11.        {

 12.           

 13.            lockBuffer.lock ();

 14.            while(buffer.empty () && ArretDemande==FALSE)

 15.            {                   

 16.                cndNotifierConsommateurs.wait(lockBuffer);

17.            }

 18.            if (ArretDemande==TRUE && buffer.empty ())

 19.            {

 20.                lockBuffer.unlock();

 21.                cndNotifierProducteur.notify_one ();

 22.                break;

 23.            }

 24.           

 25.            long element=buffer.front();

 26.            buffer.pop ();

 27.            cout << "Consommation element :" << element << " Taille de la file :" << buffer.size() << endl;

 28.           

 29.            lockBuffer.unlock ();

 30.            cndNotifierProducteur.notify_one ();

 31.        }

 32.       

 33.});

 34.

 35.thread ThreadProducteur([&]()

 36.{

 37.    //Operation atomic sur un long

 38.    std::atomic<long> interlock;

 39.    interlock=1;   

 40.    while(true)

 41.    {

 42.            Simule une charge

 43.            std::this_thread::sleep_for (chrono::milliseconds (15));               

 44.            long element=interlock.fetch_add (1);

 45.            lockBuffer.lock ();

 46.            while(buffer.size()==10 && ArretDemande ==FALSE)

 47.            {

 48.               

 49.                cndNotifierProducteur.wait (lockBuffer);

 50.            }

 51.            if (ArretDemande==TRUE)

 52.            {

 53.               

 54.                lockBuffer.unlock ();

 55.                cndNotifierConsommateurs.notify_one ();

 56.                break;

 57.            }

 58.            buffer.push(element);

 59.            cout << "Production unlement :" << element << " Taille de la file :" << buffer.size() << endl;

 60.            lockBuffer.unlock ();

 61.            cndNotifierConsommateurs.notify_one ();

 62.    }

 63.

 64.});

 65.   

 66.

 67.std::cout << "Pour arreter pressez [ENTREZ]" << std::endl;

 68.getchar();

 69.   

 70.std::cout << "Arret demande" << endl;

 71.ArretDemande=TRUE;

 72.

 73.ThreadProducteur.join();

 74.ThreadConsommateur.join();

在示例中,该互斥体将传递给无信号使用锁() 方法。不过如果队列为空 ,就可以开始在执行序列中执行。

此互斥体用来保护尾 <int> 缓冲区类型。等待() 方法使用另一种机制将这挂起,并将等待唤醒,制造者线程仅当它将调用它的方法notify_one()。

使用这里的元素类型,递增 1 在单个原子操作中我们的队列的元素。在多线程的上下文,另外,例如将总是公平的保证元素操作,而不是抢占式。

头文件<future>。未来用于执行异步操作的返回结果,要检索后,没有不同步或线程流量控制机制。示例中,作为互斥体的多个线程的交会点的方法 join () 和控制流对象。

事实上,假设您想要简单的加法的两个整数 A + B,但是来自两个不同的线程所返回的结果。

在下面的示例中,作为不确定何时执行的概念

  1. 1.std::cout << "Thread Principale : ID : " << std::this_thread::get_id() << endl;
  2. 2. future<int> f1(async([]()->int
  3. 3. {
  4. 4. //Simule une charge
  5. 5. std::this_thread::sleep_for (chrono::milliseconds (2000));
  6. 6. std::cout << "Future 1 ID : " << std::this_thread::get_id() << endl;
  7. 7.
  8. 8. return 42;
  9. 9. }));

10. 10.

11. 11. future<int> f2(async([]()->int

12. 12. {

13. 13.

14. 14. std::cout << "Future 2 ID : " << std::this_thread::get_id() << endl;

15. 15.

16. 16. return 84;

17. 17. }));

18. 18.

19. 19. std::cout << "Resultat : " << f1.get () + f2.get() << endl ;

20. 20.

1.std::cout << "Thread Principale : ID : " << std::this_thread::get_id() << endl;       

 2.    future<int> f1(async([]()->int

 3.    {

 4.        //Simule une charge

 5.        std::this_thread::sleep_for (chrono::milliseconds (2000));                       

 6.        std::cout << "Future 1  ID : " << std::this_thread::get_id() << endl;

 7.       

 8.        return 42;

 9.    }));

 10.

 11.    future<int> f2(async([]()->int

 12.    {

 13.       

 14.        std::cout << "Future 2 ID : " << std::this_thread::get_id() << endl;

 15.       

 16.        return 84;

 17.    }));

 18.   

 19.    std::cout << "Resultat : " << f1.get () + f2.get() << endl ;

 20.   

在这里宣布int类型的两个数值以异步类型作为参数的构造函数,它作为其名称在不同的线程中执行异步操作的指示。

两个未来将返回的结果,但不知道何时执行Get ()方法,这是一个调用中担保两个整数的增加会正确的范例。

在将来的VS11调用中,我们使用语法强烈靠近同步语法的异步执行。

Visual Studio 2010以后有可能在 c + + 代码中,更具体地 STL 使用 Lambda 表达式 (匿名方法的窗体)。例如,看下面的代码执行,当使用这些类型的算法for_each、 parallel_for、 parallel_for_each等等的时候。

  1. 1.std::deque<int> d1;
  2. 2. d1.push_back (2);
  3. 3. d1.push_back (1);
  4. 4. d1.push_back(3);
  5. 5. d1.push_back(0);
  6. 6. auto a=d1.begin ();
  7. 7. auto b=d1.end ();
  8. 8. std::sort(a,b);
  9. 9.

10. 10. std::for_each (a,b,[](int i)

11. 11. {

12. 12. std::cout << i << std::endl;

13. 13. });

1.std::deque<int> d1;

2.    d1.push_back (2);

3.    d1.push_back (1);

4.    d1.push_back(3);

5.    d1.push_back(0);

6.    auto a=d1.begin ();

7.    auto b=d1.end ();

8.    std::sort(a,b);

9.   

10.    std::for_each (a,b,[](int i)

11.    {

12.            std::cout << i << std::endl;

13.    });

Lambda 这里开始用两个字符[]以指示我们捕获语法相对于没有本地变量[] 或 [=]或我们捕获所有的本地变量由引用或备份分别。做不捕获任何变量是 lambda 说是无限定的。

现在,这种类型的 lambda 隐式转换为函数指针,换句话说,成功调用旧的 Win32 API。

此处的示例与CreateThreadpoolWorkAPI,指向函数的指针参数 1,键入PTP_WORK_CALLBACK ,比原来的 lambda明显更好一些。

  1. 1.PTP_POOL pool=CreateThreadpool(NULL);
  2. 2. TP_CALLBACK_ENVIRON cbEnviron;
  3. 3. InitializeThreadpoolEnvironment(&cbEnviron);
  4. 4. SetThreadpoolThreadMaximum (pool,4);
  5. 5. BOOL bRet=SetThreadpoolThreadMinimum (pool,2);
  6. 6.
  7. 7.
  8. 8. PTP_WORK work=CreateThreadpoolWork([]( PTP_CALLBACK_INSTANCE Instance,PVOID Context,PTP_WORK Work)
  9. 9. {

10. 10.

11. 11. wprintf(L"Fait du boulot\n");

12. 12. },NULL,&cbEnviron);

13. 13. SubmitThreadpoolWork(work);

14. 14. WaitForThreadpoolWorkCallbacks(work,FALSE);

15. 15. CloseThreadpoolWork(work);

16. 16. CloseThreadpool(pool);

1.PTP_POOL pool=CreateThreadpool(NULL);

2.    TP_CALLBACK_ENVIRON cbEnviron;

3.    InitializeThreadpoolEnvironment(&cbEnviron);

4.    SetThreadpoolThreadMaximum (pool,4);   

5.    BOOL bRet=SetThreadpoolThreadMinimum (pool,2);

6.   

7.   

8.    PTP_WORK work=CreateThreadpoolWork([]( PTP_CALLBACK_INSTANCE Instance,PVOID Context,PTP_WORK Work)

9.    {

10.       

11.        wprintf(L"Fait du boulot\n");

12.    },NULL,&cbEnviron);

13.    SubmitThreadpoolWork(work);   

14.    WaitForThreadpoolWorkCallbacks(work,FALSE);

15.    CloseThreadpoolWork(work);

16.    CloseThreadpool(pool);

另一个示例 API EnumWindows,或我们可以对"现代 c + +代码"混合使用旧 API 调用

  1. 1.BOOL ret=EnumWindows ([](HWND hwnd,LPARAM lParam)->BOOL
  2. 2. {
  3. 3. const size_t MAX_SIZE=2048;
  4. 4. LPWSTR title=static_cast<LPWSTR>(_malloca(MAX_SIZE));
  5. 5. if (title!=nullptr)
  6. 6. {
  7. 7. ZeroMemory (title,MAX_SIZE);
  8. 8. if (GetWindowTextLength (hwnd) >0)
  9. 9. {

10. 10. GetWindowTextW (hwnd,title,MAX_SIZE);

11. 11. wprintf(L"%ls\n",title);

12. 12. _freea(title);

13. 13. }

14. 14. }

15. 15.

16. 16. return TRUE;

17. 17. },0);

  1. 在编写多线程程序时,多个线程同时访问某个共享资源,会导致同步的问题,这篇文章中我们将介绍 C++11 多线程编程中的数据保护。
  2. 19.  数据丢失
  3. 让我们从一个简单的例子开始,请看如下代码:
  4.  

01

#include <iostream>

02

#include <string>

03

#include <thread>

04

#include <vector>

05

 

06

using std::thread;

07

using std::vector;

08

using std::cout;

09

using std::endl;

10

 

11

class Incrementer

12

{

13

private:

14

int counter;

15

 

16

public:

17

Incrementer() : counter{0} { };

18

 

19

void operator()()

20

{

21

for(int i = 0; i < 100000; i++)

22

{

23

this->counter++;

24

}

25

}

26

 

27

int getCounter() const

28

{

29

return this->counter;

30

}

31

};

32

 

33

int main()

34

{

35

// Create the threads which will each do some counting

36

vector<thread> threads;

37

 

38

Incrementer counter;

39

 

40

threads.push_back(thread(std::ref(counter)));

41

threads.push_back(thread(std::ref(counter)));

42

threads.push_back(thread(std::ref(counter)));

43

 

44

for(auto &t : threads)

45

{

46

t.join();

47

}

48

 

49

cout << counter.getCounter() << endl;

50

 

51

return 0;

52

}

  1. 这个程序的目的就是数数,数到30万,某些傻叉程序员想要优化数数的过程,因此创建了三个线程,使用一个共享变量 counter,每个线程负责给这个变量增加10万计数。
  2. 这段代码创建了一个名为 Incrementer 的类,该类包含一个私有变量 counter,其构造器非常简单,只是将 counter 设置为 0.
  3. 紧接着是一个操作符重载,这意味着这个类的每个实例都是被当作一个简单函数来调用的。一般我们调用类的某个方法时会这样 object.fooMethod(),但现在你实际上是直接调用了对象,如 object(). 因为我们是在操作符重载函数中将整个对象传递给了线程类。最后是一个 getCounter 方法,返回 counter 变量的值。
  4. 再下来是程序的入口函数 main(),我们创建了三个线程,不过只创建了一个 Incrementer 类的实例,然后将这个实例传递给三个线程,注意这里使用了 std::ref ,这相当于是传递了实例的引用对象,而不是对象的拷贝。
  5. 现在让我们来看看程序执行的结果,如果这位傻叉程序员还够聪明的话,他会使用 GCC 4.7 或者更新版本,或者是 Clang 3.1 来进行编译,编译方法:

1

g++ -std=c++11 -lpthread -o threading_example main.cpp

  1. 运行结果:
  2.  

01

[lucas@lucas-desktop src]$ ./threading_example

02

218141

03

[lucas@lucas-desktop src]$ ./threading_example

04

208079

05

[lucas@lucas-desktop src]$ ./threading_example

06

100000

07

[lucas@lucas-desktop src]$ ./threading_example

08

202426

09

[lucas@lucas-desktop src]$ ./threading_example

10

172209

  1. 但等等,不对啊,程序并没有数数到30万,有一次居然只数到10万,为什么会这样呢?好吧,加1操作对应实际的处理器指令其实包括:

1

movl counter(%rip), %eax

2

addl $1, %eax

3

movl %eax, counter(%rip)

  1. 首个指令将装载 counter 的值到 %eax 寄存器,紧接着寄存器的值增1,然后将寄存器的值移给内存中 counter 所在的地址。
  2. 我听到你在嘀咕:这不错,可为什么会导致数数错误的问题呢?嗯,还记得我们以前说过线程会共享处理器,因为只有单核。因此在某些点上,一个线程会依照指令执行完成,但在很多情况下,操作系统会对线程说:时间结束了,到后面排队再来,然后另外一个线程开始执行,当下一个线程开始执行时,它会从被暂停的那个位置开始执行。所以你猜会发生什么事,当前线程正准备执行寄存器加1操作时,系统把处理器交给另外一个线程?
  3. 我真的不知道会发生什么事,可能我们在准备加1时,另外一个线程进来了,重新将 counter 值加载到寄存器等多种情况的产生。谁也不知道到底发生了什么。
  4. 63.  正确的做法
  5. 解决方案就是要求同一个时间内只允许一个线程访问共享变量。这个可通过 类来解决。当线程进入时,加锁、执行操作,然后释放锁。其他线程想要访问这个共享资源必须等待锁释放。
  6. 互斥(mutex) 是操作系统确保锁和解锁操作是不可分割的。这意味着线程在对互斥量进行锁和解锁的操作是不会被中断的。当线程对互斥量进行锁或者解锁时,该操作会在操作系统切换线程前完成。
  7. 而最好的事情是,当你试图对互斥量进行加锁操作时,其他的线程已经锁住了该互斥量,那你就必须等待直到其释放。操作系统会跟踪哪个线程正在等待哪个互斥量,被堵塞的线程会进入 "blocked on m" 状态,意味着操作系统不会给这个堵塞的线程任何处理器时间,直到互斥量解锁,因此也不会浪费 CPU 的循环。如果有多个线程处于等待状态,哪个线程最先获得资源取决于操作系统本身,一般像 Windows 和 Linux 系统使用的是 FIFO 策略,在实时操作系统中则是基于优先级的。
  8. 现在让我们对上面的代码进行改进:

01

#include <iostream>

02

#include <string>

03

#include <thread>

04

#include <vector>

05

#include <mutex>

06

 

07

using std::thread;

08

using std::vector;

09

using std::cout;

10

using std::endl;

11

using std::mutex;

12

 

13

class Incrementer

14

{

15

private:

16

int counter;

17

mutex m;

18

 

19

public:

20

Incrementer() : counter{0} { };

21

 

22

void operator()()

23

{

24

for(int i = 0; i < 100000; i++)

25

{

26

this->m.lock();

27

this->counter++;

28

this->m.unlock();

29

}

30

}

31

 

32

int getCounter() const

33

{

34

return this->counter;

35

}

36

};

37

 

38

int main()

39

{

40

// Create the threads which will each do some counting

41

vector<thread> threads;

42

 

43

Incrementer counter;

44

 

45

threads.push_back(thread(std::ref(counter)));

46

threads.push_back(thread(std::ref(counter)));

47

threads.push_back(thread(std::ref(counter)));

48

 

49

for(auto &t : threads)

50

{

51

t.join();

52

}

53

 

54

cout << counter.getCounter() << endl;

55

 

56

return 0;

57

}

  1. 注意代码上的变化:我们引入了 mutex 头文件,增加了一个 m 的成员,类型是 mutex,在 operator()() 中我们锁住互斥量 m 然后对 counter 进行加1操作,然后释放互斥量。
  2. 再次执行上述程序,结果如下:

1

[lucas@lucas-desktop src]$ ./threading_example

2

300000

3

[lucas@lucas-desktop src]$ ./threading_example

4

300000

  1. 这下数对了。不过在计算机科学中,没有免费的午餐,使用互斥量会降低程序的性能,但这总比一个错误的程序要强吧。
  2. 100.  防范异常
  3. 当对变量进行加1操作时,是可能会发生异常的,当然在我们这个例子中发生异常的机会微乎其微,但是在一些复杂系统中是极有可能的。上面的代码并不是异常安全的,当异常发生时,程序已经结束了,可是互斥量还是处于锁的状态。
  4. 为了确保互斥量在异常发生的情况下也能被解锁,我们需要使用如下代码:

01

for(int i = 0; i < 100000; i++)

02

{

03

this->m.lock();

04

try

05

{

06

this->counter++;

07

this->m.unlock();

08

}

09

catch(...)

10

{

11

this->m.unlock();

12

throw;

13

}

14

}

  1. 但是,这代码太多了,而只是为了对互斥量进行加锁和解锁。没关系,我知道你很懒,因此推荐个更简单的单行代码解决方法,就是使用 std::lock_guard 类。这个类在创建时就锁定了 mutex 对象,然后在结束时释放。

继续修改代码:

01

void operator()()

02

{

03

for(int i = 0; i < 100000; i++)

04

{

05

lock_guard<mutex> lock(this->m);

06

 

07

// The lock has been created now, and immediatly locks the mutex

08

this->counter++;

09

 

10

// This is the end of the for-loop scope, and the lock will be

11

// destroyed, and in the destructor of the lock, it will

12

// unlock the mutex

13

}

14

}

上面代码已然是异常安全了,因为当异常发生时,将会调用 lock 对象的析构函数,然后自动进行互斥量的解锁。

  1. 记住,请使用放下代码模板来编写:

01

void long_function()

02

{

03

// some long code

04

 

05

// Just a pair of curly braces

06

{

07

// Temp scope, create lock

08

lock_guard<mutex> lock(this->m);

09

 

10

// do some stuff

11

 

12

// Close the scope, so the guard will unlock the mutex

13

}

14

}

 

转载于:https://www.cnblogs.com/codeword/archive/2012/10/23/2735283.html

你可能感兴趣的文章
10个让你忘记 Flash 的 HTML5 应用演示
查看>>
8个Python面试必考的题目,小编也被坑过 ToT
查看>>
SQL Server 使用作业设置定时任务之一(转载)
查看>>
centos 图形界面和命令行界面切换(转载)
查看>>
Maven启用代理访问
查看>>
Primary definition
查看>>
第二阶段冲刺-01
查看>>
BZOJ1045 HAOI2008 糖果传递
查看>>
发送请求时params和data的区别
查看>>
JavaScript 克隆数组
查看>>
eggs
查看>>
一步步学习微软InfoPath2010和SP2010--第七章节--从SP列表和业务数据连接接收数据(4)--外部项目选取器和业务数据连接...
查看>>
如何增强你的SharePoint 团队网站首页
查看>>
FZU 1914 Funny Positive Sequence(线性算法)
查看>>
oracle 报错ORA-12514: TNS:listener does not currently know of service requested in connec
查看>>
基于grunt构建的前端集成开发环境
查看>>
MySQL服务读取参数文件my.cnf的规律研究探索
查看>>
java string(转)
查看>>
__all__有趣的属性
查看>>
BZOJ 5180 [Baltic2016]Cities(斯坦纳树)
查看>>