From 08b3945e843cd079bac1df9c691dd717d032830b Mon Sep 17 00:00:00 2001 From: Fydui Date: Wed, 24 May 2017 23:57:27 +0800 Subject: [PATCH] Site updated: 2017-05-24 23:57:25 --- .../index.html" | 6 +- .../index.html" | 6 +- .../index.html" | 6 +- .../index.html" | 6 +- .../index.html" | 6 +- .../index.html" | 6 +- .../index.html" | 6 +- .../index.html" | 6 +- .../index.html" | 6 +- .../index.html" | 6 +- .../index.html" | 6 +- 2016/12/27/hello-world/index.html | 6 +- .../index.html" | 6 +- .../index.html" | 6 +- .../index.html" | 6 +- .../index.html" | 6 +- .../index.html" | 6 +- .../index.html" | 6 +- .../index.html" | 6 +- .../index.html" | 6 +- .../index.html" | 6 +- .../index.html" | 6 +- .../index.html" | 6 +- .../index.html" | 6 +- .../index.html" | 6 +- .../index.html" | 6 +- .../index.html" | 6 +- .../index.html" | 6 +- .../index.html" | 6 +- .../index.html" | 6 +- 2017/05/09/README/index.html | 6 +- .../index.html" | 6 +- .../index.html" | 6 +- .../index.html" | 6 +- .../index.html" | 15 +- .../index.html" | 550 ++++++++++++++++++ archives/2016/12/index.html | 6 +- archives/2016/12/page/2/index.html | 6 +- archives/2016/index.html | 6 +- archives/2016/page/2/index.html | 6 +- archives/2017/01/index.html | 6 +- archives/2017/03/index.html | 6 +- archives/2017/04/index.html | 6 +- archives/2017/05/index.html | 38 +- archives/2017/index.html | 70 +-- archives/2017/page/2/index.html | 66 ++- archives/2017/page/3/index.html | 38 +- archives/index.html | 70 +-- archives/page/2/index.html | 66 ++- archives/page/3/index.html | 70 +-- archives/page/4/index.html | 38 +- content.json | 2 +- index.html | 117 ++-- page/2/index.html | 115 ++-- page/3/index.html | 114 ++-- page/4/index.html | 60 +- tags/CPP/page/2/index.html | 6 +- tags/CPP/page/3/index.html | 6 +- tags/CPP/page/4/index.html | 6 +- tags/Cpp/index.html | 6 +- tags/Maya/index.html | 428 ++++++++++++++ tags/QML/index.html | 6 +- tags/Qt/index.html | 6 +- "tags/\344\273\243\347\240\201/index.html" | 6 +- .../page/2/index.html" | 6 +- .../page/3/index.html" | 6 +- .../page/4/index.html" | 6 +- "tags/\347\254\224\350\256\260/index.html" | 6 +- .../page/2/index.html" | 6 +- .../page/3/index.html" | 6 +- .../page/4/index.html" | 6 +- "tags/\351\232\217\347\254\224/index.html" | 38 +- 72 files changed, 1836 insertions(+), 389 deletions(-) create mode 100644 "2017/05/24/\345\246\202\344\275\225\347\250\215\345\276\256\346\234\211\347\202\271\347\221\225\347\226\265\347\232\204\346\212\2123D\345\256\232\345\210\266\345\245\263\344\273\2062\347\232\204\346\250\241\345\236\213\345\257\274\345\205\245\345\210\260Maya2017/index.html" create mode 100644 tags/Maya/index.html diff --git "a/2016/12/05/QT-QML\344\270\216C++\344\272\244\344\272\222/index.html" "b/2016/12/05/QT-QML\344\270\216C++\344\272\244\344\272\222/index.html" index 3f600b9..2b91ef2 100644 --- "a/2016/12/05/QT-QML\344\270\216C++\344\272\244\344\272\222/index.html" +++ "b/2016/12/05/QT-QML\344\270\216C++\344\272\244\344\272\222/index.html" @@ -390,6 +390,10 @@

CPP + + @@ -399,7 +403,7 @@

- 随笔 + Maya
diff --git "a/2016/12/07/C++\347\254\224\350\256\260-\345\244\232\346\200\201\345\205\254\346\234\211\347\273\247\346\211\277/index.html" "b/2016/12/07/C++\347\254\224\350\256\260-\345\244\232\346\200\201\345\205\254\346\234\211\347\273\247\346\211\277/index.html" index aaf15fb..ebeea18 100644 --- "a/2016/12/07/C++\347\254\224\350\256\260-\345\244\232\346\200\201\345\205\254\346\234\211\347\273\247\346\211\277/index.html" +++ "b/2016/12/07/C++\347\254\224\350\256\260-\345\244\232\346\200\201\345\205\254\346\234\211\347\273\247\346\211\277/index.html" @@ -372,6 +372,10 @@

CPP + + @@ -381,7 +385,7 @@

diff --git "a/2016/12/07/C++\347\254\224\350\256\260-\347\261\273\345\222\214\345\212\250\346\200\201\345\206\205\345\255\230\345\210\206\351\205\215/index.html" "b/2016/12/07/C++\347\254\224\350\256\260-\347\261\273\345\222\214\345\212\250\346\200\201\345\206\205\345\255\230\345\210\206\351\205\215/index.html" index 7a1cbda..1c55281 100644 --- "a/2016/12/07/C++\347\254\224\350\256\260-\347\261\273\345\222\214\345\212\250\346\200\201\345\206\205\345\255\230\345\210\206\351\205\215/index.html" +++ "b/2016/12/07/C++\347\254\224\350\256\260-\347\261\273\345\222\214\345\212\250\346\200\201\345\206\205\345\255\230\345\210\206\351\205\215/index.html" @@ -373,6 +373,10 @@

CPP + + @@ -382,7 +386,7 @@

diff --git "a/2016/12/08/C++\347\254\224\350\256\260-\344\270\200\344\270\252\347\256\200\345\215\225\347\232\204\345\237\272\347\261\273/index.html" "b/2016/12/08/C++\347\254\224\350\256\260-\344\270\200\344\270\252\347\256\200\345\215\225\347\232\204\345\237\272\347\261\273/index.html" index 517cf61..62350b8 100644 --- "a/2016/12/08/C++\347\254\224\350\256\260-\344\270\200\344\270\252\347\256\200\345\215\225\347\232\204\345\237\272\347\261\273/index.html" +++ "b/2016/12/08/C++\347\254\224\350\256\260-\344\270\200\344\270\252\347\256\200\345\215\225\347\232\204\345\237\272\347\261\273/index.html" @@ -375,6 +375,10 @@

CPP + + @@ -384,7 +388,7 @@

- 随笔 + Maya
diff --git "a/2016/12/11/C++\347\254\224\350\256\260-\351\235\231\346\200\201\350\201\224\347\274\226\345\222\214\345\212\250\346\200\201\350\201\224\347\274\226/index.html" "b/2016/12/11/C++\347\254\224\350\256\260-\351\235\231\346\200\201\350\201\224\347\274\226\345\222\214\345\212\250\346\200\201\350\201\224\347\274\226/index.html" index 1bc678e..9e2bb15 100644 --- "a/2016/12/11/C++\347\254\224\350\256\260-\351\235\231\346\200\201\350\201\224\347\274\226\345\222\214\345\212\250\346\200\201\350\201\224\347\274\226/index.html" +++ "b/2016/12/11/C++\347\254\224\350\256\260-\351\235\231\346\200\201\350\201\224\347\274\226\345\222\214\345\212\250\346\200\201\350\201\224\347\274\226/index.html" @@ -385,6 +385,10 @@

CPP + + @@ -394,7 +398,7 @@

- 随笔 + Maya
diff --git "a/2016/12/13/C++\347\254\224\350\256\260-\350\256\277\351\227\256\346\216\247\345\210\266protected/index.html" "b/2016/12/13/C++\347\254\224\350\256\260-\350\256\277\351\227\256\346\216\247\345\210\266protected/index.html" index fafa376..cac322b 100644 --- "a/2016/12/13/C++\347\254\224\350\256\260-\350\256\277\351\227\256\346\216\247\345\210\266protected/index.html" +++ "b/2016/12/13/C++\347\254\224\350\256\260-\350\256\277\351\227\256\346\216\247\345\210\266protected/index.html" @@ -371,6 +371,10 @@

CPP + + @@ -380,7 +384,7 @@

- 随笔 + Maya
diff --git "a/2016/12/14/C++\347\254\224\350\256\260-\346\212\275\350\261\241\345\237\272\347\261\273(Abstract-Base-Class,ABC)/index.html" "b/2016/12/14/C++\347\254\224\350\256\260-\346\212\275\350\261\241\345\237\272\347\261\273(Abstract-Base-Class,ABC)/index.html" index fb9b71d..6518a9d 100644 --- "a/2016/12/14/C++\347\254\224\350\256\260-\346\212\275\350\261\241\345\237\272\347\261\273(Abstract-Base-Class,ABC)/index.html" +++ "b/2016/12/14/C++\347\254\224\350\256\260-\346\212\275\350\261\241\345\237\272\347\261\273(Abstract-Base-Class,ABC)/index.html" @@ -377,6 +377,10 @@

总之: CPP + + @@ -386,7 +390,7 @@

总之:
diff --git "a/2016/12/15/C++\347\254\224\350\256\260-\347\273\247\346\211\277\345\222\214\345\212\250\346\200\201\345\206\205\345\255\230\345\210\206\351\205\215/index.html" "b/2016/12/15/C++\347\254\224\350\256\260-\347\273\247\346\211\277\345\222\214\345\212\250\346\200\201\345\206\205\345\255\230\345\210\206\351\205\215/index.html" index 20338a6..98a8d1c 100644 --- "a/2016/12/15/C++\347\254\224\350\256\260-\347\273\247\346\211\277\345\222\214\345\212\250\346\200\201\345\206\205\345\255\230\345\210\206\351\205\215/index.html" +++ "b/2016/12/15/C++\347\254\224\350\256\260-\347\273\247\346\211\277\345\222\214\345\212\250\346\200\201\345\206\205\345\255\230\345\210\206\351\205\215/index.html" @@ -383,6 +383,10 @@

CPP + + @@ -392,7 +396,7 @@

- 随笔 + Maya
diff --git "a/2016/12/21/C++\347\254\224\350\256\260-\347\261\273\350\256\276\350\256\241\346\200\273\347\273\223/index.html" "b/2016/12/21/C++\347\254\224\350\256\260-\347\261\273\350\256\276\350\256\241\346\200\273\347\273\223/index.html" index 205c4bd..c16cf64 100644 --- "a/2016/12/21/C++\347\254\224\350\256\260-\347\261\273\350\256\276\350\256\241\346\200\273\347\273\223/index.html" +++ "b/2016/12/21/C++\347\254\224\350\256\260-\347\261\273\350\256\276\350\256\241\346\200\273\347\273\223/index.html" @@ -397,6 +397,10 @@

CPP + + @@ -406,7 +410,7 @@

- 随笔 + Maya
diff --git "a/2016/12/23/C++\347\254\224\350\256\260-\345\214\205\345\220\253\347\261\273\345\257\271\350\261\241\347\232\204\346\210\220\345\221\230/index.html" "b/2016/12/23/C++\347\254\224\350\256\260-\345\214\205\345\220\253\347\261\273\345\257\271\350\261\241\347\232\204\346\210\220\345\221\230/index.html" index 9dc84e1..4c7740e 100644 --- "a/2016/12/23/C++\347\254\224\350\256\260-\345\214\205\345\220\253\347\261\273\345\257\271\350\261\241\347\232\204\346\210\220\345\221\230/index.html" +++ "b/2016/12/23/C++\347\254\224\350\256\260-\345\214\205\345\220\253\347\261\273\345\257\271\350\261\241\347\232\204\346\210\220\345\221\230/index.html" @@ -375,6 +375,10 @@

CPP + + @@ -384,7 +388,7 @@

- 随笔 + Maya
diff --git "a/2016/12/24/C++\347\254\224\350\256\260-\347\247\201\346\234\211\347\273\247\346\211\277/index.html" "b/2016/12/24/C++\347\254\224\350\256\260-\347\247\201\346\234\211\347\273\247\346\211\277/index.html" index 399695d..37b90d2 100644 --- "a/2016/12/24/C++\347\254\224\350\256\260-\347\247\201\346\234\211\347\273\247\346\211\277/index.html" +++ "b/2016/12/24/C++\347\254\224\350\256\260-\347\247\201\346\234\211\347\273\247\346\211\277/index.html" @@ -380,6 +380,10 @@

CPP + + @@ -389,7 +393,7 @@

- 随笔 + Maya
diff --git a/2016/12/27/hello-world/index.html b/2016/12/27/hello-world/index.html index a9cd069..98e2ad2 100644 --- a/2016/12/27/hello-world/index.html +++ b/2016/12/27/hello-world/index.html @@ -378,6 +378,10 @@

CPP + + @@ -387,7 +391,7 @@

- 随笔 + Maya
diff --git "a/2016/12/30/C++\347\254\224\350\256\260-\345\244\232\351\207\215\347\273\247\346\211\277/index.html" "b/2016/12/30/C++\347\254\224\350\256\260-\345\244\232\351\207\215\347\273\247\346\211\277/index.html" index 733f482..524607c 100644 --- "a/2016/12/30/C++\347\254\224\350\256\260-\345\244\232\351\207\215\347\273\247\346\211\277/index.html" +++ "b/2016/12/30/C++\347\254\224\350\256\260-\345\244\232\351\207\215\347\273\247\346\211\277/index.html" @@ -385,6 +385,10 @@

CPP + + @@ -394,7 +398,7 @@

- 随笔 + Maya
diff --git "a/2017/01/06/C++\347\254\224\350\256\260-\346\250\241\346\235\277\347\232\204\345\205\267\344\275\223\345\214\226/index.html" "b/2017/01/06/C++\347\254\224\350\256\260-\346\250\241\346\235\277\347\232\204\345\205\267\344\275\223\345\214\226/index.html" index d0c5848..fe763fb 100644 --- "a/2017/01/06/C++\347\254\224\350\256\260-\346\250\241\346\235\277\347\232\204\345\205\267\344\275\223\345\214\226/index.html" +++ "b/2017/01/06/C++\347\254\224\350\256\260-\346\250\241\346\235\277\347\232\204\345\205\267\344\275\223\345\214\226/index.html" @@ -384,6 +384,10 @@

CPP + + @@ -393,7 +397,7 @@

- 随笔 + Maya
diff --git "a/2017/01/08/C++\347\254\224\350\256\260-\346\210\220\345\221\230\346\250\241\346\235\277&\345\260\206\346\250\241\346\235\277\347\224\250\344\275\234\345\217\202\346\225\260/index.html" "b/2017/01/08/C++\347\254\224\350\256\260-\346\210\220\345\221\230\346\250\241\346\235\277&\345\260\206\346\250\241\346\235\277\347\224\250\344\275\234\345\217\202\346\225\260/index.html" index 2772984..44068de 100644 --- "a/2017/01/08/C++\347\254\224\350\256\260-\346\210\220\345\221\230\346\250\241\346\235\277&\345\260\206\346\250\241\346\235\277\347\224\250\344\275\234\345\217\202\346\225\260/index.html" +++ "b/2017/01/08/C++\347\254\224\350\256\260-\346\210\220\345\221\230\346\250\241\346\235\277&\345\260\206\346\250\241\346\235\277\347\224\250\344\275\234\345\217\202\346\225\260/index.html" @@ -384,6 +384,10 @@

CPP + + @@ -393,7 +397,7 @@

- 随笔 + Maya
diff --git "a/2017/03/04/C++\347\254\224\350\256\260-\345\265\214\345\245\227\347\261\273/index.html" "b/2017/03/04/C++\347\254\224\350\256\260-\345\265\214\345\245\227\347\261\273/index.html" index 0ef75f5..a0f4d09 100644 --- "a/2017/03/04/C++\347\254\224\350\256\260-\345\265\214\345\245\227\347\261\273/index.html" +++ "b/2017/03/04/C++\347\254\224\350\256\260-\345\265\214\345\245\227\347\261\273/index.html" @@ -385,6 +385,10 @@

CPP + + @@ -394,7 +398,7 @@

- 随笔 + Maya
diff --git "a/2017/03/05/C++\347\254\224\350\256\260-\345\217\213\345\205\203\347\261\273/index.html" "b/2017/03/05/C++\347\254\224\350\256\260-\345\217\213\345\205\203\347\261\273/index.html" index 13720a1..90e85b3 100644 --- "a/2017/03/05/C++\347\254\224\350\256\260-\345\217\213\345\205\203\347\261\273/index.html" +++ "b/2017/03/05/C++\347\254\224\350\256\260-\345\217\213\345\205\203\347\261\273/index.html" @@ -386,6 +386,10 @@

CPP + + @@ -395,7 +399,7 @@

- 随笔 + Maya
diff --git "a/2017/03/07/C++\347\254\224\350\256\260-\345\274\202\345\270\270/index.html" "b/2017/03/07/C++\347\254\224\350\256\260-\345\274\202\345\270\270/index.html" index a5a97e5..2a3570a 100644 --- "a/2017/03/07/C++\347\254\224\350\256\260-\345\274\202\345\270\270/index.html" +++ "b/2017/03/07/C++\347\254\224\350\256\260-\345\274\202\345\270\270/index.html" @@ -406,6 +406,10 @@

CPP + + @@ -415,7 +419,7 @@

diff --git "a/2017/03/14/C++\347\254\224\350\256\260-\345\274\202\345\270\270(2)/index.html" "b/2017/03/14/C++\347\254\224\350\256\260-\345\274\202\345\270\270(2)/index.html" index a8fcc4c..b4f9be8 100644 --- "a/2017/03/14/C++\347\254\224\350\256\260-\345\274\202\345\270\270(2)/index.html" +++ "b/2017/03/14/C++\347\254\224\350\256\260-\345\274\202\345\270\270(2)/index.html" @@ -416,6 +416,10 @@

CPP + + @@ -425,7 +429,7 @@

- 随笔 + Maya
diff --git "a/2017/03/19/C++\347\254\224\350\256\260-\345\274\202\345\270\270(3)/index.html" "b/2017/03/19/C++\347\254\224\350\256\260-\345\274\202\345\270\270(3)/index.html" index 1ad08ff..d0f0f22 100644 --- "a/2017/03/19/C++\347\254\224\350\256\260-\345\274\202\345\270\270(3)/index.html" +++ "b/2017/03/19/C++\347\254\224\350\256\260-\345\274\202\345\270\270(3)/index.html" @@ -405,6 +405,10 @@

CPP + + @@ -414,7 +418,7 @@

diff --git "a/2017/03/20/C++\347\254\224\350\256\260-RTTI/index.html" "b/2017/03/20/C++\347\254\224\350\256\260-RTTI/index.html" index 711c5f0..a21cd44 100644 --- "a/2017/03/20/C++\347\254\224\350\256\260-RTTI/index.html" +++ "b/2017/03/20/C++\347\254\224\350\256\260-RTTI/index.html" @@ -395,6 +395,10 @@

CPP + + @@ -404,7 +408,7 @@

- 随笔 + Maya
diff --git "a/2017/03/23/C++\347\254\224\350\256\260-\347\261\273\345\236\213\350\275\254\346\215\242\350\277\220\347\256\227\347\254\246&\345\217\213\345\205\203,\345\274\202\345\270\270\347\232\204\346\200\273\347\273\223/index.html" "b/2017/03/23/C++\347\254\224\350\256\260-\347\261\273\345\236\213\350\275\254\346\215\242\350\277\220\347\256\227\347\254\246&\345\217\213\345\205\203,\345\274\202\345\270\270\347\232\204\346\200\273\347\273\223/index.html" index 93e40e6..0f1e1b1 100644 --- "a/2017/03/23/C++\347\254\224\350\256\260-\347\261\273\345\236\213\350\275\254\346\215\242\350\277\220\347\256\227\347\254\246&\345\217\213\345\205\203,\345\274\202\345\270\270\347\232\204\346\200\273\347\273\223/index.html" +++ "b/2017/03/23/C++\347\254\224\350\256\260-\347\261\273\345\236\213\350\275\254\346\215\242\350\277\220\347\256\227\347\254\246&\345\217\213\345\205\203,\345\274\202\345\270\270\347\232\204\346\200\273\347\273\223/index.html" @@ -391,6 +391,10 @@

CPP + + @@ -400,7 +404,7 @@

diff --git "a/2017/03/25/C++\347\254\224\350\256\260-string\347\261\273/index.html" "b/2017/03/25/C++\347\254\224\350\256\260-string\347\261\273/index.html" index 4f8c35a..dfb8477 100644 --- "a/2017/03/25/C++\347\254\224\350\256\260-string\347\261\273/index.html" +++ "b/2017/03/25/C++\347\254\224\350\256\260-string\347\261\273/index.html" @@ -399,6 +399,10 @@

CPP + + @@ -408,7 +412,7 @@

- 随笔 + Maya
diff --git "a/2017/03/26/C++\347\254\224\350\256\260-\346\231\272\350\203\275\346\214\207\351\222\210/index.html" "b/2017/03/26/C++\347\254\224\350\256\260-\346\231\272\350\203\275\346\214\207\351\222\210/index.html" index ea5092f..c578831 100644 --- "a/2017/03/26/C++\347\254\224\350\256\260-\346\231\272\350\203\275\346\214\207\351\222\210/index.html" +++ "b/2017/03/26/C++\347\254\224\350\256\260-\346\231\272\350\203\275\346\214\207\351\222\210/index.html" @@ -388,6 +388,10 @@

CPP + + @@ -397,7 +401,7 @@

diff --git "a/2017/03/28/C++\347\254\224\350\256\260-Vector/index.html" "b/2017/03/28/C++\347\254\224\350\256\260-Vector/index.html" index 3674ec3..1abd3d7 100644 --- "a/2017/03/28/C++\347\254\224\350\256\260-Vector/index.html" +++ "b/2017/03/28/C++\347\254\224\350\256\260-Vector/index.html" @@ -399,6 +399,10 @@

CPP + + @@ -408,7 +412,7 @@

- 随笔 + Maya
diff --git "a/2017/04/05/C++\347\254\224\350\256\260-\346\263\233\345\236\213\347\274\226\347\250\213/index.html" "b/2017/04/05/C++\347\254\224\350\256\260-\346\263\233\345\236\213\347\274\226\347\250\213/index.html" index eda7aed..7080521 100644 --- "a/2017/04/05/C++\347\254\224\350\256\260-\346\263\233\345\236\213\347\274\226\347\250\213/index.html" +++ "b/2017/04/05/C++\347\254\224\350\256\260-\346\263\233\345\236\213\347\274\226\347\250\213/index.html" @@ -427,6 +427,10 @@

CPP + + @@ -436,7 +440,7 @@

- 随笔 + Maya
diff --git "a/2017/04/11/C++\347\254\224\350\256\260-\346\263\233\345\236\213\347\274\226\347\250\213(\347\273\255/index.html" "b/2017/04/11/C++\347\254\224\350\256\260-\346\263\233\345\236\213\347\274\226\347\250\213(\347\273\255/index.html" index 572c7c0..6738b97 100644 --- "a/2017/04/11/C++\347\254\224\350\256\260-\346\263\233\345\236\213\347\274\226\347\250\213(\347\273\255/index.html" +++ "b/2017/04/11/C++\347\254\224\350\256\260-\346\263\233\345\236\213\347\274\226\347\250\213(\347\273\255/index.html" @@ -408,6 +408,10 @@

CPP + + @@ -417,7 +421,7 @@

diff --git "a/2017/04/17/C++\347\254\224\350\256\260-\345\207\275\346\225\260\345\257\271\350\261\241/index.html" "b/2017/04/17/C++\347\254\224\350\256\260-\345\207\275\346\225\260\345\257\271\350\261\241/index.html" index 305f778..a8192dd 100644 --- "a/2017/04/17/C++\347\254\224\350\256\260-\345\207\275\346\225\260\345\257\271\350\261\241/index.html" +++ "b/2017/04/17/C++\347\254\224\350\256\260-\345\207\275\346\225\260\345\257\271\350\261\241/index.html" @@ -393,6 +393,10 @@

CPP + + @@ -402,7 +406,7 @@

- 随笔 + Maya
diff --git "a/2017/04/21/C++\347\254\224\350\256\260-\347\256\227\346\263\225(STL)/index.html" "b/2017/04/21/C++\347\254\224\350\256\260-\347\256\227\346\263\225(STL)/index.html" index afcb822..e8fb714 100644 --- "a/2017/04/21/C++\347\254\224\350\256\260-\347\256\227\346\263\225(STL)/index.html" +++ "b/2017/04/21/C++\347\254\224\350\256\260-\347\256\227\346\263\225(STL)/index.html" @@ -390,6 +390,10 @@

CPP + + @@ -399,7 +403,7 @@

diff --git "a/2017/04/25/C++\347\254\224\350\256\260-\350\276\223\345\205\245,\350\276\223\345\207\272\345\222\214\346\226\207\344\273\266/index.html" "b/2017/04/25/C++\347\254\224\350\256\260-\350\276\223\345\205\245,\350\276\223\345\207\272\345\222\214\346\226\207\344\273\266/index.html" index e3b69ca..443f25d 100644 --- "a/2017/04/25/C++\347\254\224\350\256\260-\350\276\223\345\205\245,\350\276\223\345\207\272\345\222\214\346\226\207\344\273\266/index.html" +++ "b/2017/04/25/C++\347\254\224\350\256\260-\350\276\223\345\205\245,\350\276\223\345\207\272\345\222\214\346\226\207\344\273\266/index.html" @@ -396,6 +396,10 @@

CPP + + @@ -405,7 +409,7 @@

- 随笔 + Maya
diff --git a/2017/05/09/README/index.html b/2017/05/09/README/index.html index 96d37b4..2e33f5b 100644 --- a/2017/05/09/README/index.html +++ b/2017/05/09/README/index.html @@ -372,6 +372,10 @@

CPP + + @@ -381,7 +385,7 @@

diff --git "a/2017/05/09/\351\232\217\347\254\224-\347\217\212\347\221\232\346\265\267/index.html" "b/2017/05/09/\351\232\217\347\254\224-\347\217\212\347\221\232\346\265\267/index.html" index cd79300..a3fdbbe 100644 --- "a/2017/05/09/\351\232\217\347\254\224-\347\217\212\347\221\232\346\265\267/index.html" +++ "b/2017/05/09/\351\232\217\347\254\224-\347\217\212\347\221\232\346\265\267/index.html" @@ -374,6 +374,10 @@

CPP +

  • + 随笔 +
  • +
  • Qt
  • @@ -383,7 +387,7 @@

  • - 随笔 + Maya
  • diff --git "a/2017/05/11/C++\347\254\224\350\256\260-\350\276\223\345\205\245,\350\276\223\345\207\272\345\222\214\346\226\207\344\273\266(\344\272\214)/index.html" "b/2017/05/11/C++\347\254\224\350\256\260-\350\276\223\345\205\245,\350\276\223\345\207\272\345\222\214\346\226\207\344\273\266(\344\272\214)/index.html" index 11ff033..102c320 100644 --- "a/2017/05/11/C++\347\254\224\350\256\260-\350\276\223\345\205\245,\350\276\223\345\207\272\345\222\214\346\226\207\344\273\266(\344\272\214)/index.html" +++ "b/2017/05/11/C++\347\254\224\350\256\260-\350\276\223\345\205\245,\350\276\223\345\207\272\345\222\214\346\226\207\344\273\266(\344\272\214)/index.html" @@ -387,6 +387,10 @@

    CPP + + @@ -396,7 +400,7 @@

    diff --git "a/2017/05/16/C++\347\254\224\350\256\260-\350\276\223\345\205\245,\350\276\223\345\207\272\345\222\214\346\226\207\344\273\266(\344\270\211)/index.html" "b/2017/05/16/C++\347\254\224\350\256\260-\350\276\223\345\205\245,\350\276\223\345\207\272\345\222\214\346\226\207\344\273\266(\344\270\211)/index.html" index 4af3a39..2567302 100644 --- "a/2017/05/16/C++\347\254\224\350\256\260-\350\276\223\345\205\245,\350\276\223\345\207\272\345\222\214\346\226\207\344\273\266(\344\270\211)/index.html" +++ "b/2017/05/16/C++\347\254\224\350\256\260-\350\276\223\345\205\245,\350\276\223\345\207\272\345\222\214\346\226\207\344\273\266(\344\270\211)/index.html" @@ -381,6 +381,10 @@

    CPP + + @@ -390,7 +394,7 @@

    diff --git "a/2017/05/17/C++\347\254\224\350\256\260-C++11\347\232\204\344\270\200\344\272\233\346\226\260\346\240\207\345\207\206/index.html" "b/2017/05/17/C++\347\254\224\350\256\260-C++11\347\232\204\344\270\200\344\272\233\346\226\260\346\240\207\345\207\206/index.html" index 8655e48..ee09498 100644 --- "a/2017/05/17/C++\347\254\224\350\256\260-C++11\347\232\204\344\270\200\344\272\233\346\226\260\346\240\207\345\207\206/index.html" +++ "b/2017/05/17/C++\347\254\224\350\256\260-C++11\347\232\204\344\270\200\344\272\233\346\226\260\346\240\207\345\207\206/index.html" @@ -282,6 +282,15 @@

    + + +
    + + 如何稍微有点瑕疵的把3D定制女仆2的模型导入到Maya2017 + +
    +
    +
    C++笔记-输入,输出和文件(三)
    @@ -381,6 +390,10 @@

    CPP + + @@ -390,7 +403,7 @@

    - 随笔 + Maya
    diff --git "a/2017/05/24/\345\246\202\344\275\225\347\250\215\345\276\256\346\234\211\347\202\271\347\221\225\347\226\265\347\232\204\346\212\2123D\345\256\232\345\210\266\345\245\263\344\273\2062\347\232\204\346\250\241\345\236\213\345\257\274\345\205\245\345\210\260Maya2017/index.html" "b/2017/05/24/\345\246\202\344\275\225\347\250\215\345\276\256\346\234\211\347\202\271\347\221\225\347\226\265\347\232\204\346\212\2123D\345\256\232\345\210\266\345\245\263\344\273\2062\347\232\204\346\250\241\345\236\213\345\257\274\345\205\245\345\210\260Maya2017/index.html" new file mode 100644 index 0000000..b018dd8 --- /dev/null +++ "b/2017/05/24/\345\246\202\344\275\225\347\250\215\345\276\256\346\234\211\347\202\271\347\221\225\347\226\265\347\232\204\346\212\2123D\345\256\232\345\210\266\345\245\263\344\273\2062\347\232\204\346\250\241\345\236\213\345\257\274\345\205\245\345\210\260Maya2017/index.html" @@ -0,0 +1,550 @@ + + + + + + + + + 如何稍微有点瑕疵的把3D定制女仆2的模型导入到Maya2017 | 果冻の随笔 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    +
    +
    + +
    + +
    +
    + + +
    + +
    +
    +
    + +
    +
    +
    + + + + + + +
    +
    + +
    +
    + + + +
    +
    +

    tag:

    + + +
    +
      +

      + 缺失模块。
      1、在博客根目录(注意不是yilia根目录)执行以下命令:
      npm i hexo-generator-json-content --save

      + 2、在根目录_config.yml里添加配置: +

      +  jsonContent:
      +    meta: false
      +    pages: false
      +    posts:
      +      title: true
      +      date: true
      +      path: true
      +      text: true
      +      raw: false
      +      content: false
      +      slug: false
      +      updated: false
      +      comments: false
      +      link: false
      +      permalink: false
      +      excerpt: false
      +      categories: false
      +      tags: true
      +
      +

      +
    • + {{ item.title }} +

      + + {{ item.date|dateformat }} +

      +

      + + #{{ tag.name }} +

      +
    • +
    +
    + + + +
    + + + +
    + + + +
    + +
    高中弱鸡学渣,低级码畜,Qt & C++养成中.想要学画画 技能树多线程划水中,脑回路清奇,反射弧可绕太阳系一圈. 懒癌晚期,绝赞抗癌中
    + +
    + +
    + +
    + + +
    + + \ No newline at end of file diff --git a/archives/2016/12/index.html b/archives/2016/12/index.html index eb4c2b1..c5b5fb0 100644 --- a/archives/2016/12/index.html +++ b/archives/2016/12/index.html @@ -541,6 +541,10 @@

    CPP + + @@ -550,7 +554,7 @@

    diff --git a/archives/2016/12/page/2/index.html b/archives/2016/12/page/2/index.html index b34f6a9..634181d 100644 --- a/archives/2016/12/page/2/index.html +++ b/archives/2016/12/page/2/index.html @@ -322,6 +322,10 @@

    CPP + + @@ -331,7 +335,7 @@

    diff --git a/archives/2016/index.html b/archives/2016/index.html index 531eec4..481f0c9 100644 --- a/archives/2016/index.html +++ b/archives/2016/index.html @@ -541,6 +541,10 @@

    CPP + + @@ -550,7 +554,7 @@

    diff --git a/archives/2016/page/2/index.html b/archives/2016/page/2/index.html index 073b27d..523e8cd 100644 --- a/archives/2016/page/2/index.html +++ b/archives/2016/page/2/index.html @@ -322,6 +322,10 @@

    CPP + + @@ -331,7 +335,7 @@

    diff --git a/archives/2017/01/index.html b/archives/2017/01/index.html index fa48752..fd6c10c 100644 --- a/archives/2017/01/index.html +++ b/archives/2017/01/index.html @@ -286,6 +286,10 @@

    CPP + + @@ -295,7 +299,7 @@

    diff --git a/archives/2017/03/index.html b/archives/2017/03/index.html index 7c61bd8..d521565 100644 --- a/archives/2017/03/index.html +++ b/archives/2017/03/index.html @@ -542,6 +542,10 @@

    CPP + + @@ -551,7 +555,7 @@

    diff --git a/archives/2017/04/index.html b/archives/2017/04/index.html index 33d62db..77f36a8 100644 --- a/archives/2017/04/index.html +++ b/archives/2017/04/index.html @@ -382,6 +382,10 @@

    CPP + + @@ -391,7 +395,7 @@

    diff --git a/archives/2017/05/index.html b/archives/2017/05/index.html index 7f75a57..67f4ee6 100644 --- a/archives/2017/05/index.html +++ b/archives/2017/05/index.html @@ -154,6 +154,38 @@

    冰果冻

    + + + + - - - -
    -
    -
    - - - -

    - C++笔记-泛型编程 -

    - - -
    @@ -546,6 +546,10 @@

    CPP + + @@ -555,7 +559,7 @@

    diff --git a/archives/2017/page/2/index.html b/archives/2017/page/2/index.html index 7117d5b..6b8cca1 100644 --- a/archives/2017/page/2/index.html +++ b/archives/2017/page/2/index.html @@ -158,14 +158,14 @@

    冰果冻

    - C++笔记-Vector + C++笔记-泛型编程

    @@ -190,14 +190,14 @@

    - C++笔记-智能指针 + C++笔记-Vector

    @@ -222,14 +222,14 @@

    - C++笔记-string类 + C++笔记-智能指针

    @@ -254,14 +254,14 @@

    - C++笔记-类型转换运算符&友元,异常の总结 + C++笔记-string类

    @@ -286,14 +286,14 @@

    - C++笔记-RTTI(运行阶段类型识别:Runtime Type Identification) + C++笔记-类型转换运算符&友元,异常の总结

    @@ -318,14 +318,14 @@

    - C++笔记-异常(3) + C++笔记-RTTI(运行阶段类型识别:Runtime Type Identification)

    @@ -350,14 +350,14 @@

    - C++笔记-异常(2) + C++笔记-异常(3)

    @@ -382,14 +382,14 @@

    - C++笔记-异常 + C++笔记-异常(2)

    @@ -414,14 +414,14 @@

    - C++笔记-友元类 + C++笔记-异常

    @@ -446,14 +446,14 @@

    - C++笔记-嵌套类 + C++笔记-友元类

    @@ -546,6 +546,10 @@

    CPP + + @@ -555,7 +559,7 @@

    diff --git a/archives/2017/page/3/index.html b/archives/2017/page/3/index.html index 83f10a9..9d64f1e 100644 --- a/archives/2017/page/3/index.html +++ b/archives/2017/page/3/index.html @@ -154,6 +154,38 @@

    冰果冻

    + + + +

    -
    -

    - - - -
    -
    -
    - - - -

    - C++笔记-泛型编程 -

    - - -
    @@ -546,6 +546,10 @@

    CPP + + @@ -555,7 +559,7 @@

    diff --git a/archives/page/2/index.html b/archives/page/2/index.html index 878fd29..f15d4bf 100644 --- a/archives/page/2/index.html +++ b/archives/page/2/index.html @@ -158,14 +158,14 @@

    冰果冻

    - C++笔记-Vector + C++笔记-泛型编程

    @@ -190,14 +190,14 @@

    - C++笔记-智能指针 + C++笔记-Vector

    @@ -222,14 +222,14 @@

    - C++笔记-string类 + C++笔记-智能指针

    @@ -254,14 +254,14 @@

    - C++笔记-类型转换运算符&友元,异常の总结 + C++笔记-string类

    @@ -286,14 +286,14 @@

    - C++笔记-RTTI(运行阶段类型识别:Runtime Type Identification) + C++笔记-类型转换运算符&友元,异常の总结

    @@ -318,14 +318,14 @@

    - C++笔记-异常(3) + C++笔记-RTTI(运行阶段类型识别:Runtime Type Identification)

    @@ -350,14 +350,14 @@

    - C++笔记-异常(2) + C++笔记-异常(3)

    @@ -382,14 +382,14 @@

    - C++笔记-异常 + C++笔记-异常(2)

    @@ -414,14 +414,14 @@

    - C++笔记-友元类 + C++笔记-异常

    @@ -446,14 +446,14 @@

    - C++笔记-嵌套类 + C++笔记-友元类

    @@ -546,6 +546,10 @@

    CPP + + @@ -555,7 +559,7 @@

    diff --git a/archives/page/3/index.html b/archives/page/3/index.html index bbc2ca7..3bc32b6 100644 --- a/archives/page/3/index.html +++ b/archives/page/3/index.html @@ -154,6 +154,38 @@

    冰果冻

    + + + +
    @@ -439,38 +471,6 @@

    -

    -
    -

    -
    -

    - - - -
    -
    -
    - - - -

    - C++笔记-访问控制protected -

    - - -
    @@ -551,6 +551,10 @@

    CPP + + @@ -560,7 +564,7 @@

    diff --git a/archives/page/4/index.html b/archives/page/4/index.html index 4ee1940..b46d49e 100644 --- a/archives/page/4/index.html +++ b/archives/page/4/index.html @@ -154,6 +154,38 @@

    冰果冻

    + + + +
    @@ -386,6 +418,10 @@

    CPP + + @@ -395,7 +431,7 @@

    diff --git a/content.json b/content.json index ebada7e..ee9b0a2 100644 --- a/content.json +++ b/content.json @@ -1 +1 @@ -[{"title":"C++笔记-C++11的一些新标准","date":"2017-05-17T15:47:06.000Z","path":"2017/05/17/C++笔记-C++11的一些新标准/","text":"一些C艹11的新标准…反正都是些泥萌大家都知道的过时东西; initializer_list C++11提供了模板类initializer_list,可将其作用于参数,如果类有接受initializer+list作为参数的构造函数,则初始化列表就语法只能用于该构造函数,且列表中的元素必须是同一种类型或可以转换为同一种类型;这个类包含了成员函数begin()和end()用于获得列表范围;123456789101112#include<initializer_list>double sum(std::initializer_list<double> il){ double tot = 0; for(auto p= il.begin(), p!=il.end(), p++) tot += *p; return to;}int main(){ double sum({2.5,3.1,4});} decltype 关键字decltype将变量的类型声明为表达式指定的类型; decltyoe(x) y让y的类型与x相同,x是一个表达式; 比如:123456double x;int n;decltype(x*n) q; // q和x*n的类型一样 即 doubledecltype(&x) pd; //pd和&x的类型一样 即 *doubledecltype(n) n1; //n1的类型和n一样 即intdecltype((x)) d1;//d1的类型为 &double 这在定义模板的时候挺有用的,因为只有在模板被实例化的时候才能够确定类型;12345template <typename T, typename U>void ef(T t, U u){ decltype(T*U) tu; } 返回类型后置 C++11新增了一种函数声明语法,在函数名和参数列表后面指定返回类型1auto f1(double ,int)-> double; // 返回double 他能让你使用decltype来指定模板函数类型:12345template <typename T, typename U>auto my(T t, U u) -> decltype(t * u){ ...} 这里解决的问题是,在编译器遇到my的形参列表之前,T U还不在作用域内,因此必须使用后置返回类型; 模板别名: using = 他能够创建模板别名,和typedef不同的是,他可以用于模板部分具体化:123template <typename T> using arr = std::array<T,12>;//上述具体化模板array<T,int> 将int设置为12array<int,12> a1; //于是这句话可以替换为:arr<int> a1; //替换为这句; 作用域内枚举 传统的C++枚举提供了一种创建名称常量的方式; 但如果在同一个作用域内定义两个枚举,则他们不能重名;C++11新增了一种枚举解决了这些问题,这种枚举使用class或struct定义:12enum class NEW1{never,sometimes,often};enum struct NEW2{never,lever,server}; 基于范围的for循环 对于内置数组以及包含方法begin()和end(0的类,可以使用基于范围的for循环来简化编程工作;123double prices[5] = {4.99,10.99,6.87,7.99,8.49}for(auto x : prices) cout << x << endl; x将以此为prices中1的每个元素的值,x的类型应与数组元素的类型匹配; 吐过想修改数组或容器里的元素可以使用引用:123vector<int> vi(6)for(auto &x : vi) x = rand();; 默认的方法和禁用方法 假设要使用某个默认的函数,而这个函数由于某种原因没有自动创建,例如提供了移动构造函数,则编译器不会自动创建默认的构造函数,复制构造函数和复值构造函数.在这情况下使用default显示的声明这些方法的默认版本:123456class Someclass{ public: someclass(someclass &&) someclass() = default; //使用编译生成的默认构造函数} 关键字delet可以禁止编译器使用特定方法,且适用于任何函数,例如,要禁止复制构造函数可以:12345678910 class Someclass { public: someclass(someclass &&) someclass(const someclass &) }``` ### 管理虚方法: override 和 final 如果一个基类声明了一个虚方法,而我在派生类中提供了不同的版本..特征标不匹配,这将隐藏旧版本; class A{ int a;public: A(int i = 0) : a(i){} virtual void f(char ch) const {…};} class B{public: B(int i = 0) : a(i){} virtual void f(char ch) const {…};}``` 由于B定义的是f(charch)而不是f(char ch); 这导致了程序不能使用: bingo(10); b.f('@') 类似这样的代码; 所以我们可以使用override,把它放在f(char * ch) 后面.如果与基类方法不匹配则将视为错误; 而final解决了另一个问题.可能想禁止派生类覆盖特定的虚方法,谓词可在参数后面加上final;例如,下面的代码禁止A的派生类重新定义f().virtual void f(char ch) const final{...};","tags":[{"name":"笔记","slug":"笔记","permalink":"http://yoursite.com/tags/笔记/"},{"name":"代码","slug":"代码","permalink":"http://yoursite.com/tags/代码/"},{"name":"CPP","slug":"CPP","permalink":"http://yoursite.com/tags/CPP/"}]},{"title":"C++笔记-输入,输出和文件(三)","date":"2017-05-16T11:36:11.000Z","path":"2017/05/16/C++笔记-输入,输出和文件(三)/","text":"封面作者 文件输入输出### 简单文件I/O 如果想让程序写入文件,可以这样来做: 1. 创建一个 ofstream 对象来管理输出流; 2. 将该对象与特定的文件关联起来; 3. 使用cout的方式使用该对象,将输出写入文件; 4. 首先包含头文件#include<fstream>,然后声明一个ofstream的对象: ofstream fout 接下来,必须将这个对象与特定的文件关联起来,可以使用open,假设要打开文件jar.txt: ofstream.open("jar.txt"); 也可以使用构造函数将这俩构步合成一条语句: ofstream fout("jar.txt"); 然后以使用cout的方式使用fout,比如要把”boy next door”放到文件中: fout << "boy next door" ; 由于ostream是ofstream类的基类,因此可以使用所有的ostream方法.另外以这种方式打开文件来进行输入时,如果没有要打开的文件,则将新创建一个.如果有了,则打开并清空文件,并输入; 读取文件和写入文件差不多: 1. 创建一个ifstream对象来管理输入流; 2. 将该对象与特定的文件关联起来; 3. 已使用cin的方式使用该对象; 同样,首先包含头文件#include<fstream>,然后声明一个ifstream对象,将它与文件名关联起来:12345ifstream fin;fin.open("my.txt");//或者写成一句话ifstream fin("my.txt"); 可以像使用cin那样使用fin;12345678910char ch;fin >> ch;char buf[80];fin >> buf;fin.getline(buf,80);string lien;getline(fin,line); 当输入和输出流对象过期时,流到文件的链接将会关闭,但不会删除流; 可以使用close()显式关闭:12fout.close; //断开文件与写入流的链接fin.close; //断开文件与输出流的链接 下面是书上的一个栗子,输入文件名将某些信息写入文件,并读出;:12345678910111213141516171819202122232425#include <iostream>#include <fstream>#include <string>using namespace std;int main(){ string filename; cout << "输入文件名: " << endl; cin >> filename; ofstream fout(filename.c_str()); fout << "梦里不知秋已深,余情岂是为他人\\n"; cout << "out DONE" << endl; fout.close(); ifstream fin(filename.c_str()); cout << "File name :" << filename << endl; char ch; while(fin.get(ch)) cout << ch; cout << "DONE" << endl; fin.close();} ### is_open() 使用isopen检查文件是否被打开;1if (!fin.is_open()) {...} ### 文件模式 文件模式描述的是文件将如何被使用: 读 写 追加等:123ifstream fin("banjo",model);ofstream fout();fout.opem("harp",mode2); ios_base类定义了一个openmode类型;可以选择ios_base类中定义的多个常量来制定模式: ○ ios_base::in 打开文件,以便读取 ○ ios_base::out 打开文件,以便写入 ○ ios_base::ate 打开文件并移动到文件尾 ○ ios_base::app 追加到文件尾 ○ ios_base::true 如果文件存在,则清空 ○ ios_base::binary 二进制文件 ○ ios_base::app 保留文件内容,并在文件尾追加新信息 ○ 文件模式 | ios_base::ate 以指定模式打开并移动到文件尾; ifstream open()使用ios_base::in作为模式参数的默认值; ofstream open()方法使用ios_base::out|ios_base::trunc作为默认值; 可以使用 | 运算符来合并格式;","tags":[{"name":"笔记","slug":"笔记","permalink":"http://yoursite.com/tags/笔记/"},{"name":"代码","slug":"代码","permalink":"http://yoursite.com/tags/代码/"},{"name":"CPP","slug":"CPP","permalink":"http://yoursite.com/tags/CPP/"}]},{"title":"C++笔记-输入,输出和文件(二)","date":"2017-05-11T07:58:06.000Z","path":"2017/05/11/C++笔记-输入,输出和文件(二)/","text":"封面作者 下面是cin の show time cin如何检查输入 不同版本的抽取运算符(>>)查看输入流的方法是相同的.他们跳过空白(空格,换行符,和制表符)直到遇到非空白字符,但在C的单字符模式下,>>它读取从非空白字符开始,到与目标类型不匹配的第一个字符之间全部内容; 例如:123int elevation;cin >> elevation;//假设键入-123Z 运算符将读取前四个.因为他们都是有效的整数.但Z不是.有的时候键入可能没有满足程序的期望,比如输入的是ZCAR,而不是-123Z,这种情况下程序不会修改elevation的值,并返回0;12345678910111213141516171819#include <iostream>int main(){ using namespace std; cout << "Enter" << endl; int sum = 0; int input; while(cin >> input) { sum += input; } cout << "" << input << endl; cout << "Sum = " << sum << endl; return 0;}/* 输入-123Z 输出: 流状态 流状态由3个ios_base元素组成: eofbit,badbit, failbit.其中没一个元素都是一位(1或0); 当cin操作达到文件末尾时,它将设置eofbit; 当cin操作未能读取到预期的字符时,他将设置failbit; 当一些无法诊断的失败破坏流时,它将设置badbit;当三个状态为都为0时表示一切顺利,(他为什么这么熟练啊); 下表列出了位和一些报告或改变流的ios_base方法; 书上的表格奉上: 其他istream类方法 ○ get(char &)和getline(void)方法读取下一个输入字符,即使他是空格,制表符,换行符; ○ get(插入, int, char)和getline(char,int,char)在默认情况下读取一行; 单字符输入: get(char &) 假设有如下代码123456789101112int ct = 0;char ch;cin.get(ch);while(ch != '\\n'){ cout << ch; ct++; cin.get(ch);}cout << ct << endl;//输入: I C++ clearly. <Enter>//输出: I C++ clearly. 通过get(ch),代码读取,显示,并考虑空格和可打印字符;假设程序使用>>:123456789101112int ct = 0;char ch;cin.get(ch);while(ch != '\\n'){ cout << ch; ct++; cin>>get(ch);}cout << ct << endl;//输入: I C++ clearly. <Enter>//输出: IC++clearly. 代码将首先跳过空格.并且循环不会停止.因为>>跳过了换行符,所以换行不会被赋值给ch,所以循环不会被终止. 成员函数get(void) get(void)尘缘函数还读取空白.但使用返回值来将输入传递给程序: 12345678910int ch = 0;char ch;ch cin.get();while(ch != '\\n'){cout << ch;ct++;ch = cin.get()}cout << ct << endl; 到达文件尾的时候.cin.get(void)都会返回值EOF(iostream的一个符号常量).可以利用此特型这样来读取输入: 12345int ch;while((ch = cin.get()) != EOF){ //...} 字符串输入: getline(),get()和ignore(); istream & get(char *, int, char); istream & get(char *, int); istream & getline(char * int, char) istream & getline(char * int) 第一个参数用于放置输入字符串的内存单元的地址.第二个参数是要读取的最大字符数+1(+1是因为要存结尾的空字符);第三个参数用于指定用作分节符的字符;上述函数都在读取最大数目的字符或遇到换行符后停止; get()和getline()最大的差距就是,get()将换行符留在输入流中,也就是截下来的输入操作首先看到的是遗留下的换行符;而getline()舍弃换行符; ignore()接受两个参数: 一个是数字,指定要读取的最大字符数; 另一个是字符,用作输入分界字符;比如: cin.ignore(255,'\\n') 调用读取并丢弃接下来的255个字符,或直到遇到个换行符为止; 1234567891011121314151617181920212223242526272829//书上例子:#include <iostream>using namespace std;const int Limit = 255;int main(){ char input[Limit]; cout << "使用getline()接受字符串: " << endl; cin.getline(input,Limit,'#'); cout << "输出: " << endl; cout << input << "\\n Done" << endl; char ch; cin.get(ch); cout << "下一个输入字符是:" << ch << endl; if(ch != '\\n') cin.ignore(Limit,'\\n'); cout << "使用get()接受字符串:" << endl; cin.get(input, Limit, '#'); cout << "输出:" <<endl; cout << input << "\\n Done" << endl; cin.get(ch); cout << "下一个输入字符是:" << ch << endl; return 0;}//注意getline() 丢弃分界字符# 而get()不会 其他istream方法 read(), peek(), gcount() putback(). read()函数读取指定数目的字节,并将它们存在指定的位置中,并返回istream & 且 read()不会在输入后加上空字符,对于处理文件的输入输出,通常和write()配合使用; 比如:12char gross[144];cin.read(gross,144); //读取144个字符,并将他们存在gross中 peek()函数能查看下一个字符,但不提取他. while((ch = cin.peek()) != '.' && ch != '\\n') //用while检查下一个字符是否是'.'或'\\n'; gcount() 方法返回最后一个非格式化抽取方法(非>>)读取的字符数; 但对于strlen()还是setlen速度较快… putback() 函数将一个字符插入到输入字符串中.被插入的字符串将是下一条输入语句读取的第一个字符..他返回istream & (可以拼接);并且他允许将字符插入到不是刚才读取的位置..是不是想到了什么? peek..没错peek()的效果相当于使用get()读取一个字符,然后使用putback()放回去….","tags":[{"name":"笔记","slug":"笔记","permalink":"http://yoursite.com/tags/笔记/"},{"name":"代码","slug":"代码","permalink":"http://yoursite.com/tags/代码/"},{"name":"CPP","slug":"CPP","permalink":"http://yoursite.com/tags/CPP/"}]},{"title":"在细雨纷飞的那天她送了我一个礼物","date":"2017-05-09T11:42:25.000Z","path":"2017/05/09/随笔-珊瑚海/","text":"“果然还是下雨了啊,果然应该拿把伞啊..”起床的时候就觉得窗外阴沉的天空能拧出水似得,不过本着嫌麻烦的精神,我还是收回了原本伸向抽屉的手.伞什么的,拿着太麻烦了啊.说不定一会儿就放晴了呢. 还好雨下的不大,淅淅沥沥的细雨落到脚下的青石砖上发出纱纱的声响混杂着自己踩在积水上的声音 现在回想起早上立的flag真想给自己一耳光,当然,是早上的自己.不过上头也是有毒,大早上开的哪门子会啊?不过我感觉这次事情可能不小,不然也不至于我话都没说完传话员就把电话挂了 唉..这才几天的太平日子啊..想到这我加快了跑向会议室的脚步 推开那沉重的会议室大门..昏暗的指挥室内充斥着水手们常抽的劣质香烟的味道,一张桌子占据了四分之一的房间,而周围一群穿着海军军装的男人围在桌边更是显得这个临时指挥室的狭小.唯一一个披着海军军装坐着的男人显得有些格格不入.稀疏的胡茬和有些凌乱的莫西干式发型说明了这个男人已经有几天没整理过自己的仪容,你肯定能从他沧桑的脸上读出战争,血,与火的味道..,眼前这个刚毅,睿智,有些许疲惫但眼神却依旧犀利紧盯着桌子上的海图的男人就是我的上级.弗莱彻上将..“现在,我们扯平了”..”在袭击图拉吉后.我们跟对面的情报就拉平了”…夜深了,我吹着海风独自走在这艘合众国新锐的航空母舰上,战争已经悄然打响..“恩哼,睡不着吗?我可爱的的指挥官?”面对突如其来的耳语我着实吓了一跳,但旋即又陷入在那温柔而又有些俏皮的语气中,无法自拔…. “咳咳..别总是捉弄我啊..太太..好歹我也是一个小指挥官啊…”“唉? 又要什么嘛?..难道太太是一个指挥官对自己下属应有的称呼嘛?我可爱的不严谨先生?”听到这话我不禁老脸一红..确实..太太是我对这艘舰的爱称…她的温柔就好像傍晚的海风….“咳咳,,指挥官,,你脸红了哦”不知不觉太太已经面对面离我很近了,,我甚至能闻到她身上独有的洗发香波的味道,,湛蓝色的大眼睛看着我..亚麻色的头发在不算亮的舰船灯光的照射下竟发出了银白色的光泽….“指挥官,,这个给你..人家花了一天的假期给你挑的,,好好珍惜哦..”话说这好像是我第一次收到女孩子的礼物,,,而且还是我下属送的,,,不不不,,这都不是重点..重点是..太太送我东西了!我打开盒子,里面的手表发出滴答滴答的声响,深褐色的表带,黄白色的表盘…好生喜欢 “啊..啊…列克星敦姐.谢谢,,,..明天,,就,..全靠你了”“指挥官,你的脸已经红的跟苹果一样了哦..”“啥? 有..有..有吗?”“有的啦..明天指挥官也要加油哦,,,我回去啦,,晚安哦~” 太太走了,,留下了在海风中还没缓过神的我,,,我现在只能感觉到手腕传来的金属独有的那种有些凉丝丝的温度..以及…脸上的温度与湿润..还有那太太头发的芳香…","tags":[{"name":"随笔","slug":"随笔","permalink":"http://yoursite.com/tags/随笔/"}]},{"title":"_>如你所见.这里都是我的随笔","date":"2017-05-09T11:33:29.000Z","path":"2017/05/09/README/","text":"欢迎来到本智障瞎写的地方,这里充斥着平时的脑洞与灵光的乍现,不过..不论是文笔还是剧情都是小学生作文级の水准,不喜勿喷.","tags":[{"name":"随笔","slug":"随笔","permalink":"http://yoursite.com/tags/随笔/"}]},{"title":"C++笔记-输入,输出和文件","date":"2017-04-25T14:58:17.000Z","path":"2017/04/25/C++笔记-输入,输出和文件/","text":"封面作者 流和缓冲区 C++程序把输入和输出看作字节流.输入时,程序从输入流中抽取字节;输出时,程序将字节插入到输出流中.输入流中的字节可能来自键盘/硬盘/程序. 输出流中的字节可以流向屏幕/打印机/硬盘/程序.换句话说,流充当了程序和流源(流的来源)或流目标之间的桥梁.C++程序处理输出的方式将独立于其去向,因此管理输入分: ○ 将流与输入去向的程序关联起来 ○ 将流与文件链接起来. 换句话说,输入流需要两个链接,每端各一个.文件端链接来源,程序段链接将流的输出部分转存到程序中; 同样对输出流的管理包括将输出流连接到程序以及将输出目标与流关联起来; 通常使用缓冲区可以更高效地处理输入输出.缓冲区是用作中介的内存块,将信息从设备传输到程序或从程序传输给设备的临时存储工具. 缓冲方法从磁盘上读取大量信息,讲这些信息存在缓冲区中,然后每次从缓冲区里读取一个字节,因为从内存中读取单个字节的速度非常快.所以这种方法更快更方便.达到缓冲区尾部之后程序将从磁盘上读取另一块数据.输出时程序首先填满缓冲区.然后把成块数据传给硬盘,并清空缓冲区,已被下一批输出使用.这被称为刷新缓冲区(flushing the buffer). 流,缓冲区和iostream文件 ○ streambuf类维缓冲区提供内存,并提供了用于填充缓冲区,访问缓冲区内容,刷新缓冲区和管理缓冲区内存的类方法. ○ ios_base 类表示流的一般特征,如是否可读取,是二进制流还是文本流等; ○ ios类基于ios_base,其中包括了一个指向streambuf对象的指针成员; ○ ostream类是从iios类派而来的, 提供了输出的方法; ○ istream 类也是从ios类派来的,提供了输入方法; ○ iostream类是基于istream和ostream类的 因此继承了输入输出方法; 一些ostream方法 除了大家喜闻乐见的方法之外,ostream类提供了put()方法和write()方法.前者用于显示字符.后者用于显示字符串. 最初,put()方法原型如下: ostream & put(char); 当前标准被模板化,以适用于wchar_t: cout.put('W'); 还可以将数值类型参数(如int)用于put,这样: cout.put(65) // 输出A cout.put(66.3) //转为66 输出B write()方法显示整个字符串,模板原型如下: basic_ostream<charT,traits>&write(const char_type* s,streamsize n) 第一个参数提供了要显示的字符串的地址,第二个参数指出要显示多少个字符串. 下面是一个及其智障的示例:123456789101112131415161718192021222324252627#include <iostream>#include <cstring>using namespace std;int main(){ const char * s1 = "Florida"; const char * s2 = "Kansas"; const char * s3 = "Euphoria"; int len = strlen(s2); cout << "Increasing LOOP" << endl; int i; for(i = 1; i <= len; i++) { cout.write(s2,i); cout <<endl; } cout << "Decreasing LOOP:"; for(i = len; i > 0; i--) cout.write(s2,i) << endl; cout << "Exceeding string length: " << endl; cout.write(s2,len+5) << endl; return 0;} 需要注意的是,write()方法并不会在遇到空字符的时候自动停止打印.而是只打印指定数目的字符.即使超出了字符的边界. 刷新输出缓冲区 在屏幕输出时,程序不必等到缓冲区被填满.列如,将换行符发送到缓冲区之后.将刷新缓冲区.多数C++实现都会在输入即将发生的时候刷新缓冲区. 可以手动控制刷新缓冲区: 控制符flush刷新缓冲区,而控制符endl刷新缓冲区并插入一个换行符.12cout << "Kazusa" << flush;cout << "Setsuna" << endl; 其实控制符也是函数 可以直接调用flush()来刷新缓冲区: flush(cout); 用cout进行格式化### 1.修改显示时使用的计数系统: ostream类是从ios类派生而来的.而后者是从ios_base派生来的.ios_base类里都是描述格式状态的信息.例如,一个类成员中某些位决定了使用的技术系统,而另一个成员决定了字段宽度.通过使用控制符(manipulator),可以控制显示整数时使用的计数系统.比如控制字段宽度,和小数尾数. 要控制整数以十进制,十六进制,八进制.可以使用dec,hex和oct控制符. 如hex(cout) 或 cout<< hex 将其技术系统格式状态设置为16进制; 下面是一个比较智障的栗子1234567891011121314151617 int main(){ cout << "Enter an integer;"; int n; cin >> n; cout << "n n*n" <<endl; cout << n << " " << n*n << "(decimal)" << endl; cout << hex; cout << n << " "; cout << n*n << " (hexadecimal)" << endl; cout << oct << n << " " << n * n << "(octal)" << endl; dec(cout); cout << n << " "<< n *n << " (decimal)" << endl; return 0;} ### 2.调整字段宽度 在上个代码示例中,输出各列没有对齐,可以使用width函数将长度不同的数字放到宽度相同的字段中: int width(); int width(int i); 第一种格式返回字段宽度的当前设置,第二种格式将宽度设置为i个空格,并返回以前的字段宽度值,且width方法只影响将显示的下一个项目.然后字段宽度将恢复为默认,下面是个书上的例子:12345678910111213141516171819202122232425262728 #include<iostream> int main() { using namespacestd int w = cout.width(30); cout << "default field width = " << w << endl; cout.with(5); cout << "N" << ':'; cout.width(8); cout <<"N*N" << endl; for(long i=1; i <= 100; i*=10) { cout.width(5); cout << i << ':'; cout.width(5); cout<<i*i<<endl; } return 0; } /*输出: default field width = 0 N: N*N 1: 1 10: 100100: 10000 */ 上诉输出中,值在字段中右对齐,右对齐时空格被插入到值的左侧,cout通过加入空格来填满整个字段,用来填充的字符叫做填充字符(fill chararcter),并且右对齐是默认的;w的值为0是因为cout.width(30)返回的是以前的字段宽度,而不是刚设置的值. ### 3.填充字符 默认使用空格填充字段中未被使用的部分,可以用fill()成员来改变填充字符,而且新的填充字段将一直有效,直到更改他为止,用星号填充:cout.fill('*')1234567891011121314151617181920 int main(){ cout.fill('*'); const char * staff[2] = {"waldo Whipsnade","Wilmarie Wooper"}; long bonus[2] = {900,1350}; for(int i = 0; i < 2; i++) { cout << staff[i] << ": "; cout.width(7); cout << bonus[i] << endl; } return 0;}/*输出waldo Whipsnade: ****900Wilmarie Wooper: ***1350*/ ### 4.设置浮点数的显示精度 浮点数精度的函数取决于输出模式.在默认模式下,他指的是显示的总位数.在定点模式和科学模式下,精度指的是小数点后面的位数.C++默认精度为6; 将精度设置为2: cout.precision(2)12345678910111213141516171819202122232425#include<iostream>using namespace std;int main(){ float p1 = 20.40; float p2 = 1.9 + 8.0 /9.0; cout << "p1:" << p1 << endl; cout << "p2:" << p2 << endl; cout.precision(2); cout << "p1(2): " << p1 << endl; cout << "p2(2):" << p2 << endl; return 0; /* 输出p1:20.4p2:2.78889p1(2): 20p2(2):2.8*/ ### 5.打印末尾的0和小数点 下面的函数调用使cout显示末尾的小数点:cout.setf(ios_base::showpoint);showpoint是ios_base类中定义的类级静态常量;123456789101112131415161718192021222324#include <iostream>using namespace std;int main(){ float p1 = 12.40; float p2 = 1.9+8.0/9.0; cout.setf(ios_base::showpoint); cout << "p1:" << p1 << endl; cout << "p2:" << p2 << endl; cout.precision(2); cout << "(2)p1" << p1 << endl; cout << "(2)p2" << p2 << endl; return 0;}/* 输出p1:12.4000p2:2.78889(2)p1:12.(2)p2:2.8*/ ### 6.setf() ios_base类有一个受保护的数据成员,其中的各位(标记) ,像开关一样分别控制着格式化的各个方面,打开开关称为设置标记,并将相应位设为1.而setf()函数提供了一种调整标记的途径: setf()有俩原型.第一个: fmtflags setf(fmtflags); fmtflags是bitmask类的typedef,用于存储标记格式;ios_base定义了代表位值的常量: ○ ios_base::boolalpha: 输入和输出bool值,可以为true或false; ○ ios_base::showbase: 对于输出,使用C++基数前缀 0 0x ○ ios_base::showpoint: 显示末尾的小数点 ○ ios_base::uppercase: 对于十六进制输出 使用大写字符E表示法 ○ ios_base::showpos: 在整数前面加上+ 1234567891011121314151617181920212223#include <iostream>using namespace std;int main(){ int temp = 63; cout.setf(ios_base::showpos); cout << "showpos: "<<temp << endl; cout << hex << "hex: " << temp << endl; cout.setf(ios_base::uppercase); cout.setf(ios_base::showbase); cout <<"uppercase & showbase: " << temp << endl; cout.setf(ios_base::boolalpha); cout << "boolalpha(true): " <<true << endl; return 0;}/*输出showpos: +63hex: 3fuppercase & showbase: 0X3Fboolalpha(true): true*/ 第二个seft()原型接受两个参数,并返回以前的设置: fmtflags setf(fmtflags, fmtflags); 函数的这种格式用于设置由多位控制的格式选项; 第一参数和以前一样,也是一个包含了所需设置的fmtlages值.第二参数指出要清除第一个参数中的哪些位; setf()函数是ios_base类的一个成员函数;由于这个类是ostream的基类,因此可以使用cout来调用该函数: ios_base::fmtflags old = cout.setf(ios::left, ios::adjustfield) 要恢复之前的可以: cou.setf(old,ios::adjustfield); 书上的例子:1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071 #include <iostream>#include <cmath>using namespace std;int main(){ //使用左对齐,显示加号,显示尾随零,精度为3 cout.setf(ios_base::left,ios_base::adjustfield); cout.setf(ios_base::showpos); cout.setf(ios_base::showpoint); cout.precision(3); //使用e表示法,保存旧的格式设置 ios_base::fmtflags old = cout.setf(ios_base::scientific,ios_base::floatfield); cout << "左对齐:" << endl; long n; for(n = 1; n < 41; n+= 10) { cout.width(4); cout << n << "|"; cout.width(12); cout << sqrt(double(n)) << "|" << endl; } //改为内对齐 cout.setf(ios_base::internal,ios_base::adjustfield); //恢复默认浮点显示样式 cout.setf(old,ios_base::floatfield); cout << "内对齐:" << endl; for(n = 1; n < 41; n+= 10) { cout.width(4); cout << n << "|"; cout.width(12); cout << sqrt(double(n)) << "|" << endl; } //右对齐,使用定点计数法 cout.setf(ios_base::right,ios_base::adjustfield); cout.setf(ios_base::fixed,ios_base::floatfield); cout << "右对齐:" << endl; for(n = 1; n < 41; n+= 10) { cout.width(4); cout << n << "|"; cout.width(12); cout << sqrt(double(n)) << "|" << endl; } return 0;}/* 输出:左对齐+1 |+1.000e+000 |+11 |+3.317e+000 |+21 |+4.583e+000 |+31 |+5.568e+000 |内对齐+ 1|+ 1.00|+ 11|+ 3.32|+ 21|+ 4.58|+ 31|+ 5.57|右对齐 +1| +1.000| +11| +3.317| +21| +4.583| +31| +5.568| */ ## 6.iomanip 如果嫌前面那些麻烦的话,头文件iomanip中提供了一些其他控制符,他们能提供前面的部分服务.setprecision(),setfill() 和 setw(); setprecision()控制符接受一个指定精度的整数参数; setfill()控制符接受一个指定填充字符的char参数;setw()控制符接受一个指定字段宽度的整数参数;并且他们都能通过cout链接起来; 书上的例子:1234567891011121314151617181920212223242526272829303132333435363738#include <iostream>#include <iomanip>#include <cmath>int main(){ using namespace std; cout << fixed << right; cout << setw(6) << "N" << setw(14) << "square root" <<setw(15) << "fourth root" << endl; double root; for(int n = 10; n <= 100; n+=10) { root = sqrt(double(n)); cout << setw(6) << setfill('.')<< n << setfill(' ') << setw(12) << setprecision(3) << root << setw(14) << setprecision(4) << sqrt(root) << endl; } return 0;}/* 输出 N square root fourth root....10 3.162 1.7783....20 4.472 2.1147....30 5.477 2.3403....40 6.325 2.5149....50 7.071 2.6591....60 7.746 2.7832....70 8.367 2.8925....80 8.944 2.9907....90 9.487 3.0801...100 10.000 3.1623*/","tags":[{"name":"笔记","slug":"笔记","permalink":"http://yoursite.com/tags/笔记/"},{"name":"代码","slug":"代码","permalink":"http://yoursite.com/tags/代码/"},{"name":"CPP","slug":"CPP","permalink":"http://yoursite.com/tags/CPP/"}]},{"title":"C++笔记-算法(STL)","date":"2017-04-21T14:53:05.000Z","path":"2017/04/21/C++笔记-算法(STL)/","text":"封面来源 算法的一些通用特征: STL文档使用模板参数名称来表示参数模型的概念.比如下面的copy()原型:12template<class InputIterator, class OutputIterator>OutputIterator copy(InputIterator first, OutputIterator last, OutputIterator result); 所以标识符InputIterator和OutputIterator都是模板参数,看名字一眼就能看出来区间参数必须是输入迭代器或更高级的,而只是结果存储的迭代器必须是输出迭代器或更高级的. STL的有些算法有两个版本: 就地算法(in-place algorithm)和复制算法(copying algorithm),顾名思义,sort()就是就地算法的例子:函数完成时,结果被存放在原始数据的位置上; 而copy()函数将结果发送到另一个位置,所以他是复制算法;STL对此的约定是,复制版本的名称将以_copy结尾. 比如replace()函数,他将所有的old_value替换为new_value,则它的复制版本为:123template<class InputIterator,class OutputIterator, class T>OutputIterator replace_copy(InputIterator first,Inputiterator last, OutputIterator result, const T& old_value, const T& old_value); 对于复制算法的约定是: 返回一个迭代器,指向复制的超尾(最后一个值后面)的位置 另一个常见变体是: 有些函数是根据函数应用于容器元素得到的结果来执行操作的.他们通常以_if结尾,比如replace_if:123456789template < class ForwardIterator, class UnaryPredicate, class T > void replace_if (ForwardIterator first, ForwardIterator last, UnaryPredicate pred, const T& new_value){ while (first!=last) { if (pred(*first)) *first=new_value; //pred()是个一元谓词,根据其真假来决定是否将new_value赋给first ++first; }} 一个使用STL的例子:假设编写一个程序,让用户输入单次,希望最后得到一个按照输入顺序排列的单次列表,一个按照字母排序的列表(忽略大小写),并记录每个单次被输入的次数(不考虑标点,数字,符号): 输入和保存我们可以用vector<>: 1234vector<string> word;string input;while(cin >> input && input != "quit") word.push_back(input); 然后我们可以创建一个set对象,然后将vector中的单词复制(使用插入迭代器)到集合中.集合自动对其排序,无需使用sort(); 且集合只允许一个键出现一次因此也无需调用unique().至于忽略大小写…可以使用transfor,将vector中的数据复制到集合中.使用一个转换函数将函数转成小写;123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051#include <iostream>#include <string>#include <vector>#include <set>#include <map>#include <iterator>#include <algorithm>#include <cctype>using namespace std;void display(const string & a){ cout << a << " ";}char toLower(char ch){ return tolower(ch);}string & ToLower(string & st){ transform(st.begin(),st.end(),st.begin(),toLower); return st;}int main(){ vector<string> words; cout << "输入单词(quit退出): " << endl; string input; while (cin >> input && input != "quit") words.push_back(input); cout << "原始数据: " << endl; for_each(words.begin(),words.end(),display); cout << endl; set<string> word_s; transform(words.begin(),words.end(), insert_iterator<set<string>>(word_s,word_s.begin()),ToLower); cout << "list: " << endl; for_each(word_s.begin(),word_s.end(),display); cout << endl; map<string,int> wordmap; set<string>::iterator si; for(si = word_s.begin();si != word_s.end(); si++) wordmap[*si] = count(words.begin(),words.end(),*si); cout << "新数据: " << endl; for_each(word_s.begin(),word_s.end(),display); return 0;} 其他库 slice类对象可用作数组索引,他们表示的是不是一个值而是一组值.slice对象被初始化为三个整数值: 起始索引,索引数,跨距.起始索引是第一个被选中的元素的索引,索引数指出要选择多少个元素,跨距表示元素之间的距离.例: slice(1,4,3)创建的对象表示选择四个元素,他们的索引分别是1,4,7,10;从索引开始加上跨距得到下一个元素的索引. 把第1,4,7,10元素设为10:1varint[slice(1,4,3)] = 10; 一个valarrat和slice混用的例子:123456789101112131415161718192021222324252627282930313233343536373839404142434445#include <iostream>#include <valarray>#include <cstdlib>using namespace std;const int SIZE = 12;void show(const valarray<int>& v, int cols){ int lim = v.size(); for(int i = 0; i < lim; ++i) { cout.width(3); cout << v[i]; if(i % cols == cols -1) cout<< endl; else cout << ' '; } if(lim % cols != 0) cout << endl;}int main(){ valarray<int> vint(SIZE); int i = 0; for(; i < SIZE; ++i) vint[i] = rand() % 10; cout << "原始数据: " << endl; show(vint,3); valarray<int> vcol(vint[slice(1,4,3)]); cout << "第二列: " << endl; show(vcol,1); valarray<int> vrow(vint[slice(3,3,1)]); cout << "第二行: " << endl; show(vrow,3); cout << "将最后一列设为10:" <<endl; show(vint,3); cout << "将第一列设置为下两个的总和:" << endl; vint[slice(0,4,3)] = valarray<int>(vint[slice(1,4,3)]) + valarray<int>(vint[slice(2,4,3)]); show(vint,3); return 0;} 模板initializer_list(C++11) 模板initializerlist(在头文件initializer中)是C++11新增的,可以使用初始化列表语法将STL容器初始化为一系列值:12vector<double> payments(45.99,39.23,19.95,89.01);//创建一个包含四个元素的容器,并用列表中的四个值来初始化这些元素; 这么做可行是因为容器包含了将initializer_list作为参数的构造函数.比如vector就包含了一个将initializer_list作为参数的构造函数,因此上述声明和下面的等同;1vector<double> payments( {45.99,39.23,19.95,89.01} ); 这是C++11新增的通用初始化语法,可以使用{}而不是()来调用构造函数,并且在类有接受initializer_list作为参数的构造函数则将优先使用:1shared_ptr<double> pd{new double}; 所有initializer_list元素的类型都必须相同,但编译器有的时候将尽心必要转换:12vector<double> payments {45.99, 39.23, 19, 89.0};//19会转为19.0,但不能进行窄化转换,也就是不能隐式的把上面的double转为int; initializer_list使用例子,另外他还包含成员函数begin() end() size():123456789101112131415161718192021222324252627282930313233343536#include <iostream>#include <initializer_list>using namespace std;double sum(initializer_list<double> il){ double s = 0; for(auto p = il.begin(); p != il.end(); p++) s += *p; return s;}double average(const initializer_list<double> & av){ double s = 0; double n = av.size(); if(n > 0){ for(auto p = av.begin(); p != av.end(); p++) s += *p; s = s/n; } return s;}int main(){ cout << "List 1: sum = " << sum({2,3,4}); cout << "ave = " << average({2,3,4}) << endl; initializer_list<double> dl = {1.1, 2.2, 3.3, 4.4, 5.5}; cout << "List 2: sum = " << sum(dl); cout << "ave = " << average(dl) << endl; dl = {16.0, 25.0, 36.0, 40.0, 64.0}; cout << "List 3: sum = " << sum(dl); cout << "ave = " << average(dl) << endl; return 0;} 可以按值传递initializer_list对象,也可以按引用传递;STL是按值传递;","tags":[{"name":"笔记","slug":"笔记","permalink":"http://yoursite.com/tags/笔记/"},{"name":"代码","slug":"代码","permalink":"http://yoursite.com/tags/代码/"},{"name":"CPP","slug":"CPP","permalink":"http://yoursite.com/tags/CPP/"}]},{"title":"C++笔记-函数对象","date":"2017-04-17T14:58:36.000Z","path":"2017/04/17/C++笔记-函数对象/","text":"作者 很多STL算法都使用函数对象也叫函数符.函数符是可以以函数方式与()结合使用的任意对象.包括函数名,指向函数的指针,和重载了()运算符的类对象(即定义了函数operator()()的类)可以定义这样一个类:12345678910class Linear{ private: double slope; double y0; public: Linear(double s = 1, double y = 0) : slope(s),y0(y){} double operator() (double x){return y0+slope * x;}}; 这样重载的()运算符将能够像函数那样使用Lineat对象:123Linear f2(2.5,10.0);double y1 = f1(12.5);double y2(0.4) 其中y1将使用表达式0+112.5来计算,y2将使用表达式10.0+2.50.4来计算.y0和slope的值来自对象的构造函数,而x的值来自于operator()()的参数; 话说回来,还记得for_each()的第三个参数吗? 通常,第三个参数可以是常规函数,也可是函数符.那么如何声明第三个参数呢?STL使用模板解决了这个问题,for_each的原型看上去:12345template<class InputIterator, class Function>Function for_each(InputIterator fist,InputIterator last,Function f);//ShowReview()的原型如下:void ShowReview(const Review &); 这样表示符ShowReview的类型为void(*)(const Review &),将其赋给模板参数f,也是Function的类型,Function是可以表示具有重载的()运算符的类类型.f是指向函数的指针,而f()调用该函数.如果参数是个对象,则f()是调用其重载的()运算符的对象; 函数符概念 ○ 生成器(generator) 是不用参数就可以调用的函数符 ○ 一元函数(unary function) 是用一个参数可以调用函数符,返回bool值的一元函数的是谓词(predicate) ○ 二元函数(binary function) 是用两个参数可以调用的函数符,返回bool值的二元函数是二元谓词(binary predicate) 例如:sort()的一个版本,将二元谓词作为其第三个参数:12bool WorseThan(const Review & r1, const Review & r2);sort(books.begin(),books.endl(),WoresThan); list模板有一个将谓词作为参数的remove_if()成员,该函数将谓词应用于区间中的所有元素,如果谓词返回true则删除这些元素,例如删除链表three中所有大于200的元素:1234bool tooBig(int n){n > 100};list<int> scores;scores.remove_if(tooBig); 下面这个类演示了类函数符适用的地方,假设要删除另一个链表中所有大于200的值,且如果能将被比较的值作为第二个参数传给tooBig(),那我们就可以使用不同的值调用该函数辣,但谓词只能有一个参数,这可咋整..:123456789template<class T>class TooBig{ private: T cutoff; public: TooBig(const T & t) : cutoff(t) {}; bool opeator()(const T & v){return v > cutoff;}} 这里,一个值(v)是作为函数参数传递的,另一个cutoff是由类的构造函数设置的. 下面的程序演示了这种技术:12345678910111213141516171819202122232425262728293031323334353637383940#include <iostream>#include <list>#include <iterator>#include <algorithm>using namespace std;template<typename T>class TooBig{public: T cutoff; TooBig(const T & t) : cutoff(t) {} bool operator()(const T & c){return c > cutoff;} //重载()};void outint(int n){cout << n << " ";}int main(){ TooBig<int> f100(30); //创建实例初始化cutoff为30 int vals[10] = {10,20,30,40,50,60,70,80,90,100}; list<int> a(vals,vals+10); //创建俩链表 list<int> b(vals,vals+10); //输出链表 for_each(a.begin(),a.end(),outint); cout << endl; for_each(b.begin(),b.end(),outint); cout << endl; a.remove_if(f100); //此时cutoff为30.即在a中删除所有大于30的数 b.remove_if(TooBig<int>(50));//创建匿名对象 for_each(a.begin(),a.end(),outint); cout << endl; for_each(b.begin(),b.end(),outint); cout << endl; return 0;} f100是一个声明的对象,而TooBig(50)是个匿名对象; 综上,我们可以将接受两个参数的模板函数转化为接受单个参数的函数对象例如:123456789101112template <typename T> //接受两个参数的模板函数bool tooBig(const T & t1,const T & t2){ return t1 > t2};template<typename T> //接受单个参数的模板函数对象class TooBig{private: T val;public: TooBig(const T & t) : val(t){} bool operator()(const T & ot) {return t > val;}} 即可以这么写:1234TooBig<int> my(30);int x;cin << x;if( my(x)){...} //和tooBig(x,100)一样 调用my(x),相当于调用了tooBig(x,100),换句话说类TooBig是个函数适配器,可以使函数满足不同的接口; 预定义的函数符: transform()函数有两个版本,第一个版本的前两个参数是指定容器区间的迭代器,第三个参数将结果复制到哪里的迭代器,第四个参数则是一个函数对象(函数符);比如:12transform(l.begin(),l.end(),out,sqrt);//你就当l是个容器实例,out是个输出迭代器,sqrt是开平方;//这就是吧容器l里的挨个开平方,并发送到输出流 第二个版本与第一个版本有所不同,版本二是一个二元函数,其第三个参数为第二个区间的起始位置,后面的参数不变,所以说如果我们想算俩容器里每个数的平均值就可以:12double add(double x, double y){return x + y};transform(l.begin(),l.end(),l2.begin(),out,add); 但这样吧….就得给每种类型都定义一个add,,,所以让我们用模板吧比如plus<>..那么让我们看看SLT预定的几个函数符: 自适应函数符合函数适配器 上表列出的预定义函数符都是自适应的,自适应函数符携带了表示参数类型和返回类型的typedef成员,他们分别是:result_type,first_argument_type和second_argument_type.比如plus对象的返回类型被表示为plus::result_type,这是int的typedef; 函数符自适应性的意义在于: 函数适配器对象可以使用函数对象,并使用typedef成员.例如,接受一个自适应函数符参数的函数可以使用result_type成员来声明一个与函数的返回类型匹配的变量. 比如现在我们想将容器l里的每个元素都乘上2.5..那么跟上面那个一样我们是用:1transform(l.begin(),l.end(),out,multiplies); 但是吧multiplies是个二元函数…所以我们得把接受两个参数的函数符(multiplies)转换成接受一个参数的multiplies.我们使用神奇的bind1st();假设现在有个二元函数对象f2(),可以把f2()的第一个参数的值跟f2()相关联,所以我们对multiplies的二元转一元就可以写成:12bind1st(multiplies<double>,2.5); //将mu和2.5相关联.2.5将用于mu的第一个参数; 第二个参数在此:12transform(l.begin(),l.end(),out,multiplies<double>,2.5);//这样容器l里的每个元素都将作为mu的第二个参数;达到乘2.5的目的","tags":[{"name":"笔记","slug":"笔记","permalink":"http://yoursite.com/tags/笔记/"},{"name":"代码","slug":"代码","permalink":"http://yoursite.com/tags/代码/"},{"name":"CPP","slug":"CPP","permalink":"http://yoursite.com/tags/CPP/"}]},{"title":"C++笔记-泛型编程(续","date":"2017-04-11T14:35:05.000Z","path":"2017/04/11/C++笔记-泛型编程(续/","text":"封面来源 容器种类 STL具有容器概念和容器类型.概念是具有名称的通用类别,容器类型是可用于创建具体对象的模板. 容器概念 容器概念描述了所有STL容器都需要满足的一些要求.他是个概念化的抽象基类,说他是一个概念化的抽象基类,是因为容器类不真正使用继承机制. 所有的容器都提供某种特定的特征和操作,下表对一些通用特征进行了总结: 上表中的复杂度表示执行操作所需的时间,从快到慢依次为: ○ 编译时间 ○ 固定时间 ○ 线性时间 如果复杂度为编译时间,则操作将在编译时执行,执行时间为0;固定复杂度意味着操作发生在运行阶段,但独立于对象中的元素数目;线性复杂度意味着时间与元素数目成正比; C++11新增的容器要求 下表列出了C++11新增的通用容器要求,其中rv表示类型为X的非常量右值,如函数返回值,另外要求X::iterator满足正向迭代器的要:; 序列(sequence) 序列概念增加了迭代器至少是正向迭代器的要求,并且要求元素按严格的线形顺序排列,即存在第一个元素,最后一个元素,除了端点的俩元素外,每个元素前后都分别有一个元素,且不会在两次迭代之间发生变化.这7个STL容器类型都是序列: deque,forward_list,list,queue,priority_queue,stack,vector; 下表表示了序列所需要的操作,t表示类型为T(存在容器中的值的类型)的值,n表示整数,p,q,i,j表示迭代器: 因为模板deque,list,queue,priority_queue,stack和vector都是序列的概念模型,所以他们中的一些还可以使用下表所列出的操作: 详细介绍 vector: 前面说过了,不做赘述 deque: deque模板类表示双端队列,实现类似vector,他支持随机访问,但不同的是:从deque对象的开始位置插入和删除元素的时间是固定的,而vector是在结尾处提供了固定时间的插入和删除,在而别的地方都是线性时间,但vector的速度似乎快一点.. list: list模板类表示双向链表.除了第一个和最后一个元素外,每个元素都与前后的元素相连接.他和vector的区别在于,list在链表中任意位置插入和删除的时间是固定的(废话).list擅长的是元素的快速插入和删除.但他不支持数组表示法和随机访问.从容器中管插入或删除元素后,链表迭代器指向的位置不变,但数据不同.然后插入新元素并不会移动已有元素,只是修改链接信息. list的成员函数,Alloc有默认值: ○ void merge(list & x) //将链表x与调用链表合并.两个链表必须排序,合并后的链表在调用链表中,x为空.这个函数复杂度为线性时间; ○ void remove(const T & val) //从链表中删除val的所有实例,复杂度为线性时间; ○ void sort() //使用<运算符排序,复杂度为NlogN; ○ void splice(iterator pos, list x) //将链表x的内容插入到pos的前面,x为空,复杂度为固定时间; ○ void unique() //将连续的相同元素压缩为单个元素(删除重复元素),复杂度为线性时间 list的示例代码 insert()和splice()之间的区别在于:insert()将原始区间的副本插入到目标地址,而splice()则将原始区间移动到目标地址,且splice()方法执行后,迭代器指向的元素不变. forward_list(C++11): forward_list容器类实现了单链表.也就是每个节点都只连接到下一个节点,而没有连接到前一个节点.所以他只需要正向迭代器,并且是不可反转容器; queue: queue模板类是个适配器类.他让底层类(默认为deque)展示典型的队列接口.但他限制比deque多,不允许随机访问队列,不允许遍历队列,他可以: ○ bool empty()const //如果队列为空,返回true,否则返回false ○ size_type size()const //返回队列中元素的数目 ○ T& front() //返回指向队首元素的引用 ○ T& back() //返回指向队尾元素的引用 ○ void push(const T& x) //在队尾插入x ○ void pop() //删除队首元素 priority_queue: 该类是另一个适配器,支持的操作和queue一样,区别是在priority_queue中最大的元素会被移动到队首; 构造函数参数:12priority_queue<int> pql;priority_queue<int> pq2(greater<int>) stack: 该类也是个适配器,和queue相似,他给底层类(默认是vector)提供了典型的栈接口;但他不允许随机访问栈元素,不允许遍历栈,只能用一些栈的基本操作,如压入,弹出,查看栈顶值,检查元素数目,测试栈是否为空,他还可以: ○ bool empty() const //如果栈为空则返回true,否则返回false ○ size_type size() const //返回栈中元素数目 ○ T& top() //返回指向栈顶元素的引用 ○ void push(const T& x) //在顶部插入x ○ void pop() //删除栈顶元素 array(C++11): 非SLT容器,其长度为固定,所以也就没有调整容器大小的操作(如push_back(),insert()),但定义了一些成员函数如operator [] ()和at().可将很多STL算法用于他(如copy(),for_each()); 关联容器(associative container) 关联容器是将值于键值关联在一起,并使用键来查找值.表达式X::key_type指出了键的类型.他们提供了对元素的快速访问(查找).STL提供了四种关联容器: set, mulitiset,map,multimap. 前两种在文件set.h中,后两者在map.h中定义. 最简单的关联容器是set,其值类型与键值相同,键是唯一的,对于set来说值就是键; multiset类似set,只是可以有多个键值相同,比如: 如果建和值的类型为int,则multiset对象包含的内容可以有1,2,2,2,3,5等.而在map中,值于键的类型不同,键和值是一一对应的;而multimap和map差不多,只是一个键可以与多个值相关联; set示例:12345 const int N = 6; string s1[N] = {"ass","we"."can","booy","next","can"}; set<string> A(s1,s1+N); ostream_iteratior<string,char> out(cout," "); copy(A.begin(),A.end(),out); 和别的容器相似set也使用模板来制定要储存的值类型,与其他容器相似,set也有一个将迭代器区间作为参数的构造函数.上述代码片段输出表明键是唯一的,虽然数组里有俩for,但如果两个集合包含相同的值,则这个值将在并集中只出现一次,所以for在集合中只出现了一次,这是因为set的键值是唯一的,且集合是经过排序的:cout: ass booy can next we set_union()函数接受五个迭代器参数,前两个迭代器定义了第一个集合的区间,接下来的两个参数定义了第二个集合的区间,最后一个参数是输出迭代器,指出将结果集合复制到什么位置.举个栗子,要显示集合A,B的并集可以:1set_union(A.begin(),A.end(),B.begin(),B.end(),ostream_iterator<string,char> out(cout," ")); 假设要将记过放到C集合中,而不是现实他,则最后一个参数应该是个匿名insert_iterator,将信息复制给C:1set_union(A.begin(),A.end(),B.begin(),B.end(),insert_iterator<set<string>>(C,C.begin())); 方法lower_bound()将键作为参数并返回一个迭代器,迭代器指向集合中的第一个不小于键参数的成员,upper_boun()将键作为参数并返回一个指向集合中第一个大于键参数的成员.函数set_intersection()和set_difference()粉蝶查找交集和获得两个集合的差,参数与set_union()相同; multimap示例: 基本的multimap声明使用模板参数指定键的类型和存储的值类型.他一个键可以与多个键值相关联.下面声明创建一个multimap对象,键类型为int,值类型为string,第三个模板参数是可选的,用于对键排序,默认使用less<>:1multimap<int,string> codes; 如果要用区号作为键来存城市名字,一种方法是创建个pair,再将它插入:12345pair<const int, string> item(213,"Los Angeles");codes.insert(item);//或创建匿名pair对象:codes.insert(pair<const int, string> (213,"Los Angeles");) 对于pair对象,可以使用first和second访问其两个部分:12pair<const int, string> item(213,"Los Angeles");cout << item.first << item.second << endl; 成员函数count()接受键作为参数,返回具有该键的元素数数目.lower_bound()和upper_bound()将键作为参数,并分别返回集合中第一个不小于键参数的成员和第一个大于键参数的成员;equal_range()用键做参数,返回两个迭代器,表示的区间与该键匹配. 无序关联容器 无需关联容器也是将值与键关联起来,并用键找值,但底层差别在于关联容器基于树形结构,而无序关联容器基于哈希表,速度奇快.比如unordered_set,unordered_map,unordered_multiset,unordered_multimap;","tags":[{"name":"笔记","slug":"笔记","permalink":"http://yoursite.com/tags/笔记/"},{"name":"代码","slug":"代码","permalink":"http://yoursite.com/tags/代码/"},{"name":"CPP","slug":"CPP","permalink":"http://yoursite.com/tags/CPP/"}]},{"title":"C++笔记-泛型编程","date":"2017-04-05T14:20:46.000Z","path":"2017/04/05/C++笔记-泛型编程/","text":"封面作者 STL是一种泛型编程.面向对象编程关注的是程序的数据方面,而泛型编程关注的是算法.泛型编程旨在编写独立于数据类型的代码.在C艹中通常使用的工具是模板,模板是的能够按照泛型定义函数和类,而STL通过通用算法更近了一步. 为何使用迭代器 模板使得算法独立于存储类型,而迭代器使算法独立于使用的容器类型.所以他们都是STL通用方法的组成部分. 如果在一个double数组中搜索特定值可以这样写:1234567double * find_ar(double * ar, int n, const double & val){ for(int i = 0; i < n; i++) if(ar[i] == val) return &ar[i];//如果在数组中找到val则返回val在数组中的地址; return nullptr; //否则返回一个空指针} 在这里我们基于了特定的数据结构(数组)可以使用模板来将这种算法推广到包含==运算符的任意类型的数组. 不过我们也可以使用链表:12345struct Node{ double item; Node * p_next;} 假设有一个指向链表第一个节点的指针,每个节点的p_next都指向下一个节点,链表最后一个节点的p_next被设置为nullptr…则可以这么写:12345678Node * find_ll(Node * head,const double & val){ Node * start; for(start= head; start!=nullptr; start = start->p_next) if(start->item == val) return start; return nullptr;} 他俩一个使用数组,一个将start重置为start->p_next,广义上他们都一样:将值于容器中的值比较,直到找到匹配为止.但在这里我们基于了特定的数据结构(链表) 而泛型编程旨在使用同一个find函数来处理数组,链表,或其他容器类型.函数不仅独立于容器中储存的类型,而且独立于容器本身的数据结构.模板为我们提供了存储在容器中的数据类型的通用表达,所以我们还需要遍历容器中的值的通用表达.而迭代器正好是干这活的… 要实现find函数,我们的迭代器应该具备以下特征: 应能够对迭代器执行解引用操作,一边能够访问他引用的值,即如果p是个迭代器,则能对*p进行定义; 应能够将一个迭代器赋值给另一个.即如果p和q是迭代器.则能够对p=q进行定义; 应能够使用迭代器遍历容器中的所有元素,这可以为迭代器定义++p和p++来实现; 应能够将一个迭代器和另一个迭代器进行比较,看他们是否相等.即如果p和q都是迭代器则能对p==q和p!=q进行定义 对于find函数来讲,有上述功能就够了,STL按照功能的强弱定义了不同等级的迭代器.另外常规指针就能满足迭代器的要求,可以使之接受两个指示区间的指针参数,其中一个指向数组的起始位置另一个指向数组的超尾;并且如果我们的函数没找到指定的值,那么让他返回尾指针,因此可以这样重写find_ar():123456789typedef double * iterator;iterator find_ar(iterator begin,iterator end,const double & val){ iterator ar; for(ar = begin; ar!=end; ar++) if(*ar == val) return ar; return end;} 对于find_ll()函数,可以定义一个迭代器类,其中定义了运算符*和++;123456789101112131415161718192021struct Node{ double item; Node * p_next;}class iterator { Node * pt;public: iterator() : pt(nullptr){} iterator(Node * p) : pt(p){} double operatror*(){return pt->p_next;} iterator & operator++(){ //++pt pt = pt->p_next; return *this; } iterator & operator++(int){ //int表示后缀版++运算符,该int参数永远不会用到只做区分 iterator temp = *this; pt = pt->p_next; return temp; } //你就假装我这里重载了==和!=} 废了这么大劲之后,我们的find函数改就可以这样写:12345678iterator find_ll(iterator head, const double & val){ iterator start; for(start = head; head!=0; ++start) if(*start = val) return start; return nullptr;} 机智的你一定发现了我这不学好的一天净扯淡: 这踏马不废话么这俩玩意(函数)一毛一样啊,差别就是find_ar用了超尾.find_ll返回的是nullptr啊..其实我们还可以让链表的最后一个元素后面还有个空白元素..这样链表也有了超尾.他们俩就成了真·一个(功能的)算法; //我是第一个分割线↓ 前面的废话都是为了下面的废话做铺垫的: STL其实就是遵循上面的方法.首先每个容器类(vector, list等)定义了相同的迭代器类型,对于其中的某个类,迭代器可能是指针;但对于另一个类,迭代器可能是对象.但他们都将提供列如 * 和++等这样的操作.其次每个容器类都有个超尾标记,当迭代器递增到容器最后一个值的后面的时候把这个值赋值给迭代器.每个容器类都有begin()和end()方法,begin返回指向第一个元素的迭代器,end返回一个指向超尾的迭代器.每个容器类都有++操作用来遍历容器 尽是废话 使用迭代器的时候不用管他是咋实现的..知道咋用就成(如果你碰到需要自己定义迭代器的情况当我放屁):观众: 你智障吧废话谁踏马不会用这玩意 哎呦你咋知道的…没错我就是智障.在知道了迭代器大概是咋构造的之后的我们可以更加自信满满的并没有雄赳赳气昂昂跨..啊不..写出迭代器:123vector<double>::iterator pr; //这玩意是输出vector<double>里的元素for(pr = sco.begin(); pr != sco.end(); pr++) cout << *pr << endl; 这里本萌妹使用了C艹11,做一个炫酷的紧跟潮流个屁的智障: 12for(auto pr = sco.begin(); pr != sco.end(); pr++) cout << *pr << endl; 作为一种编程风格,最好直接使用STL函数来处理细节,如for_each(),或C艹11的迷之基于范围的for循环:1for(auto x: sco) cout << x << endl; 迭代器类型 STL定义了五中迭代器,分别是: 输入迭代器,输出迭代器,正向迭代器,双向迭代器,随机访问迭代器;他们都可以执行解引用操作 输入迭代器: 从程序的角度来说,即来自容器内部的讯息被视为输入,因此输入迭代器可被程序用来读取容器中的信息,但不一定能让程序修改容器里的值. 输入迭代器必须能访问容器中所有的值(废话),可以通过++运算符来实现(废话).但输入迭代器并不保证第二次遍历容器时顺序不变,另外当迭代器被递增后,也不保证先前的值可以被解引用.基于输入迭代器的算法都应该是单通行(single-pass)的,输入迭代器是单项迭代器,可以递增,但不能递减. 输出迭代器: 输出指的是将信息从程序传输给迭代器,因此程序的输出就是容器的输入,和输入迭代器差不多,他只能够解引用让程序能修改容器的值,也就是只能写,而不能读. 对于单通行,只读算法可以使用输入迭代器,对于单通行,只写算法,则可以使用输出迭代器. 正向迭代器: 正向迭代器与输入输出迭代器不同的是,他总是按照相同的顺序遍历一系列值.另外正向迭代器递增后仍可以怼前面的迭代器值解引用,并得到相同的值. 既可以读写又可以只读:12int * pirw; //读写const int * pir;//只读 双向迭代器:具有正向迭代器全部特征,并且支持(前后缀)递减运算符; 随机迭代器:具有全部双向迭代器的特征,并且能够根据要求直接跳到容器中任何一个元素; 概念,改进和模型(完全不知道书上在说啥)下面是咬文嚼字时间 迭代器是一系列要求,而不是类型.STL算法可以使用任何满足要求的迭代器所实现,STL术语叫概念(concept)来描述一系列要求.概念可以具有继承的关系,例如双向迭代器继承了正向迭代器的功能,然而不能将继承机制用于迭代器: 假设我们将双向迭代器实现为一个常规指针,而指针属于C++内置类型,不能从派生而来.但从概念上将他确实能够继承.所以有些STL文献使用术语改进(refinement)来表示这种概念上的继承.so,双向迭代器是正向迭代器概念的一种改进.而概念的具体实现叫做模型(model). 将指针用于迭代器 迭代器是广义指针,指针满足所有迭代器要求,因此STL算法可以使用指针来对基于指针的非STL同期进行操作其实就是可以将STL用于数组…..123const int SIZE = 100;double Rec[SIZE];sort(Rec,Rec+100); sort()函数接受指向容器第一个元素的迭代器和指向超尾的迭代器作为参数.Rec或&Rec[0]是第一个元素的地址,Rec+SIZE是最后一个元素后面的元素的地址;由于指针是迭代器,而算法是基于迭代器的,所以可将STL算法用于常规数组. 假设要把信息输出到显示器上,可以使用一个表示输出的迭代器则可以使用copy();STL有一个ostream_iterator模板是输出迭代器的一个概念模型:123#include <iterator>...ostream_iterator<int,char> out_iter(cout," "); //注意这双引号里有个空格 out_iter是个接口,能够使用cout来显示信息,第一个模板参数指出被发送给输出流的数据类型,第二个参数(car)值出了输出流使用的字符串类型; 将copy()用于迭代器:12345copy(dice.begin(),dice.end(),out_iter);//这意味着将dice容器整个区间复制到输出流中,即显示容器里的内容//也可以直接构建个匿名的迭代器:copy(dice.begin(),dice.end(), osteram_iterator<int,char>(cout," ")); 这样用表示将由15和空格组成的字符串发送到cout的输出流中:1*cout_iter++ = 15; // 和 cout << 15 << " "; 一样 有输出就有输入,可以用俩isteram_iterator模板对象来定义copy()的输入范围,如下,isteram_iterator的第一个参数(int)是指出要读取的数据类型,第二个参数指出输入流使用的字符串类型(char),构造函数参数cin意味着使用由cin管理的输入流,构造函数参数为空则表示输入失败:12copy(istream_iterator<int,char>(cin), istream_iterator<int,char>(), dice.begin() ); //从输入流中读取,直到失败; 下面的代码演示了如何使用copy和istream迭代器以及反向迭代器:1234567891011121314151617181920212223242526#include<iostream>#include<iterator>#include<vector>using namespace std;int main(){ using namespace std; int casts[10] = {6,7,2,9,4,11,8,7,10,5}; vector<int> dice(10); copy(casts,casts+10,dice.begin()); cout << "原始数据: " << endl; ostream_iterator<int,char> out_iter(cout," "); copy(dice.begin(),dice.end(),out_iter); cout << endl; cout << "使用了反向迭代器重新输出:" << endl; copy(dice.rbegin(),dice.rend(),out_iter); cout << endl; cout << "使用了vector类的reverse_iterator: " << endl; vector<int>::reverse_iterator ri; for(ri = dice.rbegin(); ri != dice.rend(); ++ri) cout << *ri << ' '; cout << endl;return 0;} 但是上述代码是在其已知了dice的大小的情况下进行的,如果不知道容器的大小呢?,并且还是向容器中刚添加元素(而不是像上述代码覆盖已有内容),这种情况下咋整?有三种插入迭代器可以将复制转为插入解决问题,他们使用动态内存分配插入新元素: back_insert_iterator; //将元素插入到容器尾,但只能用于允许在尾部快速插入的容器; (vector符合) front_insert_iterator; //将元素插入到容器前面,但只能用于允许在起始位置做时间固定插入的容器; insert_iterator; //将元素插入到insert_iterator构造函数的参数指定位置之前; 这些迭代器将容器类型作为模板参数,将模板名作为构造函数参数:1back_insert_iterator<vector<int>>back_iter(dice); //为名为dice的vector<int>容器创建个back_insert_iterator 下列程序演示了两种迭代器的使用方法,并且使用for_each()输出:1234567891011121314151617181920212223242526272829#include <iostream>#include <string>#include <iterator>#include <vector>#include <algorithm>using namespace std;void output(const string & s){cout << s << " ";}int main(){ string s1[4] = {"F","A","♂","Q"}; string s2[2] = {"B","L"}; string s3[2] = {"M","J"}; vector<string> words(4); copy(s1,s1+4,words.begin());//words地方够,可以复制进来 for_each(words.begin(), words.end(), output); cout << endl; copy(s2,s2+2,back_insert_iterator<vector<string>>(words));//从后面插♂入 for_each(words.begin(),words.end(), output); cout << endl; copy(s3,s3+2,insert_iterator<vector<string>>(words,words.begin())); //插♂前面 for_each(words.begin(),words.end(), output); cout << endl; return 0;}","tags":[{"name":"笔记","slug":"笔记","permalink":"http://yoursite.com/tags/笔记/"},{"name":"代码","slug":"代码","permalink":"http://yoursite.com/tags/代码/"},{"name":"CPP","slug":"CPP","permalink":"http://yoursite.com/tags/CPP/"}]},{"title":"C++笔记-Vector","date":"2017-03-28T13:34:47.000Z","path":"2017/03/28/C++笔记-Vector/","text":"画师P站ID:61403923 标准库模板 STL提供了一组表示,容器,迭代器,算法,函数对象的模板.容器是一个与数组类似的单元,可以储存若干个类型相同的值.算法用来完成特定任务(如对数组进行排序)的处方;迭代器用来遍历容器里的对象,与能够遍历数组的指针类似,是广义的指针.函数对象是类似于函数的对象,可以是类对象或函数指针. 模板类Vector 可以创建vector对象; 将一个vector对象赋值给另一个,或者使用[]运算符来访问vector中的元素.头文件vector中定义了vector模板举个书上的栗子,输入书名和评分:123456789101112131415161718192021222324252627#include <iostream>#include <string>#include <vector>using namespace std;const int NUM = 5;int main(){ vector<int> vi(NUM); vector<string> vs(NUM); cout << "请输入" << NUM << "个书名和你的评分" << endl; int i = 0; for(;i < NUM;i++) { cout << "#第" << i+1 << "本:"; getline(cin,vs[i]); cout << "\\n 请输入评分:"; cin >> vi[i]; cin.get(); } for(i = 0; i < NUM; i++) { cout << "名字: " << vs[i] << " 评分:" vi[i] << endl; } return 0;} 对vector的操作 size(): 返回容器中的元素数目; swap(): 交换两个容器的内存; begin(): 返回一个指向容器中第一个元素的迭代器; end(): 返回一个表示超过容器尾的迭代器; 什么是迭代器? 他是一个广义的指针(恩没错就是指针),可以对其执行类似指针的操作比如解引用,递增;每个容器都定义了适合的迭代器,一般是名为iterator的typedef,其作用域为整个类.比如声明一个vector的迭代器可以这样做:1234567891011vector<double>::iterator pd; //pd是一个迭代器vector<double> sum;//sum是一个vector对象pd = sum.begin(); //将pd指向sum的第一个元素*pd = 22.3; //对pd解引用,把22.3赋值给pd所指向的元素(也就是sum的第一个元素);++pd; //使pd指向下一个元素//另外可以不这么写: vector<double>::iterator pd = sum.begin();//而是这样做:auto pd = sum.begin(); //C++11 前面说过超过容器尾的迭代器,那么什么是超过结尾呢(past-the-end)?它是一种迭代器,指向容器最后一个元素后面的那个元素的指针.end()成员函数表示超过结尾的位置,如果将迭代器设为容器的第一个元素,然后自加.则最终可将它到达容器结尾,从而遍历整个容器.12for(pd=sum.begin(); pd != sum.end(); pd++)cout << *pd << endl; push_back()是一个方便的方法,他讲元素添加到矢量末尾,这样他讲负责管理内存,增加矢量的长♂度,使之容纳下新成员:12345vector<double> sco;double temp;while(cin >> temp && temp >= 0) sco.push_back(temp); //只要有足够的内存,程序可以根据需要增加sco的长度 cout<< "你输入了:" << sco.size() << "个元素"; erase()方法可以删除是两种给定区间的元素.他接受两个迭代器的参数,这俩参数定义了要删除的区间.第一个迭代器指向区间的起始处,第二个指向区间的末尾.例如下列代码删除了[sco.bgein(),sco.begin()+2)区间内的元素:1sco.erase(sco.begin(),sco.begin()+2); inster()方法的功能与erase()相反,他接受三个迭代器参数,第一个指向了新元素的插入位置,第二三个迭代器定义了被插入区间,这个区间通常是另一个容器对象的一部分;就是把A容器的一部分复制出来,插入到B容器的某一位置;列入下列代码将new_v中的除了第一个元素之外所有的元素查到old_v矢量的第一个元素前面:1234vector<int> old_v;vector<int> new_v;old_v.inster(old_v.begin(),new_v.begin()+1,new_v.end()); 对Vector的其他操作 矢量模板并不包括如搜索,排序,随机排序等.STL从更广泛的角度定义了非成员函数(non-member)来执行这些操作.但有的时候,即使有执行相同任务的非成员函数,STL还会定义一个成员函数,因为有的操作使用特定的算法比使用通用的算法效率更高.比如Vector的swap()效率比非函数成员的swap()高;但非函数成员能交换俩类型不同的容器的内容; 三个具有代表性的STL函数: for_each(); fandom_shuffle(); sort(); for_each()接受三个参数,前两个是定义容器中区间的迭代器,最后那个是指向函数的指针(函数对象).被指向的函数不能修改容器元素的值,这玩意可以代替for用:12345678void ShowStr(const string &str){ cout << str << endl;}vector<string> books;vector<string>::iterator pr;for(pr = books.begin(); pr != books.end(),pr++) //正常的for ShowStr(*pr); //上面的for可替换为: for_each(books.begin(),books.end(),ShowStr) //这样可以避免显示使用迭代器 random_suffle()该函数接受两个指定区间的迭代器参数,并随机排列该区间的元素,前提是该函数要求可以对容器任意访问,显然vector满足:1random_shuffle(books.begin(),books.end()); sort()函数也要求容器资呲随机访问.该函数有俩重载,一个接受两个定义区间的迭代器参数,并使用容器定义的<运算符容器内容进行升序排序.12 vector<int> myint; sort(myint.begin(),myint.end()); 但如果容器元素是用户自定义的,那么则要给你自定义的类填个<的重载,也就是operator<()..比如我自定义了个结构或类Rev:12345678910111213struct { string title; int rating;};bool operator<(const Rev & rl, const Rev & r2){ if(r1.title < r2.title) return true; eles if(r1.title == r2.title && r1.rating < r2.rating) return true; else return false;} 然后就可以对包含Review对象(如books)的矢量进行排序了:1sort(books.begin(),books.end()); 上述程序是按照title成员的字母顺序排序的,如果title一样就按rating排序.如果想降序排序或者按照rating排序,可以使用另一种格式的sort().他接受三个参数,前两个参数也是指定区,间的迭代器,最后一个参数指向要使用的函数的指针(函数对象) 说白了就是函数名 而不去使用operator<().:123456bool WorseThan(const Rev & r1, const Rev & r2){ if(r1.rating < r2.rating) return true; eles return false;} 有了这函数之后就可以….将Rev对象的books矢量按照rating升序排列(你不说降序么)..:1sort(books.begin(),books.end(),WorseThan); 上述代码与operator<()相比,WorseThan()函数对Rev的排序工作不是那么完整,WorsThan()中如果俩对象的title一样,则视为相同,这叫完整弱排序(strict weak ordering).而operator()中如果俩对象的title一样则比较rating,这叫全排序(total ordering); 基于范围的for循环 基于范围的for循环是为了STL而设计的:123double prices[5] = {4.66,10.99,6.54,6.55,8.48};for(double x : prices) cout << x << endl; 在这种for循环中,括号内的代码声明一个与容里内容类型相同的变量,然后指出了容器名称,循环将使用指定的变量x依次访问容器的每个元素…. 比如 for_each(books.begin(),books.end(),ShowStr);可以写成:1for(auto x : books) ShowStr(x); //根据books将推断出x的类型为Rev 不同于for_each(),基于范围的for循环能修改容器里的内容,你只需要指定一个引用参数:12void reRev(Rev & r){r.rating++;}for(auto & x: books) reRev(x);","tags":[{"name":"笔记","slug":"笔记","permalink":"http://yoursite.com/tags/笔记/"},{"name":"代码","slug":"代码","permalink":"http://yoursite.com/tags/代码/"},{"name":"CPP","slug":"CPP","permalink":"http://yoursite.com/tags/CPP/"}]},{"title":"C++笔记-智能指针","date":"2017-03-26T14:45:49.000Z","path":"2017/03/26/C++笔记-智能指针/","text":"封面来源 使用智能指针 C++有三个智能指针模板(auto_ptr,unique_ptr,shared_ptr),都定义了类似于指针的对象,可以将new获得的地址赋值给这种对象.(其中auto_ptr是C++98提供的解决方案,C++11已经摒弃,不建议使用).当智能指针过期时,其析构函数将使用delete来释放内存.另外share_ptr和unique_ptr的行为与auto_ptr相同. 要使用智能指针首先包含头文件memory.1auto_ptr<double> pd(new double); new double是new返回的指针,指向新分配的内存块.他是构造函数auto_ptr<double>的参数.即对应于原型中的形参p的实参.同样new double也是构造函数的实参.其他两种智能指针的写法:12unique_ptr<double> pdu(new double);shared_ptr<string> pss(new string); 下列代码举例了全部三种智能指针:123456789101112131415161718192021222324252627282930#include <iostream>#include <string>#include <memory>using namespace std;class Report{private: string str;public: Report(const string s) : str(s) {cout << "Objectt created:" << endl;} ~Report() {cout << "Object deleted" << endl;} void comment() const {cout << str << endl;}};int main(){ { auto_ptr<Report> ps(new Report("auto_ptr")); ps->comment(); } { shared_ptr<Report> ps(new Report("shared_ptr")); ps->comment(); } { unique_ptr<Report> ps(new Report("unique_ptr")); ps->comment(); } return 0;} 智能指针很多地方和正常指针类似,可以对他进行解引用操作( *p),用它来访问成员(p->fun),将他赋值给指向相同类型的常规指针,或者赋值给另一个同类型的只能指针; unique_ptr为何优于auto_ptrauto:123auto_ptr<string> p1(new string("auto"));auto_ptr<string> p2;p2 = p1; 在第三句,p2接管string对象所有权后,p1的所有权被剥夺,但如果程序随后视图使用p1,则是件坏事因为p1已经不再指向有效的数据;unique_ptr:123unique_ptr<string> p3(new string("auto"));unique_ptr<string> p4;p4 = p3; 编译器认为第三句非法,避免了p3不再指向有效数据的问题; 但有时候将一个智能指针赋值给另一个不会出现悬挂指针的危险:12345678unique_ptr<string> demo(const char * s){unique_ptr<string> temp(new string);return temp;}...unique_ptr<string>ps;ps = demo("2333333"); demo()返回一个临时unique_ptr,然后ps接管了demo的临时返回值,ps拥有了string对象的所有权.因为demo返回的临时对象很快就会被销毁.所以没有机会使用它来访问无效的数据,所以编译器允许这种赋值.所以就是,如果源unique_ptr是个临时右值,编译器将允许赋值,但如果源unique_ptr将存在一段时间,编译器将禁止这样做..….. 如果一定要写类似于p3p4那样的代码.要安全的使用指针的话,可以给他赋新值,C++有个标准库函数std::move(),能够让你将一个unique_ptr赋给另一个.:12345using namespace std;unique_ptr<string> ps1, ps2;ps1 = demo("23333");ps2 = move(ps1);cout << *ps2 << *ps1 << endl; unique_ptr还可以当数组用:1unique_ptr<double[]>pda(new double(5)); 为啥unique_ptr能知道安全和不安全的用法呢? 因为它使用了C++11的移动构造函数和右值引用…. 选择智能指针 如果程序要使用多个指向同一个对象的指针,应选择shared_ptr,必去有个指针数组,并使用一些辅助指针来标示特定的元素(比如最大值最小值);比如两个对象都指向第三个对象的指针; stl容器;这些操作都可以使用shared_ptr;但不能用unique_ptr和auto_ptr; 如果程序不需要多个指向同一个对象的指针,则可以使用unique_ptr.如果函数使用new分配内存并返回指向该内存的指针.则其返回类型声明为unique_ptr是个不错的选择;这样所有权将转让给接受返回值的unique_ptr,而该智能指针将负责调用delete;12345678910111213141516unique_ptr<int> make_int(int n){ return unique_ptr<int> (new int (n)); }void show(unique_ptr<int> & pi){ cout << *a << " ";}int main(){ vector<unique_ptr<int>> vp(size); for(jint i = 0; i < vp.size(); i++) vp[i] = make_int(rand() % 1000); vp.push_back(make_int (rand() % 1000)); for_each(vp.begin(),vp.end(),show());} 其中的push_back()调用没毛病,因为他返回一个临时的unique_ptr对象.另外如果安置而不是按引用给show()传递对象,则for_each()将会非法,因为这将导致使用一个来自vp的非临时unique_ptr初始化pi,前面说过,这是不行的. 在unique_ptr为右值的时候,可将其赋值给shared_ptr,要求和赋值给另一个unique_ptr一样:123unique_ptr<int> pup(make_int(rand%1000)); //假设make_int返回类型是unique_ptrshared_ptr<int> spp(pup);shared_ptr<int> spr(make_int(rand() % 1000)); //假设make_int返回类型是unique_ptr 模板shared_ptr有个显示构造函数,可将右值的unique_ptr转化为shared_ptr.shared_ptr将接管原来的unique_ptr所有的对象;","tags":[{"name":"笔记","slug":"笔记","permalink":"http://yoursite.com/tags/笔记/"},{"name":"代码","slug":"代码","permalink":"http://yoursite.com/tags/代码/"},{"name":"CPP","slug":"CPP","permalink":"http://yoursite.com/tags/CPP/"}]},{"title":"C++笔记-string类","date":"2017-03-25T14:51:53.000Z","path":"2017/03/25/C++笔记-string类/","text":"string类的构造函数:size_type是一个依赖于实现的整形在头文件string中定义的.string类将string::npos定义为字符串的最大长度,通常为unsigned int的最大值.NBTS(null-terminated-string)来表示空字符结束的字符串.12345678910111213141516string(const char *s); //将string对象初始化为s指向的NBTSstring(size_type n, char c) //创建一个包含n个元素的string对象,其中每个都被初始化为cstring(const string &str) //将一个string对象初始化为string对象str(复制构造函数)string() //创建一个长度为0的默认string对象string(const char * s,size_type n) //创建一个裁剪了s的前n个字符串的对象.template<class Iter>string(Iter begin, Iter end) //将一个string对象初始化为区间[begin,end)内的字符串,begin和end就像指针,用于指定位置string(const string & str, string size_type pos = 0, size_type n = npos)//将一个string对象初始化为对象str中从位置pos开始到结尾的字符,或从pos的位置到第n个字符串;string(string && str) noexcept//C++11新增,将一个string对象初始化为string对象str,并且可能修改str(移动构造函数)string(initializer_list<char>il) //C++11新增,将一个string对象初始化为列表il中的字符 第五个构造函数将一个C-风格字符串和一个整数作为参数,其中的整数参数来表示要复制多少个字符串(如果20被改成40则将会继续复制字符串..将导致十五个无用的字符串被复制到five的结尾处):12char alls[] = "All's well that ends well";string five(alls,20);//这里只是用了前二十个字符来初始化five对象. 第六个构造函数有一个模板参数:1template<class Iter>string(Iter begin,Iter end); begin和end想指针那样指向内存中的两个位置,构造函数将使用begin和end之间的值对string对象初始化.1string six(alls+6,alls+10) //six被初始化为well; 在这里数组名相当于指针,所以alls+6和alls+10的类型都是char,因此类型char将替换Iter.第一个参数指向数组(从零开始)alls中的第六个字符串(w),第二个参数指向alls的第十个(well后面的空格); 现在假设要用该构造函数将对象初始化为另一个string对象(假设为five)的一部分内容,则下面这句不管用:1string seven(five+6,five+10); 原因在于对象名并不是数组名,所以five不是个指针,但five[6]是一个char值,&five[6]则是个地址,因此可以被用作该构造函数的一个参数:1string seven(&five[6],&five[10]); 第七个构造函数将一个string对象的部分复制到构造的对象中:1string eight(four,7,16);//从four的第八个字符开始将16个字符复制到eight中 C++11新增的构造函数: 构造函数string(string && str)类似于复制构造函数,导致新的string为str的副本,但跟复制构造函数不一样的是,他不保证将str视为const.这种构造函数被称为移动构造函数(move constructor). 构造函数string(initialzer_list)使得下面的声明是合法的:12string p = {'L','U','C','K'};string c{'L','U','C','K'}; string类输入: 对于string有两种输入方式:123456string stuff;cin >> stuff;getline(cin,stuff);//getline有个可选参数,用于指定使用哪个字符串来确定输入的边界:getline(stuff,':'); //string的getline会自动调整大小使得正好容得下输入的字符串' 虽然string的getline能自动调节大小,但是有一些限制,如过想要读取一个文本,那么string对象所允许的最大长度(大小为unsigned int的值)可能不够. string的getlin从输入中读取字符串并将其储存到目标string中,直到发生下面三种情况之一: 到达文件尾. 遇到分界字符(如 \\n) 读取的字符数达到最大值; 使用字符串 可以比较字符串.string类对于全部六个关系运算符都进行了重载.如果在机器排列序列中,一个对象位于另一个对象的前面,则前者小于后者.如果机器排列序列为ASCII码,则数字江小鱼大写字符,大写字符小于小写字符.12345678910string str1 = "cobra";string str2 = "coral";string str3[20] = "anaconda";if(str1 < str2) ...if(str1 == str3) ...if(str3 != str2) ...//可以确定字符串的长度.size()和length()成员函数都返回字符串中的字符数:if(str1.length() == str2.size()) ... 可以以多种方式在字符串中搜索给定的字符串或字符.重载的find方法:12345678size_type find(const string & str, size_type pos = 0) const;//从字符串的pos位置开始,查找字符串str,找到了则返回该字符串首次出现时其首字符的索引,没有就返回string::npossize_type find(const char* s,size_type pos = 0) const; //同上size_type find(char ch, size_type pos = 0)const; //同上size_type find(const char* s,size_type pos = 0, size_type n);//从字符串的pos开始,查找s的前n个字符串组成的子字符串.找到则返回子字符串首次出现的首字符的索引,否则返回string::npos 除此之外还有: rfind()方法查找子字符串或字符串最后一次出现的位置; find_first_of()方法在字符串中查找参数中任何一个字符串首次出现的位置; find_last_of()方法用法相同,查找的是最后一次出现的位置 find_first_not_of()方法在字符串中查找第一个不包含在参数中的字符串","tags":[{"name":"笔记","slug":"笔记","permalink":"http://yoursite.com/tags/笔记/"},{"name":"代码","slug":"代码","permalink":"http://yoursite.com/tags/代码/"},{"name":"CPP","slug":"CPP","permalink":"http://yoursite.com/tags/CPP/"}]},{"title":"C++笔记-类型转换运算符&友元,异常の总结","date":"2017-03-23T14:51:04.000Z","path":"2017/03/23/C++笔记-类型转换运算符&友元,异常的总结/","text":"图片来源 类型转换运算符 C++对于类型转换采取更严格的限制.并添加了四个类型转换运算符: dynamic_cast const_cast static_cast reinterpret_cast; dynamic_cast运算符前面介绍过.假设有俩类High和Low,而ph和pl的类型分别为High和Low,则仅当Low是High的可访问基类(直接或者间接)时,下述语句才能将Low*指针赋给pl:1pl = dynamic_cast<Low*>ph; 该运算符的作用是能够在类层次结构中进行向上类型转换. const_cast运算符只有一种用途的类型转换,即改变值为const或volatile(去掉/增加const/volatile特性):1const_cast<type-name>(expression) 如果类型的其他地方也被修改则上述类型将出错.除了cosnt或volatile特征(有或无)可以不同外,type_name和expression的类型必须相同.再次假设High和Low:12345High bar;const High * pbar = &bar;...High * pb = const_cast<High*>(pbar); //没毛病,将删除pbar的const标签const Low * pl = const_cast<const Low*>(pbar);//不行,因为他尝试将const High*改为const Low* 然而其实也可以不用const_cast:12345High bar;const High * pbar = &bar;...High * pb = (High *)(pbar);Low * pl = (Low *)(pbar); 但修改const值的结果可能是不确定的,请看示例:123456789101112131415161718void change(const int * pt, int n){ int *pc; pc = const_cast<int * >(pt); *pc += n;}int main(){ int popl = 38383; const int pop2 = 2000; cout << "pop1,pop2: " << pop1 << "," << pop2 << endl; change(&pop1,-103); change(&pop2,-103); cout<< "pop1,pop2"<< pop1 << "," << pop2 << endl; return 0;} 调用change()时修改了pop1,但没有修改pop2.在change()中,虽然指针pt被声明为const int ,但const_cast去掉了pt的const标签,并赋给了pc,所以pc能修改pop1的值.但仅当指针指向的值不是const时才可行,所以pc不能修改pop2的值. *static_cast语法:1static_cast<type-name>(expression) 仅当typename能被隐式的转换成expression所属类型或expression能被隐式的转换成typename所属的类型的时候,上述转换才是合法的.假设Low是High的基类,而P是一个无关的类,则从High转换到Low,或者从Low转换到High的时候是合法的,而从Low转到p则是非法的.123456High bar;Low blow; High * pb = static_cast<High*>(&blow); //可以 Low * pl = static_cast<Low *>(&bar); //可以 P * pmer = static_cast<Pond *>(&blow) //不行 reinterprete_cast几乎能资呲所有的类型转换,比如可以将指针类型转化为能存下这个指针的整形,但不能将指针转化为更小的整形或浮点型.并且不能讲函数指针转化为数据指针,且不能去掉const标签;语法:1reinterpret_cast<type-name>(expression); 示例:1234struct dat(short a, short b);long value = 0xA224B118;dat * pd = reinterpret_cast<dat *>(&value);cout << hex << pd->a;//显示 前两个字节的值 友元,异常总结 类可以将其它函数,其他类的程序作为自己的友元.在一些情况下需要使用向前声明.并需要注意正确的组合类和方法的顺序; 嵌套类是声明在其他类中的类,但不比是其公有接口的组成部分. 当异常触发时,程序将控制权转交给匹配的catch块,catch块里面的是解决异常或终止程序的代码,在catch块之前的是try块,直接或间接导致异常的函数调用必须放在try块中. RTTI可以检测对象的类型.dynamic_cast运算符可以用于将派生类指针转化为基类指针,其可以安全的调用虚函数.typeid运算符返回一个type_info对象.可以对两个typeid的返回值进行比较看看是不是特定的类型.而type_info对象可用于获得关于对象的信息 而dynamic_cast,static_cast,const_cast,和retinterpret_cast提供了安全的明确的类型转换.","tags":[{"name":"笔记","slug":"笔记","permalink":"http://yoursite.com/tags/笔记/"},{"name":"代码","slug":"代码","permalink":"http://yoursite.com/tags/代码/"},{"name":"CPP","slug":"CPP","permalink":"http://yoursite.com/tags/CPP/"}]},{"title":"C++笔记-RTTI(运行阶段类型识别:Runtime Type Identification)","date":"2017-03-20T14:53:04.000Z","path":"2017/03/20/C++笔记-RTTI/","text":"霞之丘诗羽 RTTI这个听上去银瓶乍破水浆迸铁骑突出刀枪鸣的名字正是我大C艹的运行阶段类型识别(Runtime Type Identification)的简称 RTTI可以通过基类的指针和引用来检测这些指针和引用所指向的派生类对象的具体类型. 假设有一个类层次结构,其中的类都是从一个基类派生而来的,则可以让基类指针指向其中的任何一个类的对象.在处理一些信息之后,选则一个类,并创建这种类型对象,然后返回这个对象的地址,而这个地址可以赋值给基类指针,那么如何确定这个对象的类型? 只有知道了类型,才可能调用类方法的正确版本,如果在这个类结构层次中,所有的成员都拥有虚函数,在这个时候就不需要知道对象的类型.但派生类可能包含一些新的方法,在这种情况下只有某些类型可以使用该方法.这时候就可以使用RTTI提供解决方案. dynamic_cast 运算符: 这个运算符可以检测是否可以安全的将对象的地址赋值给特定的类型指针. 假设有下列类层次结构:12345678910111213class Grand{};class Superb: public Grand(){}class Magn : public Superb(){}//假设有下列指针:Grand * pg = new Grand;Grand * ps = new Superb;Grand * pm = new Magn;//假设有下列类型转换,那么谁是比较安全的?:Magn * p1 = (Magn *) pm; //安全,因为相同类型的指针指向了相同类型的对象.Magn * p2 = (Magn *) pg; //不安全,因为派生类指向基类,而派生类可能有些方法是基类没有的.Superb * p3 = (Magn *) pm;//安全,因为基类指向派生类. 所以,问题”指针指向的是那种类型”和”类型转换是否安全”,类型是否安全更通用点.原因在于:要调用类方法.类型并不一定要完全匹配,儿科一是定义了方法的与你版本的基类类型. dynamic_cast语法:12 Superb * pm = dynamic_cast<Superb *>(pg); //指针pg的类型如果可以被安全的转换为Superb*则运算符将返回对象的地址,否则返回一个空指针 下列代码演示了这种处理,首先他定义了三个类.Grand类定义了一个虚函数Speak(),并且其他类都重定义了这个虚函数,Superb类定义了一个虚函数Say(),而Magn也重定义了他.程序定义了GetOne()函数用来随机创建这三种类中的某种类对象,并对其初始化,然后将地址作为Grand*指针返回并赋给pg.然后使用pg调用Speak().因为这个函数是虚的随意代码能够正确的调用指向的对象的Speak()版本.1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556class Grand{private: int hold;public: Grand(int h = 0) : hold(h){} virtual void Speak() const{cout << "Grand" << endl;} virtual int Value() const{return hold;}};class Superb : public Grand{public: Superb(int h = 0) : Grand(h){} void Speak() const {cout << "Superb" << endl;} virtual void Say() const{cout << "superb value of " << Value() << endl;}};class Magn : public Superb{private: char ch;public: Magn(int h = 0,char c = 'A') : Superb(h),ch(c){} void Speak() const {cout << "Magn" << endl;} void Say() const{cout << "I hold the character " << ch << "and the integer " << Value() << endl;}};Grand * GetOne(){ Grand * p; switch(std::rand() % 3) { case 0 : p = new Grand(std::rand() % 100); break; case 1 : p = new Superb(std::rand() % 100); break; case 2 : p = new Magn(std::rand() % 100,'A' + std::rand() % 26); break; } return p;}int main(){ std::srand(std::time(0)); Grand * pg; Superb * ps; for(int i = 0; i < 5; i++) { pg = GetOne(); pg->Speak(); if(ps = dynamic_cast<Superb *>(pg)) //如果类型转换成功,则ps为非零.失败则返回空指针0. ps->Say(); } return 0;} typeid运算符和type_info类 typeid运算符使得能够确定两个对象是否为同种类型.他接受两种参数: 类名 结果为对象的表达式 typeid运算符返回一个对type_info对象的引用,其中type_info是在头文件typeinfo中定义的一个类.这个类重载了==和!=运算符,以便可以使用这些运算符来对类型进行比较.1typeid(Magn) == typeid(*pg); //如果pg指向的是一个Magn对象,则表达式结果为bool值.true,否则为false. 如果pg是一个空指针,则程序将引发bad_tyoeid异常.type_info包含了一个name()方法,该函数返回一个随着实现而异的字符串…通常是类名.1 cout<< "Class Name:" << tyoeid(*pg).name() << endl; 有瑕疵的RTTI例子: 不讨论大家对RTTI的争论,介绍一个应该避免的编程方式:123456789 Grand * pg;Superb * ps;for(int i = 0; i < 5; i++){ pg = GetOne(); pg->Speak(); if(ps = dynamic_cast<Superb *>(pg)) //如果类型转换成功,则ps为非零.失败则返回空指针0. ps->Say();} 通过不使用dynamic_cast和虚函数,而使用typeid(),可将上述代码重写为:1234567891011121314151617181920Grand * pg;Superb * ps;for(int i = 0; i < 5; i++){ pg = GetOne(); if(typeid(Magn) == typeid(*pg)) { pm = (Magn*) pg; pm->Speak(); pm->Say(); } else if(typeid(Superb) == typeid(*pg)) { pm = (Superb*) pg; pm->Speak(); pm->Say(); } else pg->Speak();} 上述代码不仅sb而且还有毛病..如果从Magn类派生出一个Insu的类,而后者需要重新定义Speak()和Say().则必须修改for循环,添加一个else if.但下面的语句适合所有从Grand派生出的类:1234pg->Speak();//下面语句适合所有从Superb派生而来的类:if(ps = dynamic_cast<Superb *>(pg))ps->Say(); 所以说如果发现ifelse中使用了typeid,则应该考虑是否使用虚函数和dynamic_cast","tags":[{"name":"笔记","slug":"笔记","permalink":"http://yoursite.com/tags/笔记/"},{"name":"代码","slug":"代码","permalink":"http://yoursite.com/tags/代码/"},{"name":"CPP","slug":"CPP","permalink":"http://yoursite.com/tags/CPP/"}]},{"title":"C++笔记-异常(3)","date":"2017-03-19T14:51:16.000Z","path":"2017/03/19/C++笔记-异常(3)/","text":"图片来源 异常何时会迷失方向异常被引发之后,有两种情况会导致问题: 意外异常(unexpected exception): 如果异常是在带异常规范的函数中引发的,则必须与规范列表中的某种异常匹配(在继承层次结构中,类类型与这个类机器派生类的对象匹配)就是得有个catch块能跟你写的异常符合/能接受你那异常,否则则成为意外异常.(C++11已经摒弃这东西了,然而有的代码还在用它) 举个正确的栗子: 通过给函数制定异常规范,可以让函数的用户知道要捕获那些异常.放屁,真水1234567891011 double Argh(doouble,double) throw(out_of_bounds) { ... try{ x = Argh(a,b); } catch(out_of_bounds & ex){ ... } .. } 未捕获异常(uncaught exception): 如果异常不是在函数中引发,则必须捕获他.如果没有捕获(在没有try块或没有catch块时,将出现) 在默认情况下,出现上述情况程序将被终止,然而可以修改程序对上述异常的反应…未捕获异常不会导致程序立刻停止,首先会调用terminate()函数,terminate()会调用abort()函数,但可以修改terminate()调用的函数(不让他调用abort()).可以使用set_terminate()函数来修改.(在#include中):12345typedef void (*terminate_handler)();terminate_handler set_terminate(terminate_handler f) throw(); //C++ 98terminate_handler set_terminate(terminate_handler f) noexcept; //C++ 11void terminate(); //C++ 98void terminate() noexcept; // C++ 11 typedef使得terminate_handler成为一个指向没有参数和返回值的函数的指针.set_terminate()将这个不带任何参数且返回类型为void的函数的名字(地址)作为参数,并且返回这个函数的地址,如果set_terminate()被多次调用,则terminate()将调用最后一次set_terminate调用设置的函数. 举个栗子:一个未被捕获的异常导致程序打印一条消息,然后调用exit()函数.12345678910111213#include<exception>using namespace std;void myQuit(){ cout << "由于出现未捕获异常,程序终止"; exit(5);}int main{ set_terminate(fun);//如果出现未捕获异常,将终止操作指定为调用fun(); throw; //触发未捕获异常} 现在如果程序引发未捕获异常,则将调用terminate(),而terminate()将会调用myQuit(). 原则上,异常规范应该包含函数调用的其他函数所引发的异常.比如A()调用了B(),而B()可能引发retor对象异常,则A(),B()的异常规范中都应该包含retort. 如果函数引发了其异常规范中没有的异常呢?这样处理起来就比较繁琐,所以C++11也将其摒弃.所以说这玩意咋看都像是坑 那么在这种情况下,行为与未捕获异常极其相似,程序将调用unexpected().这函数将调用terminate(),后者在默认情况下调用Abort().跟terminate()一样,有一个可以修改其行为的set_terminate()一样,也有一个用于修改unexpected()的set_unexpected():12345typedef void (*unexpected_handler)();unexpected_handler set_unexpected(unexpected_handler f) throw(); //C++98unexpected_handler set_unexpected(unexpected_handler f) noexcept; //C++11void unexpected(); //C++98void unexpected() noexcept;//C++0x 然而set_unexpected()比set_terminate()更加严格,unexpected_handler函数可以: 通过调用terminate()(默认行为).abort(),exit()等来终止程序 引发异常 如果新引发的异常原来的相匹配,则程序将开始寻找引发新异常的函数规范; 如果新引发的异常跟原来的不一样,且异常规范中没包括sed::bad_exception则将调用terminate(). 如果新引发的异常跟原来的不一样,且异常规范中包括了sed::bad_exception则不匹配的异常会被sed::bad_exception异常取代. 有关异常的注意事项 使用异常会降低程序运行速度.(废话 异常规范不适用于模板. 因为模板函数引发的异常随特定的具体化而异. 异常和动态内存分配并非总能协同工作 动态内存分配和异常:正常:1234567void fun(){ string mesg("Boy Next Door"); ... if(..) throw exception(); ... return;} 当函数结束时,将为mesg调用string的析构函数,虽然throw过早的终止了函数,但因为栈解退的存在使得析构函数仍然被调用完成清理. 有瑕疵:12345678void fun(int n){ string * mesg = new string[n]; ... if(..) throw exception(); ... delete [] mesg; return;} 这里有个瑕疵: 当栈解退时,将删除栈中的变量mesg,但函数过早的终止意味着句尾的delete [] mesg;被忽略.指针虽然没了,但内存还没被释放且不可访问…这就容易造成一些问题.. 修改版:123456789101112131415void fun(int n){ string * mesg = new string[n]; ... try{ if(..) throw exception(); //捕获异常 } catch(exception & e){ delete [] mesg; //清理 throw; //重新引发 } ... delete [] mesg; return;} 然而可以用智能指针解决该问题","tags":[{"name":"笔记","slug":"笔记","permalink":"http://yoursite.com/tags/笔记/"},{"name":"代码","slug":"代码","permalink":"http://yoursite.com/tags/代码/"},{"name":"CPP","slug":"CPP","permalink":"http://yoursite.com/tags/CPP/"}]},{"title":"C++笔记-异常(2)","date":"2017-03-14T14:46:57.000Z","path":"2017/03/14/C++笔记-异常(2)/","text":"封面来源 其他异常特型(一些碎碎念 虽然throw-catch机制类似于函数参数和返回机制,但还是有所不同. 其一便是函数中的返回语句是将控制权返回到调用函数的函数,但throw语句将向上寻找,并将控制权返回给一个能够捕获相应异常的try-catch组合. 另一个不同便是,引发异常是编译器总是创建一个临时拷贝.举个栗子:123456789101112131415class problem{...}void fun() throw (problem) // 表示函数只能抛出problem类型的异常.更简单的写法: throw problem(){ if(...){ problem opps; throw oops; }}...try{ super();}catch(problem &p){ // 这里的p指向的是oops的副本而不是其本身,因为函数执行完毕之后 oops当不复存在; ....} 对于接在函数名后面的throw(something): void fun() throw(); //表示fun函数不允许抛出任何异常,即fun函数是异常安全的 void fun() throw(…); //表示fun函数可以抛出任何形式的异常 void fun() throw(exceptionType); // 表示fun函数只能抛出exceptionType类型的异常 使用引用传递参数更重要的原因是,基类可以使用派生类对象.现在假设有个异常类层次结构,冰妖分别处理不同的异常类型,则使用基类引用能够捕获任何异常对象;而使用派生类对象则只能捕获他所属类以及他的派生类的对象. 因为基类可以使会用派生类对象,而且引发异常的对象将被第一个与之匹配的catch块捕获,那么所以catch块的排列顺序应该与派生类顺序相反:这又是一个一不留神就会留下bug的功能123456789101112131415161718class bad_1{...};class bad_2 : public bad_1{..};class bad_3 : public bad_2{...}...void duper(){ ... if(...) throw bad_1(); if(...) throw bad_2(); if(...) throw bad_3();}...try { duper();}catch (bad_3 & b3){...}catch (bad_2 & b2){...}catch (bad_1 & b1){...} 如果将catch(bad_1 & b1)放在最前面,他将捕获 bad_1,bad_2,bad_3, 只能通过相反的顺序排列,bad_3才会被bad_3处理程序所捕获. 所以说,如果有一个异常类继承层次结构,应该这样排列catch块: 将捕获位于层次结构最下面的异常类的catch语句放在最前面,将捕获基类异常的catch语句放在最后面;也就是倒着写 exception类 exception头文件定义了exception类,C++可以把它用作于其他异常类的基类.使代码可以引发exception异常,他有一个名曰what()的虚方法,因为是个虚方法所以你可以根据你的实现重定义他. 12345678910111213#include <exception>class bad_hmean : public std:: exception{ public: const char * what() {return "不要总想搞个大新闻";}}//如果不想以不同的方式处理这些派生类的异常,可以在同一个基类中捕获他们:try{ ...}catch(std::exception & e){ cout << e.what() << endl;} 当然你也可以分开捕获他们,去吧大师球 1.stdexcept异常类 头文件sedexcept定义了几个异常类.比如logic_error和runtime_error类,他们都是以公有集成的方式从exception类继承过来的:12345678910class logic_error: public exception{ public: except logic_error(const string & what_arg);}class runtime_error: public exception{ public: except runtime_error(const string & what_arg);}//注意 这些类的构造函数都接受一个string对象作为参数,他们提供了what()方法并且返回C-风格字符串 这两个新的类又被作为两个派生类系列的基类,其中logic_error类描述了典型的逻辑错误,这些逻辑错误是可以通过合理编程避免的,但还是可能发生.下面每个类的名称指出了他们用于报告的错误类型: logic_error类: domain_error: invalid_argument: length_error out_of_bounds: doormain_error: 数学函数值域(range)和定义域(domain),定义域由函数可能的参数组成,值域由函数可能的返回值组成,函数在输入参数或返回值不在制定范围的情况下将会引发domain_error异常; incalid_argument: 异常incalid_argument指出了给函数传递了一个意料之外的值.这个和定义域(domain)都有点不一样.例如如果希望输入的每个字符串要么是0要么是1,那么当输入的字符串中包含其他字符的时候,incalid_argument会被触发. length_error: 异常length_error指出了由于没有足够的空间类执行所需操作.比如string类的append()方法在合并得到的字符串长度超了的时候; out_of_bounds: 异常out_of_bounds通常用于指示索引错误,比如定义了个数组类,其operator()[]在使用的索引无效时引发out_of_bounds异常 runtime_error类这个类描述了可能在运行期间发生的难以预料的错误: range_error: overflow_error: underflow_error: 下溢(underflow) 存在浮点类型可以表示的最小非零值,当计算结果小于这个值的时候,将导致下溢错误.上溢(overflow) 存在计算结果超过了某种类型能够表示的最大数值时,将导致上溢.对于计算结果可能不在函数允许范围之内,但没有发生上下溢的时候可以用range_error异常; 继承关系可以使程序员一起处理他们(如果你愿意的话): 下面代码分别处理每种异常,先单独捕获out_of_bounds,然后统一不过其他logic_error系列异常,最后统一不过exception异常,runtime_error,以及其他从exception派生而来的异常: 1234567try{...}catch(out_of_bounds & oe){...}//捕获out_of_boundscatch(logic_error & oe){...}//捕获logic_error类catch(exception & oe){...}//捕获runtime_error,exception,和其他从exception派生而来的异常//↑↑如果你很不爽这么做的话可以给runtime_error,exception派生出来俩异常类,使异常类可以归入同一个继承层次中. bad_alloc异常和new 对于使用new导致的内存分配问题,C++比较新的处理方式是让new引发bad_alloc异常,头文件new包含bad_alloc的声明.他是从exception类公有派生而来,但在以前当无法分配请求的内存量时new返回一个空指针. 举个栗子:123456789101112131415161718192021222324252627282930313233343536#include <iostream>#include <new>#include <cstdlib>using namespace std;struct Big{ double stuff[20000];};int main(){ Big* pb; try{ cout << "试着申请一大块儿内存" << endl; pb = new Big[100000]; /*pb = new (std::nothrow) Big[10000]; //这样在内存请求失败的时候会返回空指针 if(){ cout << "请求失败" << endl; exit(EXIT_FAILURE); }*/ cout << "请求通过" << endl; } catch(bad_alloc & ba){ cout << "捕捉到异常" << endl; cout << ba.what() << endl; exit(EXIT_FAILURE); } cout << "成功分配内存" << endl; pb[0].stuff[0] = 4; cout << pb[0].stuff[0] << endl; delete [] pb; return 0;} 如果内存申请失败了则方法what()将会返回字符串std::bad_alloc.(在我的MinGW5.5下返回std::bad_array_new_length)如果你的程序没触发异常清加大请求分配内存量 另外还有一种是在new处理失败时返回空指针的:12int *p = new(std::nothrow) int;int *b = new (std::nowthrow) int[500];","tags":[{"name":"笔记","slug":"笔记","permalink":"http://yoursite.com/tags/笔记/"},{"name":"代码","slug":"代码","permalink":"http://yoursite.com/tags/代码/"},{"name":"CPP","slug":"CPP","permalink":"http://yoursite.com/tags/CPP/"}]},{"title":"C++笔记-异常","date":"2017-03-07T15:14:32.000Z","path":"2017/03/07/C++笔记-异常/","text":"图片来源: 不好意思本智障找不到了.请在p站搜雪染ちさ.. 在(keng)神(te)奇(duo)的C++中, 本智障经常写出一些正常的代码导致我的bug编译不过去. 举个书上的栗子,计算两个数的调和平均数的定义为: 两个数字倒数的平均数, 表达式: 2.0 * x * y / (x + y) 这样的话如果xy互为相反数的情况下岂不是很尴尬? abort() 对于这种问题,处理方式之一是如果检查到xy互为相反数则调用 abort() 函数(abort处于 cstdlib.h ).他会想标准错误流发送 abnormal program termination (程序异常终止),而且返回一个由时间决定的值,告诉操作系统处理失败..当然也可以用exit(),只不过不显示消息而已; 1234567891011121314151617181920212223242526 #include <iostream>#include <cstdlib>double hmean(double a,double b){ if(a == -b) { std::cout << "这俩数有毛病..." << std::endl; //在MinGW 5.3.0 32bit 下不使用abort的情况输入10和-10,调和平均数是 -inf std::abort(); //运行到这儿直接退出 所以不会显示下面bye的那句 } return 2.0 * a * b / (a+b);}int main(){ std::cout<< "输入俩数以计算调和平均数:"; double x, y, z; while(std::cin >>x >> y) { z = hmean(x,y); std::cout<< x <<" 和 "<< y << " 的调和平均数是 " << z << std::endl; std::cout << "输入任意按回车退出" << std::endl; } std::cout << "bye 再次按回车退出."; return 0;} 异常机制对于异常的处理有三个部分: 引发异常(throw) 使用处理程序捕获异常(catch) 使用 try 块(try) throw关键字表示引发异常,紧随其后的值(如字符串或对象)指出异常的特征;catch关键字表示使用异常处理程序(exception handler)捕获异常,括号内的表示异常要处理的类型,花括号内的表示遇到异常所采取的措施,虽然catch长的像个自带定义的函数,然而他并不是.try关键字表示其中的代码可能会出现异常,他后面一般跟着一个或多个catch. 如码所示:1234567891011121314151617181920212223242526#include <iostream>using namespace std;double hmean(double a, double b){ if(a == -b) throw "异常:这俩数有毛病"; //异常被触发 return 2.0 * a * b /(a + b);}int main(){ double x , y, z; cout << "请输入俩数: "; while(cin >> x >> y) { try { z = hmean(x,y); //try块里的表示需要注意这些代码可能触发异常 } catch (const char * s){ //捕捉到异常 程序跳到这儿 发现char类型与 throw后面的字符串匹配 cout << s << endl; //匹配之后执行代码块内的代码处理异常 cout << "请重试: "; continue; } cout << x << " 和 " << y << " 这俩数的调和平均数是: " << z << endl; cout << "输入任意字符串退出: "; } cout << "bye,再按回车退出" << endl;} 现在假设异常被触发,hmean()引发异常,被引发的异常是常量字符串:”异常:这俩数有毛病”,于是throw终止函数hmean()的执行,沿着函数调用序列往后查找,发现hmean()函数是从main()中的try块中调用的,于是throw把控制权返回给main函数,程序将在main里寻找与引发的异常类型所匹配的异常类型处理程序(说白了就是找参数类型跟throw后面的类型一样的catch块),程序找到唯一匹配的参数为char* 的catch块:类似下面的12345catch (const char * s){ //捕捉到异常 程序跳到这儿 发现char类型与 throw后面的字符串匹配 cout << s << endl; //匹配之后执行代码块内的代码处理异常 cout << "请重试: "; continue;} 于是,程序吧字符串:”异常:这俩数有毛病”赋值给s,然后执行catch(const char* s)内的代码.如果函数引发了异常而没有try块或没有匹配的catch时程序将调用abort()函数 将对象作用异常类型通常,引发异常的函数将传递一个对象,这就可以通过不同的异常类型来区分不同的函数在不同的情况下引发的异常,另外对象可以携带信息,同时catch块可以根据这些信息来决定采取什么样的措施.请查看具体代码举个栗子:12345678910111213void hmean(int a, int b) { if(...) throw _Error(a,b); //你就假装_Error是个构造函数并且异常被触发,此时调用构造函数初始化对象并存储参数;}try{ hmean(2,3); //这是个可能触发异常的函数}catch (_Error & e){ e.mesg(); //你就假装这里是调用了_Error类的消息输出方法并告诉你代码有毛病了;} 异常规范和C++11C++98新增了一种不受待见(最好别用这玩意)的异常规范(exception specification),他长这样:12double harm(double a) throw(bad_thing); //可能会抛出bad_thing异常double marm(double) throw(); //不会抛出异常 throw()部分就是异常规范,他可能出现在函数原型和函数定义中,他可以包含类型列表.这玩意的作用之一是告诉用户可能需要使用try块(然而直接写注释更方便),另一作用是让编译器添加执行运行阶段的代码,使劲检测是否违反了异常. C++11资呲一种特殊的异常规范,使用noexcept指出函数不会引发异常,不过对于这个还是存在争议的:1double marn() noexcept; //marn() 不会抛出异常 栈解退其实一张图就可以解释栈解退不过我还是要哔(chao)哔(xi)两句 假设try块没有直接调用引发异常的函数,而是调用了对引发异常函数进行调用的函数,则程序将从引发异常的函数直接跳到包含try块的函数. C++是如何处理函数的调用和返回的? 程序将调用函数的指令的地址(返回地址)放到栈中.当被调用的函数执行完毕之后程序将通过地址来确定从哪里开始继续执行.函数调用将函数参数也放到了栈中,他们被视为自由变量,如果被调用的函数又调用了另一个函数,那么后者的信息也会被添加到栈中,以此类推.当函数结束时,程序流程将跳到调用函数时储存的地址处(也就是返回到调用他的那个函数里),同时栈顶元素被释放,以此类推.并在结束时释放自由变量,如果自动变量是类对象,那么他的析构函数将被调用(如果有析构函数的话). 现在假设异常被触发(程序终止),则程序也将释放栈中的内存,但不会在释放栈的第一个返回地址后停止,而是继续释放栈,直到找到一个位于try块中的返回地址才停止.随后控制权将转到块尾的catch里,而不是调用函数后面的第一条语句,这个个过程被称为栈解退. 然而栈解退有个和函数返回一样的特征. 对于栈中的自动类型对象,类的析构函数将被调用.不同的是,函数返回仅仅处理放在栈中的对象,而throw则是处理try块和throw之间整个函数调用序列放在栈中的对象. 如果没有栈解退这种特性,则引发异常后,对于中间函数调用放在栈中的对象,他们的析构函数不会被调用. (现在上图: throw 与 return","tags":[{"name":"笔记","slug":"笔记","permalink":"http://yoursite.com/tags/笔记/"},{"name":"代码","slug":"代码","permalink":"http://yoursite.com/tags/代码/"},{"name":"CPP","slug":"CPP","permalink":"http://yoursite.com/tags/CPP/"}]},{"title":"C++笔记-友元类","date":"2017-03-05T15:07:46.000Z","path":"2017/03/05/C++笔记-友元类/","text":"类并非只能拥有友元函数还可以将类作为友元,友元被赋予从类外访问类的私有部分的权限,在这种情况下,友元类的所有方法都可以访问原始类的公有/私有/保护成员,也能指定特定的成员函数为一个类的友元. 什么时候希望一个类成为另一个类的友元? 书上举得一个栗子:完整代码 假设编写一个模拟遥控器和电视机的程序,决定有TV和Remote类表示电视机和遥控器,显然这两个类存在着某种关系(净说废话).但电视机不是遥控器,反之亦然,所以正常的公有继承is-a关系并不适用.遥控器并非电视机的一部分.所以,包含私有和保护集成的has-a关系也不适用.但众所周知遥控器可以改变电视机的状态.这说明遥控器应该是电视机类的一个友元. 友元的声明:友元声明可以位于公有,私有或者保护部分,所在其位置无关紧要.但由于Remote类提到了TV类所以必须先定义Tv类,或者使用前向声明.这样来声明一个友元类:1friend class Remote; 1234567891011121314151617181920212223242526272829303132333435363738394041class Tv{public: friend class Remote; enum{off,on}; enum{MinVal,MaxVal = 20}; enum{Antenna,cable}; enum{tv,dvd}; Tv(int s = off,int mc = 125):state(s),volume(5), maxchannel(mc),channel(2),mode(cable),input(tv){} void onoff(){state = (state == tv) ? off: on;} bool ison() const {return state == on;} bool volup(); bool voldown(); void chanup(); void chandown(); void set_mode(){mode = (mode = Antenna) ? cable: Antenna;} void set_input(){input = (input = tv) ? dvd : tv;} void settings() const;private: int state; int volume; int maxchannel; int channel; int mode; int input;};class Remote{private: int mode;public: Remote(int m = Tv::tv):mode(m){} bool volup(Tv & t){return t.volup();} bool voldown(Tv &t){return t.voldown();} void onoff(Tv & t){t.onoff();} void chanup(Tv &t){return t.chanup();} void chandown(Tv &t){return t.chandown();} void set_mode(Tv &t){return t.set_mode();} void set_input(Tv &t){return t.set_input();} void set_chan(Tv &t, int c){t.channel = c;}}; 从上面的从书中摘抄的毫无诚意的代码中可以看出,所有的Remote方法都是Tv类的友元,似乎Remote类除了构造函数都使用了Tv类的公有接口,但是事上唯一直接访问了Tv类成员的Remote类方法是Remote::set_chan(),那么就可以选择仅特定的类成员成为另一个类的友元.但这样就要小心的排列声明和各种定义;让Remote::set_chan()成为Tv类的友元的方法是,在Tv类声明中将其定义为友元: 123class Tv{ friend void Remote::set_chan();} 就酱,但是吧……编译器要处理这句话首先得知道Remote的定义,不然编译器不知道Remote是个类,所以这就要把Remote类的声明挪到Tv类声明前面,但Remote类用了Tv对象..这就又得把Tv类定义挪到Remote类定义前面去.咦等等.那Remote咋办他需要在Tv类的前面啊.这..这就很尴尬了,呐,避免这种死循环的方法是使用 前向声明(forward declaration):123class Tv ; //前向声明class Remote{...}class Tv{...}; 那能不能这样?:123class Remote;class Tv{...};class Remote{...}; 这是不行的,在编译器在Tv类中的声明中看到Remote类的一个方法称为Tv类的友元之前,该先让编译器看到Remote类的声明和Remote::set_chan()函数的声明. 好了,但在Remote类中可以看到,有些方法包含了内联代码,例如: void onoff(Tv & t){t.onoff();}由于它使用了一个Tv的方法,所以在此之前编译器必须看到Tv类声明,但是Tv类在Remote类后面声明..解决方法就是把函数定义放在Tv类之后就成.. 吼,现在只有一个Remote方法是Tv类的友元了; 编译器一开始通过前向类型得知了Tv是个类,在读取声明并编译了这些方法之后,使用lnline关键字仍然可以使Remote类未定义的函数称为内联方法.完整代码 其他友元关系遥控器能影响电视.现在你想通过电视对遥控器产生某种影响,这可以让类彼此成为对方的友元来实现; 需要记住的是对于使用Remote类对象的Tv方法,其 函数原型 可以在Remote类声明之前声明,但必须在Remote类之后定义,这样编译器才有足够的信息来编译该方法.12345678910111213class TV{friend class Remote;public: void buzz(Remote & r); ...};class Remote{ friend class Tv; public: void bool volup(Tv $ r){t.volup();}};inline void Tv::buzz(Remote & r){...} 由于Remote声明在Tv后面,所以可以在类声明中定义volup();buzz()可以再Tv中声明,但必须在Remote后面定义; 共同友元函数需要访问两个类的私有数据,函数可以是一个类的成员,另一个类的友元; 也可以是两个类的友元:12345678910111213class A; //前向声明class B { friend void fun(A & a,const B & b); //编译器发现前向类型A 得知A是一个类型 friend void fun(B & b,const A & a);}class A{ friend void fun(A & a,const B & b); friend void fun(B & b,const A & a);}//定义友元函数inline void fun(A & a,const B & b){...}inline void fun(B & b,const A & a){...}","tags":[{"name":"笔记","slug":"笔记","permalink":"http://yoursite.com/tags/笔记/"},{"name":"代码","slug":"代码","permalink":"http://yoursite.com/tags/代码/"},{"name":"CPP","slug":"CPP","permalink":"http://yoursite.com/tags/CPP/"}]},{"title":"C++笔记-嵌套类","date":"2017-03-04T15:04:38.000Z","path":"2017/03/04/C++笔记-嵌套类/","text":"封面来源 在(quan)神(shi)奇(keng)的C++中,放在另一个类中声明的类被称为嵌套类(nested class),它通过提供新的类型类作用域来避免名称混乱.包含类的成员和函数可以创建和使用被包含类的对象; 仅当嵌套类声明位于包含类的公有部分时,才可以在包含类外通过作用域解析运算符使用嵌套类; 不不不,这个和包含不一样,包含是将一个类的类对象作为另一个类的类成员; 而嵌套类则是定义了一种类型且仅在包含嵌套类声明的类中有效 12345678910//在包含类的私有部分声明了个嵌套类class Queue{private: class Node { public: Node(); }} 使用两次作用域解析运算符就可以定义Node()辣:1Queue::Node::Node(){...} 嵌套类和访问权限如果嵌套类是在另一个类的私有部分声明的,则只有包含他的类知道他的存在,且对于从包含类派生出来的类来讲,因为派生类不能直接访问基类私有部分,所以嵌套类也是不可见的. 如果嵌套类是在另一个类的保护部分声明的,则对于包含他的类来说是可见的,而对于外部来讲嵌套类是不可见的,但后者的派生类可以直接创建这种类对象. 如果嵌套类是在另一个类的公有部分声明的,因为他是公有的,则对于包含他的类,对与包含他的类的派生类以及外部世界都可以使用它. 访问控制在Queue类中声明Node类并没有赋予Queue类任何访问权限,Node也没有赋予Queue任何访问权限,所以Queue只能显示的访问Node成员,所有我将Noede类所有成员声明为公有,不过没关系,虽然Queue的方法可以直接访问Node类,但被Queue声明为私有的Node对于外部来讲是不可见的. 模板类中的嵌套(并不会发生什么奇怪的问题; 完整代码","tags":[{"name":"笔记","slug":"笔记","permalink":"http://yoursite.com/tags/笔记/"},{"name":"代码","slug":"代码","permalink":"http://yoursite.com/tags/代码/"},{"name":"CPP","slug":"CPP","permalink":"http://yoursite.com/tags/CPP/"}]},{"title":"C++笔记-成员模板&将模板用作于参数","date":"2017-01-08T15:43:01.000Z","path":"2017/01/08/C++笔记-成员模板&将模板用作参数/","text":"模板成员:模板类将另一个模板类和模板函数作为其成员1234567891011121314151617181920212223template <typename T>class beta{private: template<typename V> class miao { private: V val; public: miao(V v = 0) : val(v){} void show() const {cout << val << endl;} V value() const {return val;} }; miao<T> q; miao<int> n;public: beta(T t, int i) : q(t),n(i) {} template <typename U> U blab(U u,T t) {return ((n.value() + q.value()) * u/t);} void bshow() const {q.show(); n.show();}}; 12hold<T> q;hold<int> n; n 是基于int 类型的hold 对象,q 的基于T 类型的hold 对象,下述声明使得T表示的是double,因此q 的类型是 hold< double>:1beat<double> guy(10,2.5); blab() 方法的U 类型由该方法被调用时的参数决定,T 类型由对象的实例化决定,下述例子中,guy 的声明将T 类型设置为double,U 的类型则为int.1cout<<guy.blab(10,2.5); 虽然混合类型所引起的自动类型转换导致blab() 函数中的计算以double 类型进行,但返回值的类型为U (即int),因此上述输被截断为28. 如果使用guy.blab()时,使用10.0代替10,那么U 的类型将会设置为double ,使得返回值也为double,因此输出为28.2608. 将模板类用作参数模板类可以包含类型参数(如 typename T )和非类型参数(如 int ),还可以包含本身就是模板的参数. 举个书上的栗子:12345678910111213141516171819202122232425262728293031template<template <typename T>class Thing>class Crab{private: Thing<int> s1; Thing<double> s2;public: Crab(){} bool push(int a,double x){ return s1.push(a) && s2.push(x);} bool pop(int & a,double & x){return s1.pop(a) && s2.pop(x);}};int main(){ Crab<Stack> cs; int ni; double nd; cout << "Enter int double pairs,such as 4 3.5(0 0 to end):" << endl; while (cin >> ni >>nd && ni > 0 && nd > 0) { if(!cs.push(ni,nd)) break; } while(cs.pop(ni,nd)) cout << ni << " , " << nd << endl; cout << "DONE" <<endl; return 0;} 12template <template <typename T>class Thing>class Crab{} 如上, 上述模板参数类型为template < typename T> class Thing,其中Thing 为参数. 这意味着为了使 Crab< King> legs 被接受,模板类参数King 必须是个模板类:12template <typename K> class King{...} legs 声明将用King< int> 替换 Thing< int> ,用 King< double> 替换 Thing< double>,但是在下面的代码中就有所不同. 在Crab 类中,有两行代码声明了两个类对象:12Thing<int> s1;Thing<double> s2; 而mian 函数中包含以下声明,因此,Thing< int> 将被实例化为 Stack< int> ,而Thing< double>将被实例化为Stack< double>.总之,模板参数Thing 将被替换为声明Crab 对象时被用作模板参数的模板类型.","tags":[{"name":"笔记","slug":"笔记","permalink":"http://yoursite.com/tags/笔记/"},{"name":"代码","slug":"代码","permalink":"http://yoursite.com/tags/代码/"},{"name":"CPP","slug":"CPP","permalink":"http://yoursite.com/tags/CPP/"}]},{"title":"C++笔记-模板的具体化","date":"2017-01-06T11:32:50.000Z","path":"2017/01/06/C++笔记-模板的具体化/","text":"隐式实例化:编译器在需要对象之前,不会生成类的隐式实例化12ArrayTp<int,100> * pt; //不生成实例pt = new ArrayTp<int,100>;//现在生成实例 显式实例化:使用关键字template并指出所需类型来声明类的时候,编译器将生成类声明的显式实例化:1template class ArrayTp<string,100>; 在这种情况下,虽然没有创建类对象,编译器也将生成类,包括方法定义. 显式具体化:(显式具体化==特型)在需要特殊要求的时候对模板进行修改,使其行为不同,这时可以创建显式具体化. 书上举的栗子:假设现在定义一个表示排序后数组的类:1template <typename T> class SortedArray{...} 假设模板使用>运算符来对值进行比较,对于数字来说没毛病,对于类型T,只要定义了T::operator>(),也没毛病,但是T如果是个const char **的字符串就不行了,因为这需要使用strcmp(),而不是>*.这种情况下可以提供一个现实模板具体化.格式如下:1template <> class Classname <specialized-type-name>{...} 当具体化模板和通用模板都与实例化请求匹配时,编译器将使用具体化模板. 所以说要使用const char 类型的SortedArray*模板可以这么写:1template <> class SortedArray<const char *> {...} 这样在使用const char **类型的SortedArray*模板时将使用上述专用定义而不是用通用模板定义. 部分具体化:部分具体化可以给类型参数制定具体的类型:12template <class T1, class T2> class Pair{...};template <class T1> class Pari<class T1,int>{...}; template后面的是没有被具体化的类型参数.上述T2被具体化为int,但T1不变. 部分具体化特型使得能够设置各种限制,例如:12345678//一般模板template<typename T1,typename T2,typename T3>class Trio{...}//对T3具体化的模板template<typename T1,T2> class Trio<T1,T2,T3>{...}//对T2,T3具体化的模板template<typename T1> class Trio<T1,T2*,T3*>{...} template 后面的是没有被具体化的类型参数 给出上述定义编译器将作出如下选择:123Trio<int,double,string*> T1; //使用一般模板Trio<int,short> T2; //使用对T3具体化的模板Trio<string,string*,char*>;//使用对T2,T3具体化的模板","tags":[{"name":"笔记","slug":"笔记","permalink":"http://yoursite.com/tags/笔记/"},{"name":"代码","slug":"代码","permalink":"http://yoursite.com/tags/代码/"},{"name":"CPP","slug":"CPP","permalink":"http://yoursite.com/tags/CPP/"}]},{"title":"C++笔记-多重继承","date":"2016-12-30T11:31:05.000Z","path":"2016/12/30/C++笔记-多重继承/","text":"必须使用关键字来限定每一个基类,不然编译器会默认成私有派生:1class mylove : public string, valarray<double> //valarray为私有继承 其实你不用看这仨奇葩类的123456789101112131415161718192021222324252627282930313233343536373839404142class Worker{public: Worker() : name("NULL"),id(0L){} Worker(const string & s,long n) : name(s),id(n){} virtual ~Worker() = 0; virtual void Set(); virtual void Show() const;private: string name; long id;};class Waiter : public Worker{public: Waiter() : Worker(),panache(0){} Waiter(const string & s, long n, int p = 0) : Worker(s,n),panache(p){} Waiter(const Worker & w, int p) : Worker(w),panache(p){} void Set(); void Show() const;private: int panache;};class Singer : public Worker{public: Singer() : Worker(),voice(other){} Singer(const string & s, long n, int v = other) : Worker(s,n),voice(v){} Singer(const Worker & w, int v =other) : Worker(w),voice(v){} void Set(); void Show() const;protected: enum{other,alto,contralto,soprano,bass,baritone,tenor}; enum{Vtypes = 7};private: static char* pv[Vtypes]; int voice;}; Worker?从Singer和Waiter共有派生出SingingWaiter:1class SingingWaiter : public Singing, public Waiter{...} 但这将出现二义性,因为Singing和Waiter都继承了一个Worker:12SingingWaiter sw;Worker* pw = sw; //二义性,鬼知道这时候用哪个worker so..应该使用类型转换来指定对象:123Worker* pw = (Singing*) &sw;Worker* pw2 = (Waiter*) &sw;//不过下面还有更简(ma)单(fan)的虚基类可以解决该问题 虚基类虚基类使得从多个类(他们基类相同)派生出的对象只继承一个基类对象.例如:在类声明中使用关键字virtual,可以使Worker被作用Singer和Waiter的虚基类:12class Singer : virtual public Worker{...}class Waiter : public virtual Worker{...} //这么写也行 然后 SingingWaiter可以定义为:1class SingingWaiter : public Singer,public Waiter{...} 现在SingingWaiter类只有一个Worker对象副本了,Singer和worker共享一个Worker对象,所以现在可以使用多态了. 新的构造函数规则使用虚基类时,构造函数需要使用一种新的方法,这是因为C++在基类是虚的时,禁止信息通过中间自动传递给基类,编译器在这时会使用基类的默认构造函数. 12345678910111213//通过中间类自动传递:class A{ int a; A(int n = 0) : a(n);}class B :public A{ int b; B(int a = 0, int bm = 0) : A(a),b(bm);}class C : public B{ int c; C(int a = 0, int b = 0, int cm = 0) : B(a,b),c(cm);} 使用虚基类时我们必须显示调用构造函数:1234567SingingWaiter(const Worker & wk,int p = 0,int v = Singer::other) :Worker(wk),Waiter(wk,p),Singer(wk,v){} //显示使用worker SingingWaiter(const Worker & wk, int p = 0, int v = Singer::other) :Waiter(wk,p),Singer(wk,v){} //错误的示范,会调用Worker的默认构造函数 对于非虚基类,显示调用Worker(const Worker&)是非法的 哪个方法?那么问题来了,我们打算在SingingWaiter中重定义Show方法,并用SingingWaiter对象调用继承的Show方法:12345//所以我傻不愣登的写下了如下代码:SingingWaiter aha("喵喵",2017,1,soprano);aha.Show(); //二义性,Worker和Singer都有Show() 鬼知道这个是哪个?aha.Singer::Show(); //然而可以用作用域解析运算符来确定 最好是使用模块化:12345678910111213141516171819Woeker::Data() const { cout << "Name: " << name; cout << "ID: " << id;}Waiter::Data() const{ cout << "panache: " << panache << endl;}Singer::Data() const{ cout << "rating: " << pv[voice] << endl;}SingingWaiter::Data() const{ Worker::Data(); Singer::Data();}SingingWaiter::Show() const{ Worker::Data(); Data();}//就是.........有点麻烦...","tags":[{"name":"笔记","slug":"笔记","permalink":"http://yoursite.com/tags/笔记/"},{"name":"代码","slug":"代码","permalink":"http://yoursite.com/tags/代码/"},{"name":"CPP","slug":"CPP","permalink":"http://yoursite.com/tags/CPP/"}]},{"title":"Hello World!!","date":"2016-12-27T13:49:34.066Z","path":"2016/12/27/hello-world/","text":"喵喵喵 本人的渣渣博客 以后就在这里撒欢辣 写的比较傻逼~ 大神轻喷呐 如有错误还劳驾指出哦~ Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub. Quick StartCreate a new post1$ hexo new \"My New Post\" More info: Writing Run server1$ hexo server More info: Server Generate static files1$ hexo generate More info: Generating Deploy to remote sites1$ hexo deploy More info: Deployment","tags":[]},{"title":"C++笔记-私有继承","date":"2016-12-23T16:17:20.000Z","path":"2016/12/24/C++笔记-私有继承/","text":"使用私有继承,基类的共有成员以及保护成员都将成为派生类的私有成员派生类不继承基类的接口,但能在派生类的成员函数中使用它们,私有继承特征与包含相同:不继承接口,继承实现.所以它可以用来实现has-a关系. 示例:123456class player : private string,private valarray<double>//使用多个基类:多重继承{ public: ...} 在这里新版本的构造函数将使用初始化成员列表,使用类名而不是使用成员名来标识构造函数: 12player::player(const char* str,const double* p,int i) : string(str),valarray<double>(p,i){ } 保护继承1class player : protected string,protected valarray<double>{...} 保护继承时,基类的公有成员和保护成员都将成为派生类的保护成员.使用私有继承时,第三代类将不能使用基类的接口,这是因为基类的公有方法在派生类中将变成私有方法.使用保护继承时,基类的公有方法将在二代类中变成受保护的,所以第三代类可以使用它们. 使用using重定义访问权限使用保护派生或者私有派生时,基类的公有继承将成为保护或者私有成员,如果想让基类方法在类外使用可以定义一个使用该基类方法的派生类方法.1234//派生类player希望使用基类valarray类的sum方法double player::sum() const { return valarry<double>::sum();} 或者可以使用using声明:123456class player : private valarray<double>{ public: using valarray<double>::min; using valarray<double>::max;} 他们就像player类的共有方法一样:1cout << "high score:" << a[i].max << endl; 注意 using声明只使用成员名,没有特征标,圆括号,返回类型,例如要在player]类中使用valarray类的operator[]方法,只需包含:1using::valarray::operator[]; 通常使用包含来建立has-a关系;如果新类需要访问原有类的保护成员,或需要重新定义虚函数应使用私有继承","tags":[{"name":"笔记","slug":"笔记","permalink":"http://yoursite.com/tags/笔记/"},{"name":"代码","slug":"代码","permalink":"http://yoursite.com/tags/代码/"},{"name":"CPP","slug":"CPP","permalink":"http://yoursite.com/tags/CPP/"}]},{"title":"C++笔记-包含类对象的成员","date":"2016-12-23T14:19:08.000Z","path":"2016/12/23/C++笔记-包含类对象的成员/","text":"接口和实现 使用公有继承时,类可以继承接口,可能还有实现(基类的纯虚函数提供接口,但不提供实现),获得接口是is-a的关系组成部分.而是用组合,类可以获得实现不继承接口是has-a的关系组成部分. 错误的示范:1234567891011121314class player{private: string name; valarray<double> source; ostream & arr_out(ostream & os) const;public: player():name("NULL"),source(){} explicit player(const string & s) :name(s),source(){} explicit player(int n) :name("NULL"),source(n){} player(const string& na, int n) :name(na),source(n){}}; 12player mylove("YSY",10);mylove = 5; //喵喵喵??重置ArrayD为五个空值的元素? mylove = 5;这里应该是:mylove[5] = 5才对.如果没有写explicit,编译器将调用转换构造函数player(5),name 的默认值将是NULL,并且编译器将会生成一个临时对象,并用临时对象替换mylove原有的值,这并不是我们想要的.如果加了explicit,编译器会报错这对我们debug很有利.毕竟在编译期出错优于在运行期出错.","tags":[{"name":"笔记","slug":"笔记","permalink":"http://yoursite.com/tags/笔记/"},{"name":"代码","slug":"代码","permalink":"http://yoursite.com/tags/代码/"},{"name":"CPP","slug":"CPP","permalink":"http://yoursite.com/tags/CPP/"}]},{"title":"C++笔记-类设计总结","date":"2016-12-21T14:57:57.000Z","path":"2016/12/21/C++笔记-类设计总结/","text":"默认构造函数:默认构造函数要么没有参数,要么所有参数都有默认值.在派生类构造函数初始化列表中记得显示调用基类构造函数,否则编译器将会使用基类的默认构造函数,这可能会出现错误. 复制构造函数:1classname(const classname &) 在按值传递,安置返回对象,编译器生产临时对象,将新对象初始化为一个同类对象的时候,将调用复制构造函数. 赋值运算符: 1classname & operator=(const classname &) 用于处理同类对象间的赋值,如果希望处理string类与classname类的赋值可以写成:1classname & operator=(const string &) 析构函数:当对象过期时,析构函数负责清理工作(如释放内存),对于基类应该提供一个虚析构函数,即使他不需要. 转换构造函数:1classname(const schar *) 使用一个参数的构造函数他定义了从参数类型到类类型的转换(话说这个中文名跟转换函数就差了两个字,但不一样容易弄混). 123Star(const char*);Star a = Star("233");a = "233"; 第二/三句话将会调用Star::operator(const Star &)并使用Star(const char)生成一个对象,该对象将作用于赋值运算符函数的参数. 使用转换构造函数时候时建议使用explicit禁止隐式转换* 按值传递对象与传递引用通常在编写以对象作为参数的函数时,应该按引用,不应该使用按值传递参数,一是为了效率,二是因为在继承使用虚函数时,基类使用引用参数的函数可以接受派生类. 返回对象与返回引用如果函数返回的是通过引用或指针传递给他的对象,则应该按引用返回对象.返回引用可以节约内存和时间,与按引用传递相似,调用与被调用函数使用同一个对象进行操作.但不总是可以返回引用,比如函数不能返回一个在函数中创建的临时变量的引用,因为当函数结束时临时变量将会消失,这时候应该返回对象. const123456789classname::classname(const char * s) //确保方法不修改参数classname::show() const; //确保方法不修改调用他的对象,这里的const表示const classname * this,this指向调用的对象const classname& classname::fun(const classname & cn) const{ if(s.total > total) return s; else return *this;};//该方法返回cn/this的引用,但因为cn/this是const,所以方法不能对cn/this进行修改,这意味着返回类型也必须为const 共有继承要考虑的因素:is-a关系is-a为”是一个”的意思,如果派生类不是某种特殊的基类则不要使用,比如从大脑类派生出程序员类.is-a关系的标志之一是:无需进行显示转换即可将积累指针或引用指向或引用派生类对象(向上强制类型转换).反之是可能出现错误的. 赋值运算符将派生类赋值给基类对象:123456class ZheXue {...}//基类-哲♂学class GaoBiLi : public ZheXue{...}//派生类-搞♂比♂利ZheXue bili;GaoBiLi fuc;bili = fuc; 1bili = fuc; 这将转化为: bili.operator=(fuc)他将调用 ZheXue::operator=(const ZheXue &); 那如果将基类赋值给派生类对象呢:1fuc = bili; 这将转化为fuc.operator=(bili) ; 他将调用 GaoBiLi::operator=(const GaoBiLi &);然而,派生类引用不能自动引用基类对象.除非我们定义转换构造函数:GaoBiLi(const ZheXue &) ;转换构造函数可以有一个类型为基类的参数和其他参数,但其他参数必须有默认值: 1GaoBiLi(const ZheXue & zx,string & na = "比♂利♂王",string & ty = "森之♂妖精"); 这样转换构造函数会根据bili来创建一个临时对象,然后把它作为赋值运算符的参数.然而还可以直接写个参数为ZheXue的赋值运算符函数….: 1GaoBiLi::operator=(const ZheXue &){......}; 私有成员与保护成员对于外界来说,只能用共有成员来访问二者,对于派生类来说,可以直接访问基类的保护成员,而私有成员仍要通过基类的成员函数来访问. 虚方法如果要在派生类中重定义基类的方法则应该使用virtual. 析构函数基类的析构函数应当是虚的.这样在使用指针或者引用删除派生对象时,程序会先调用派生类的析构函数然后调用基类的,而不会只调用基类的析构函数 友元函数友元函数并非类成员因此不能继承,如果希望派生类函数能使用基类的友元函数,可以使派生类指针或引用强制转换为基类的指针或引用,然后使用转换后的指针或引用来调用友元函数. 123456ostream & GaoBiLi::operator<<(ostream & os, GaoBiLi & gbl){ os << (const ZheXue &)gbl; os << "name:" << gbl.name << endl; return os;}","tags":[{"name":"笔记","slug":"笔记","permalink":"http://yoursite.com/tags/笔记/"},{"name":"代码","slug":"代码","permalink":"http://yoursite.com/tags/代码/"},{"name":"CPP","slug":"CPP","permalink":"http://yoursite.com/tags/CPP/"}]},{"title":"C++笔记-继承和动态内存分配","date":"2016-12-15T14:01:19.000Z","path":"2016/12/15/C++笔记-继承和动态内存分配/","text":"如果基类使用动态内存分配,派生类不使用1234567891011121314151617class player{ privatec: string * name; bool sex; public: player(const string & na = "NULL",bool a = 0); player(const player & p); virtual ~player(); player & operator=(const player & p);}class ship : public player{ private: int age; public: ...} 那么是否要为ship类定义显示析构函数,复制构造函数,重载赋值运算符呢? 不需要. 析构函数: 对于ship类,我们没有对他进行任何特殊的操作,所以默认的析构函数是合适的 复制构造函数:首先我们知道的是,默认复制构造函数是执行成员复制,因为player使用了动态内存分配,所以,默认复制构造函数不适用于player类,但对于ship类是适合的.当复制继承的组件获或者成员时,则使用他的复制构造函数.所以当ship的默认复制构造函数会使用player的默认复制构造函数来复制ship里的player对象.所以默认复制构造函数对于他们来说是合适的. 赋值运算符: ship默认的赋值运算符也会使用player的赋值运算符来对player成员进行赋值,所以是合适的. 如果基类和派生类都使用动态内存分配123456class ship : public player{ private: string * type; public: ...} 在这种情况下必须显示定义派生类的显示析构函数,复制构造函数,重载赋值运算符. 析构函数: 派生类的析构函数先释放type所管理的内存,然后基类析构函数释放name所管理的内存. 复制构造函数:派生类的复制构造函数只能访问派生类的数据,所以他必须调用父类的复制构造函数.ship::ship(const ship & p) : player(p) , 因为player类因为复制构造函数有一个player&参数,而基类可以指向派生类型,因此player的复制构造函数将使用ship参数的player部分来构造新对象的player部分. 赋值运算符:因为派生类采用动态内存分配,所以他需要一个显示赋值运算符.ship的赋值运算符只能直接访问ship类的数据,但他却要负责所继承的基类对象的赋值,这个时候可以显示调用基类的赋值运算符方法.然后在处理派生类的赋值.1234567ship & ship::operator=(const player & p){ if(this == p) return *this; player::operator=(p);//显示调用基类赋值运算符 delete type; type = new string; type = p.type;}","tags":[{"name":"笔记","slug":"笔记","permalink":"http://yoursite.com/tags/笔记/"},{"name":"代码","slug":"代码","permalink":"http://yoursite.com/tags/代码/"},{"name":"CPP","slug":"CPP","permalink":"http://yoursite.com/tags/CPP/"}]},{"title":"C++笔记-抽象基类(Abstract-Base-Class,ABC)","date":"2016-12-14T15:46:31.000Z","path":"2016/12/14/C++笔记-抽象基类(Abstract-Base-Class,ABC)/","text":"啥时候使用抽象基类?在下理解的是:你有一个基类和一个该基类的派生类,但是基类里有一些你派生类根本用不上的方法,使用了派生类就会导致一些信息冗余.然而不继承基类单独写个类你发现效率也不高,而且你发现你的基类和你的派生类之前还是有共同点的..这个时候就要上抽象基类了….把其共同点放到抽象基类里,然后分别从抽象基类派生刚才的”基类”与”派生类”. 啥是抽象基类 就是类里定义了纯虚函数的类………然而定义了纯虚函数就只能作为基类了.23333 纯虚函数纯虚函数:virtual 返回类型 函数名(形参) =0;在虚函数声明后面加个 =0 就是纯虚函数了,当类声明中包含纯虚函数的时候,则不能创建该类对象.所以包含纯虚函数的类只能作为基类,在原型中使用 =0 指出类是一个抽象基类,在类中可以不定义该函数.12345678class player{ private: int age; string name; pbulic: ... virtual void show() = 0;} 总之: ABC描述的是至少使用一个纯虚函数的接口,从ABC派生出的类将根据派生类的具体特征使用常规虚函数来实现这种接口.","tags":[{"name":"笔记","slug":"笔记","permalink":"http://yoursite.com/tags/笔记/"},{"name":"代码","slug":"代码","permalink":"http://yoursite.com/tags/代码/"},{"name":"CPP","slug":"CPP","permalink":"http://yoursite.com/tags/CPP/"}]},{"title":"C++笔记-访问控制protected","date":"2016-12-13T15:22:36.000Z","path":"2016/12/13/C++笔记-访问控制protected/","text":"protected关键字protected的意思是保护,和private有点相似,在类外只能通过共有类成员来访问protected中的成员.但他与private的区别体现在:在派生类中,派生类的成员可以直接访问基类的protected成员,但不能直接访问基类的private成员.举个栗子:12345678910111213141516class player{ ... public: void R18(int a){ if(a<18)cout << "禁止入内"; else {cout << "欢♂迎"; } protected: int age;}class ship : public player{ ... public: void showshipage(int m){ age = m; cout << age << "欢♂迎";}//通过派生类公有成员直接访问基类protected成员 ...} 但是这样做是有点小问题的age成员被设置为只能通过player::R18()来访问,但是有了ship::showshipage()将会忽略player::R18()的禁止入内措施,这使得age变成了一个公有变量…….然而对于成员函数来,保护控制很有用,他可以让派生类访问一些公众不能访问的内部函数.so..你问我滋补滋磁,我是滋次的.","tags":[{"name":"笔记","slug":"笔记","permalink":"http://yoursite.com/tags/笔记/"},{"name":"代码","slug":"代码","permalink":"http://yoursite.com/tags/代码/"},{"name":"CPP","slug":"CPP","permalink":"http://yoursite.com/tags/CPP/"}]},{"title":"C++笔记-静态联编和动态联编","date":"2016-12-11T15:38:53.000Z","path":"2016/12/11/C++笔记-静态联编和动态联编/","text":"一些理解性概念 啥是联编将源代码中的程序调用解释为执行特定的函数代码块被称为函数名联编在编译过程中的联编被称为静态联编在程序运行时的联编被称为静态连编 ##指针和引用类型的兼容性将派生类指针或引用转为基类指针或者引用被称为向上强制转换,如果是共有继承则不需要进行显式类型转换,任何对基类对象做的操作都适合派生类对象.相反,将基类指针或者引用转换为派生类指针或者引用被称为向下强制转换,需要显示类型转换,但是派生类可以新增成员函数,由于基类没有这些函数,这使得使用新增成员函数的类成员函数不能作用于基类.123456789101112131415161718192021222324class player{ pubilc: player(string & na){name = na;} void showname()const {cout << name;} private: string name;}class ship : pubilc player{ public: ... void shouage(int age); private: int age;}player lex("lexington"); ship * t =(ship*)&lex; //将基类指针转化为派生类指针,必须显示类型转换,向下强制转换ship sar("saratoga");player* v = &sar; //将派生类指针转化为基类指针,向上强制转换t->showage(20); //不安全的操作 showage不是player的成员v->showname(); //安全的 现在我们有个虚方法1234567891011121314``` void fr(player & r) //r.sizhai(); void fp(player * p) //p->sizhai(); void fv(player v) //v.sizhai(); fun(){ player p("LEX"); ship s("sar"); fr(p); // player::sizhai(); fr(s); // ship::sizhai(); fp(p); // player::sizhai(); fp(s); // ship::sizhai(); fv(p); // player::sizhai(); fv(s); // player::sizhai(); } 由于按值传递ship对象的player部分被传递给函数fv().但是引用和指针发生的向上强制转换分别为player对象和ship对象使用了不同的函数(virtual).隐式向上强制类型转换使得基类对象可以指向基类对象或派生类对象,因此需要动态联编. 虚函数和动态联编概念理解:虚函数工作原理通常,编译器处理虚函数的原理是:给每个对象添加一个隐藏成员,这个隐藏成员保存了一个指向函数地址数组的指针.它被称为虚函数表,虚函数表保存了类对象的虚函数地址.列如,基类对象包含一个指针,它指向基类中所有虚函数的地址表.派生类对象将指向一个独立地址表的指针,如果派生类里提供了虚函数的定义,那么这个独立的地址表将会保存新函数的地址,如果没提供,该表将使用原始版本的地址.如果派生类定义了新的虚函数,那么该函数的地址也会被添加到表中. 注意:无论类中有多少个虚函数,都只在对象中添加一个地址成员,只是大小有所差别 虚函数总结: 构造函数不能是虚函数; 析构函数应该是虚函数(除非不作为基类); 友元不能是虚函数,因为友元函数不是类成员; 如果基类声明被重载,则应在派生类中重定义所有的基类重载版本; 重定义不是重载,如果重定义继承的方法,应该确保与原型完全一致;注意:如果原返回类型是指向基类的指针或者引用,可以改成指向派生类的指针和引用,这被成为返回类型协变","tags":[{"name":"笔记","slug":"笔记","permalink":"http://yoursite.com/tags/笔记/"},{"name":"代码","slug":"代码","permalink":"http://yoursite.com/tags/代码/"},{"name":"CPP","slug":"CPP","permalink":"http://yoursite.com/tags/CPP/"}]},{"title":"C++笔记-一个简单的基类","date":"2016-12-08T13:58:54.000Z","path":"2016/12/08/C++笔记-一个简单的基类/","text":"从一个类派生出另一个类,原始类被称为基类,继承类被称为派生类例:1234567891011121314151617181920class player{ public: player(const string & na,bool se = 1); void Name() const{cout << name << endl;} bool sex {return sex;} privat: string name; bool sex;} class Ship : public player //继承了player类 公有派生{ public://派生类需要自己的构造函数并且必须使用基类的构造函数 Ship(bool mw = 0,int ag, const string & na, bool se = 1); Ship(bool mw = 0,int ag,player & p); int ShowAge() {cout << age; return age;} private: bool myWife; int age;} 冒号说明了Ship的基类是player,public表示了这个基类是公有基类,这被成为公有派生.使用公有基类,基类的公有成员将成为派生类的公有成员,基类的私有成员也将成为派生类的一部分但是不能直接访问,需要通过继承的基类的公有方法来间接访问.创建派生类对象的时候,首先创造基类对象,C艹使用成员列表初始化完成该工作:12345678910//在此声明成员列表初始化只能用于构造函数 Ship::Ship(bool mw, int ag,const string & na,bool se) : player(na,se){ mw = 1; ag = 18; } Ship::Ship(bool mw,int ag,player & p) : player(p){ mw = 0; ag = 14; } 在第二个构造函数中 由于基类类型为 player & ,因此将会调用基类的复制构造函数,由于基类没有该函数,则编译器将会自动生成一个~基类对象首先被创建.派生类构造函数应通过成员初始化列表将基类信息传递给基类构造函数 派生类和基类之间的特殊关系基类指针或引用可以在不进行显式类型转换的情况下,指向或引用派生类对象或引用.然而基类指针和引用只能用于调用基类方法.所以不能用它们来调用派生类方法,如果将基类对象和地址赋值给派生类引用和指针.因为派生类引用可以为基类对象调用派生类方法,但是基类没有派生类的成员所以这么做是没意义的.可以这么写:123456789Ship sp1(1,17,"Lexington",0);player *a = &sp1; //OKplayer &b = sp1; //OKa.Name();b->Name();player sp2("Saratoga",0);Ship *c = &sp2; //不能这么写Ship &d = sp2; //也不能这么写 但是如果基类引用和指针可以指向派生类对象呢?12345678void Show(const player & p){ //OK p.Name();}player temp1("LEX",0);Ship temp2(1,17"SAG",0);Show(temp1); //OKShow(temp2); //OK 函数Show的形参为一个基类引用,他可以指向基类对象或者派生类对象,并且该函数使用了基类的一个方法,所以Show可以使用player参数或者Ship参数.","tags":[{"name":"笔记","slug":"笔记","permalink":"http://yoursite.com/tags/笔记/"},{"name":"代码","slug":"代码","permalink":"http://yoursite.com/tags/代码/"},{"name":"CPP","slug":"CPP","permalink":"http://yoursite.com/tags/CPP/"}]},{"title":"C++笔记-多态公有继承","date":"2016-12-07T15:35:20.000Z","path":"2016/12/07/C++笔记-多态公有继承/","text":"虚方法:virtual该声明之处方法在派生类版本的行为不同.12345678910111213141516171819class You{public: You(const string& Na){Name = na;} virtual string Show(){return Name;} ~You(){}private: string Name;}class YouName : public You{public:YouName(){}~YouName(){}virtual void Show(){cout << "your name is "; Name();}}You A("YSY"); YouName B("YES");A.Name(); //You::Name()B.Name(); //YouNAme::Name(); 基类版本限定名为You::Show(),派生类限定名为YouName::Show()程序会根据使用对象类型来确定使用哪个版本. 需要注意的是如果方法是通过指针或引用调用的呢?程序将使用哪种方法? 如果没有使用关键字virtual,程序将根据引用类型或者指针类型来选择方法,如果使用了关键字virtual,程序将根据引用或指针指向的对象的类型来确定方法~","tags":[{"name":"笔记","slug":"笔记","permalink":"http://yoursite.com/tags/笔记/"},{"name":"代码","slug":"代码","permalink":"http://yoursite.com/tags/代码/"},{"name":"CPP","slug":"CPP","permalink":"http://yoursite.com/tags/CPP/"}]},{"title":"C++笔记-类和动态内存分配","date":"2016-12-07T13:59:20.000Z","path":"2016/12/07/C++笔记-类和动态内存分配/","text":"对于类中的非静态const数据成员,必须在执行到构造函数体前,级创建对象时进行初始化.他叫做成员列表初始化:123456789101112131415161718class FUN{ public: struct Node{Item item;struct Node* next;} Node* fornt; Node* rear; int items; const int qsize;}FUN::FUN(int qs) : qsize(qs){ front = rear = nullptr; items = 0;}//然而这种方法不局限于常量 所以也可以这么写FUN::FUN(int qs):qsize(qs),rear(nullptr),front(nullptr),items(0){...} 注意只有构造函数才可以使用这种方法,另外对于被引用的类成员也必须这么写: 123456class A{...};class B{private: A & hello;}B::B(A & h) : hello(h){....} 关于C++11的内存初始化直接这么写就行(那我们为啥要用第一种方法啊魂淡(。•ˇ‸ˇ•。)):1234class Classy{ int meml = 10; const int mem2 = 20;}","tags":[{"name":"笔记","slug":"笔记","permalink":"http://yoursite.com/tags/笔记/"},{"name":"代码","slug":"代码","permalink":"http://yoursite.com/tags/代码/"},{"name":"CPP","slug":"CPP","permalink":"http://yoursite.com/tags/CPP/"}]},{"title":"QML与C++交互","date":"2016-12-05T14:38:29.000Z","path":"2016/12/05/QT-QML与C++交互/","text":"图片作者为:Bison仓鼠终于搞定了gayhub的博客,以后就在这里写一些自己想写的东西好了 ╰(´︶`)╯ 前几天用QT的QML与C++交互,基于QMediaPlayer类撸了一个baka音乐播放器 因为是第一次用QML撸 再加上我幼儿园水平的代码 写的一团糟..代码请用鼠标♂插 这里 这个坑爹的QML与C++交互折腾了我好久 (╬▔▽▔)凸 以我这辣鸡播放器为例(大神请绕道orz) 那么问题来了如何使用C艹来控制QML?假设我们的qml是酱紫写的 1234567891011121314151617181920212223Rectangle { id: head y: 0 width: parent.width height: 40 color: \"#222225\" opacity: 0.95 Layout.maximumHeight: 45 Layout.fillWidth: true Layout.fillHeight: false Text { id: musicteitle x:5 y:5 width: parent.width height: 30 color: \"#C8C8C8\" font.family: \"microsoft yahei\" font.pixelSize: 23 text:\"正在播放: \" + myTITLE } } 让我们用这段代码来显示正在播放的某音乐的名字,细心的你可能发现了 你写这辣鸡玩意前面的我都能看懂 那个myTITLE是什么鬼?这里让我们隆重介绍一下QQmlContext这个神奇的类 : “QQmlContext类定义了一个QML引擎上下文引擎上下文允许将数据暴露给由QML引擎实例化的QML组件每个QQmlContext包含一组属性,与其QObject属性不同,它允许数据通过名称显式绑定到上下文。上下文属性通过调用QQmlContext :: setContextProperty()来定义和更新” –power for 谷歌翻译 参照官网 于是我们可以酱紫来控制音乐的名字:123QQmlApplicationEngine* view = new QQmlApplicationEngine;QQmlContext* title = view->rootContext();title->setContextProperty(\"myTITLE\",QVariant(NowMusicName)); 通过修改NowMusicName的值就可修改myTITLE的值 这俩东西是绑定的于是我们就可以通过C++来修改NowMusicName 从而修改QML中的myTITLE 进而达到显示出当前音乐名字的目的 那么 view是何方神圣? 官方文档: QQmlApplicationEngine QQmlApplicationEngine提供了从单个QML文件加载应用程序的方便方法。这个类结合了QQmlEngine和QQmlComponent来提供一个方便的方式来加载单个QML文件。它还向QML提供了一些中央应用程序功能,C++/QML混合应用程序通常会从C++控制 –power for 谷歌翻译 果冻,能不能给力点啊?(눈_눈) 你说的这么水 没人会看的好吧ಥ_ಥ 既然能从用C艹来艹QML 那自然也能用QML来艹C艹了 如何用QML来艹C艹?假设我们现在有个音乐类Music 我们想通过点击某个按钮来调用Music类的暂停函数 那么如何实现呢?这个就相对的比较简单了123456//main.cpp如下Music A;QQmlApplicationEngine* view = new QQmlApplicationEngine;QQmlContext* context = view->rootContext();context->setContextProperty("myPlay",A); //先创建一个关于Music类的一个上下文 123456789101112131415161718192021222324//fun.qml如下MouseArea { //设置鼠标点击区域 id: sta_pau x: 120 y: 8 width: 45 height: 45 Layout.maximumHeight: 40 Layout.maximumWidth: 70 Layout.fillHeight: true Layout.fillWidth: true onClicked: { myPlay.pausePlay(); } } Image { //把图片放到鼠标点击区域上去 这就是个按钮了 不要在意用没用button那个细节233 id: sta_pau_bg x: 120 y: 8 width: 45 height: 45 source: \"///img/我是一个萌萌的按钮图片.png\" } 这样 一旦我们点击鼠标区域 就会触发与myPlay相之绑定的Music类对象A 从而调用A的暂停播放函数. 果冻,能不能再给力点啊?(눈_눈) 你说的这么水 等着挨喷吧 o(////▽////)q 既然你诚心诚意的发问了~那么,现在让我们结合起用C艹来艹QML与用QML来艹C艹,假设我们要撸一个音乐播放器的进度条.C艹负责处理歌曲总时长,当前播放进度,QML负责用花哨の特♂技显示出来这些. 机智的你一定想到了,这还不简单,QML中使用一个Text控件用于显示,C艹则用QQmlContext建立上下文把总时长与标记绑定,当总时长改变,标记的值也会随之改变,显示出来的总时长也会随之改变. 而当前播放进度则使用QML的进度条显示Slider 控件处理.Slider的value就是当前播放的进度,通过点击事件onPressedChanged来处理进度条的拖动~ 为了更精细的显示出当前播放进度,我们还需要一个用于显示当前播放秒数的Text~同理用C艹获得当前播放秒数,绑定上下文就成.这里只截出一部分 详情还劳烦各位去窝项目上的main.qml与music.cpp上瞅瞅 鬼畜级代码奉上(。・`ω´・)123456789101112131415161718192021222324252627282930313233343536373839404142434445464748/*QML*/ RowLayout { id: progress x: 0 y: 20 width: 580 height: 60 spacing: 5 Layout.maximumWidth: 580 Text { /*用于精确显示当前播放秒数*/ id: s_time y: 0 width: 40 height: 20 color: \"#C8C8C8\"; text: mySTIME /*秒数的上下文*/ font.pixelSize: 18 } Slider { id: bar x: 50 width: 450 height: 20 Layout.maximumWidth: 550 Layout.maximumHeight: 20 Layout.fillWidth: true maximumValue:myPlay.getEndtime();/*使用与Music类对象A绑定的上下文来获得当前音乐的总时长*/ value: setNOW /*用当前播放的值来设置进度条的当前进度*/ onPressedChanged: { /*点击事件:当进度条拖动时改变音乐进度*/ myPlay.setNowMusicPos(value); } } Text { id: e_time x: 515 y: 0 width: 40 height: 20 color: \"#C8C8C8\" text: myETIME /*总时长的上下文*/ font.pixelSize: 18 } } 12345678910111213141516/*C艹*/ QObject::connect(now, &QMediaPlayer::positionChanged, [this](qint64 position){ if(nowMusic->duration() != 0) /*QMediaPlayer* nowMusic */ this->setEndtime(this->now->duration()); //获取当前音乐的总时长 settime(position); /*Music类成员,用于获得当前播放的位置(就是当前播放到哪了 单位:毫秒)*/ QQmlContext* s_time =myView->rootContext(); //当前播放时长的上下文 s_time->setContextProperty(\"mySTIME\",QVariant(timeformat(position))); QQmlContext* now_progress = myView->rootContext();//进度条值的上下文 now_progress->setContextProperty(\"setNOW\",QVariant(position)); QQmlContext* e_time = myView->rootContext(); //总时长的上下文 e_time->setContextProperty(\"myETIME\",QVariant(timeformat(this->endtime))); }); 在这里我们使用了一个QMediaPlayer类的一个信号,每当音乐播放进度改变时都会发射该信号从而调用与之对应的槽(在这段代码里槽为一个lambda).需要注意的是这个信号发射粒度(周期)为1s,据说可以修改发射粒度,找了半天无果,如果你知道怎么改,请务必联系我~阿里嘎多~每当音乐播放1s,positionchanged信号被发射,C++代码刷新各项数据,通过上下文引擎把数据暴露给QML.当进度条拖动的时候,QML的点击事件将触发改变音乐进度的函数来改变播放进度. 大概就酱 谢谢捧场 谢谢~ 欢迎讨论~欢迎纠错(逃","tags":[{"name":"代码","slug":"代码","permalink":"http://yoursite.com/tags/代码/"},{"name":"CPP","slug":"CPP","permalink":"http://yoursite.com/tags/CPP/"},{"name":"Qt","slug":"Qt","permalink":"http://yoursite.com/tags/Qt/"},{"name":"QML","slug":"QML","permalink":"http://yoursite.com/tags/QML/"}]}] \ No newline at end of file +[{"title":"如何稍微有点瑕疵的把3D定制女仆2的模型导入到Maya2017","date":"2017-05-24T15:56:02.000Z","path":"2017/05/24/如何稍微有点瑕疵的把3D定制女仆2的模型导入到Maya2017/","text":"Warning:萌新向 众所周知,我是个死宅,我觉得我死的还可以,但算不上是硬核死宅,主要是因为囊中羞涩以及时间大部分被我用来扯淡了,不过像3D妹抖2这种游戏我还是十分喜欢的.233333 然后在一个月黑风高的晚上,本死宅正摩拳擦掌跃跃欲试准备重温我大I社的经典作品的时候,某群里的一个dalao突然问我3D定制女仆2能不能捏男的.. 是时候展现我..等等..Eexcuse me?我是不是撸多了看错了什么? 男..男的?!什么鬼!…算了是时候展现我高超の捏脸技术辣…那么开工..于是我抱着大佬的大腿开始讨论电影大业(大雾 那么.呃..首先.怎么把3D妹抖2里的模型导出来呢..于是这个大佬给我发了个链接(你..为什么这么熟练啊?): https://tieba.baidu.com/p/4347623203?qq-pf-to=pcqq.group 是贴吧某大神自制的十分牛逼的3D妹抖插件,可以把3D妹抖2的人物模型导出为PMX格式(似乎是mmd的工程文件?)或OBJ格式,具体使用方法在帖子里,我就不重复叙述了。。 于是我展现了我神(手)乎(残)其(至)神(极)の捏脸技术..果断捏了个帅气的小♀哥♀哥: (再次感叹下..3D妹抖2这渲染真牛逼) 哎.,等等..说好的小哥哥! 这胸是怎么一回事啊,不过即使这样我也真觉得这个男装少女挺帅的.. 真没得治了..或许某些MOD可以解决此问题..不过这不是重点.. 重点是接下来到了激动人心的打开Maya环节.讲道理..这是我第二次打开Maya…真·萌新; [文件->导入].导入完我看了一眼吓得本萌新喵躯一震..口列娃纳尼? 为啥身体被分成了三段…这是何等的恶人才能干出的事儿啊(大雾).讲道理我一开始以为是Maya对obj格式的兼容问题.但有人告诉我,Maya对obj资呲的不错.然后我又想到是不是导出的问题?..我甚至考虑了3D妹抖2是不是只建了这三段的模型?….不过后来再次证明了我是个sb而且图样图森破… 在我不经意之间,鼠标轻划过屏幕勾勒出了一片…一片网格?! 凭借这本萌新敏锐的自觉.和十多分钟的摸索.我觉得.导出没问题.也不是3D妹抖建模的锅..只是..它变!透!明!了!..结果果然是透明度的问题..调回来就好辣..(不过他为啥会变透明啊? 求大佬详解~) 我们可以在[大纲视图]中查看透明的区域.并在右边的[属性编辑器]中把透明度调回来; 以此类推..我们还可以把贴图打上..按下[数字6]进入Maya的着色显示模式..(我才不会告诉你们我一开始没开着色显示弄了半个点的贴图…..被自己傻到);然后发现有的贴图已经被打上去了..而有的地方还是单调的模型灰..先把皮肤打上吧…看到右边的[属性编辑器]里有个[颜色]选项了吗? 看到颜色后面那个长的跟一个门似得的黑色框型小按钮了吗? 就是他,点一下,找到你存放模型的目录..找到长的跟皮肤似得的贴图..打开 让我们把所有的贴图都打上..不知道为啥有的地方贴图只有渲染才能看见.比如眉毛睫毛….而且那个神一般的阿诺德渲染器本baka完全玩不转啊.. 没有渲染: 五官不知道为啥都没了 阿诺德渲染器: 讲道理,这渲染效果真吊,就是那眉毛和眼睛是什么鬼啊! (求哪位大佬告诉我~) Maya软件渲染:终于有一个正常点的了.小伙…还挺帅的嘛..不过因为是CPU渲染..速度嘛..你们感受一下,我I7-6700HQ渲染540960分辨率的3D运动模糊产品级别,一帧用了6S.. *硬件2.0渲染: 速度快了一个数量级….但是效果..我真不会调","tags":[{"name":"随笔","slug":"随笔","permalink":"http://yoursite.com/tags/随笔/"},{"name":"Maya","slug":"Maya","permalink":"http://yoursite.com/tags/Maya/"}]},{"title":"C++笔记-C++11的一些新标准","date":"2017-05-17T15:47:06.000Z","path":"2017/05/17/C++笔记-C++11的一些新标准/","text":"一些C艹11的新标准…反正都是些泥萌大家都知道的过时东西; initializer_list C++11提供了模板类initializer_list,可将其作用于参数,如果类有接受initializer+list作为参数的构造函数,则初始化列表就语法只能用于该构造函数,且列表中的元素必须是同一种类型或可以转换为同一种类型;这个类包含了成员函数begin()和end()用于获得列表范围;123456789101112#include<initializer_list>double sum(std::initializer_list<double> il){ double tot = 0; for(auto p= il.begin(), p!=il.end(), p++) tot += *p; return to;}int main(){ double sum({2.5,3.1,4});} decltype 关键字decltype将变量的类型声明为表达式指定的类型; decltyoe(x) y让y的类型与x相同,x是一个表达式; 比如:123456double x;int n;decltype(x*n) q; // q和x*n的类型一样 即 doubledecltype(&x) pd; //pd和&x的类型一样 即 *doubledecltype(n) n1; //n1的类型和n一样 即intdecltype((x)) d1;//d1的类型为 &double 这在定义模板的时候挺有用的,因为只有在模板被实例化的时候才能够确定类型;12345template <typename T, typename U>void ef(T t, U u){ decltype(T*U) tu; } 返回类型后置 C++11新增了一种函数声明语法,在函数名和参数列表后面指定返回类型1auto f1(double ,int)-> double; // 返回double 他能让你使用decltype来指定模板函数类型:12345template <typename T, typename U>auto my(T t, U u) -> decltype(t * u){ ...} 这里解决的问题是,在编译器遇到my的形参列表之前,T U还不在作用域内,因此必须使用后置返回类型; 模板别名: using = 他能够创建模板别名,和typedef不同的是,他可以用于模板部分具体化:123template <typename T> using arr = std::array<T,12>;//上述具体化模板array<T,int> 将int设置为12array<int,12> a1; //于是这句话可以替换为:arr<int> a1; //替换为这句; 作用域内枚举 传统的C++枚举提供了一种创建名称常量的方式; 但如果在同一个作用域内定义两个枚举,则他们不能重名;C++11新增了一种枚举解决了这些问题,这种枚举使用class或struct定义:12enum class NEW1{never,sometimes,often};enum struct NEW2{never,lever,server}; 基于范围的for循环 对于内置数组以及包含方法begin()和end(0的类,可以使用基于范围的for循环来简化编程工作;123double prices[5] = {4.99,10.99,6.87,7.99,8.49}for(auto x : prices) cout << x << endl; x将以此为prices中1的每个元素的值,x的类型应与数组元素的类型匹配; 吐过想修改数组或容器里的元素可以使用引用:123vector<int> vi(6)for(auto &x : vi) x = rand();; 默认的方法和禁用方法 假设要使用某个默认的函数,而这个函数由于某种原因没有自动创建,例如提供了移动构造函数,则编译器不会自动创建默认的构造函数,复制构造函数和复值构造函数.在这情况下使用default显示的声明这些方法的默认版本:123456class Someclass{ public: someclass(someclass &&) someclass() = default; //使用编译生成的默认构造函数} 关键字delet可以禁止编译器使用特定方法,且适用于任何函数,例如,要禁止复制构造函数可以:12345678910 class Someclass { public: someclass(someclass &&) someclass(const someclass &) }``` ### 管理虚方法: override 和 final 如果一个基类声明了一个虚方法,而我在派生类中提供了不同的版本..特征标不匹配,这将隐藏旧版本; class A{ int a;public: A(int i = 0) : a(i){} virtual void f(char ch) const {…};} class B{public: B(int i = 0) : a(i){} virtual void f(char ch) const {…};}``` 由于B定义的是f(charch)而不是f(char ch); 这导致了程序不能使用: bingo(10); b.f('@') 类似这样的代码; 所以我们可以使用override,把它放在f(char * ch) 后面.如果与基类方法不匹配则将视为错误; 而final解决了另一个问题.可能想禁止派生类覆盖特定的虚方法,谓词可在参数后面加上final;例如,下面的代码禁止A的派生类重新定义f().virtual void f(char ch) const final{...};","tags":[{"name":"笔记","slug":"笔记","permalink":"http://yoursite.com/tags/笔记/"},{"name":"代码","slug":"代码","permalink":"http://yoursite.com/tags/代码/"},{"name":"CPP","slug":"CPP","permalink":"http://yoursite.com/tags/CPP/"}]},{"title":"C++笔记-输入,输出和文件(三)","date":"2017-05-16T11:36:11.000Z","path":"2017/05/16/C++笔记-输入,输出和文件(三)/","text":"封面作者 文件输入输出### 简单文件I/O 如果想让程序写入文件,可以这样来做: 1. 创建一个 ofstream 对象来管理输出流; 2. 将该对象与特定的文件关联起来; 3. 使用cout的方式使用该对象,将输出写入文件; 4. 首先包含头文件#include<fstream>,然后声明一个ofstream的对象: ofstream fout 接下来,必须将这个对象与特定的文件关联起来,可以使用open,假设要打开文件jar.txt: ofstream.open("jar.txt"); 也可以使用构造函数将这俩构步合成一条语句: ofstream fout("jar.txt"); 然后以使用cout的方式使用fout,比如要把”boy next door”放到文件中: fout << "boy next door" ; 由于ostream是ofstream类的基类,因此可以使用所有的ostream方法.另外以这种方式打开文件来进行输入时,如果没有要打开的文件,则将新创建一个.如果有了,则打开并清空文件,并输入; 读取文件和写入文件差不多: 1. 创建一个ifstream对象来管理输入流; 2. 将该对象与特定的文件关联起来; 3. 已使用cin的方式使用该对象; 同样,首先包含头文件#include<fstream>,然后声明一个ifstream对象,将它与文件名关联起来:12345ifstream fin;fin.open("my.txt");//或者写成一句话ifstream fin("my.txt"); 可以像使用cin那样使用fin;12345678910char ch;fin >> ch;char buf[80];fin >> buf;fin.getline(buf,80);string lien;getline(fin,line); 当输入和输出流对象过期时,流到文件的链接将会关闭,但不会删除流; 可以使用close()显式关闭:12fout.close; //断开文件与写入流的链接fin.close; //断开文件与输出流的链接 下面是书上的一个栗子,输入文件名将某些信息写入文件,并读出;:12345678910111213141516171819202122232425#include <iostream>#include <fstream>#include <string>using namespace std;int main(){ string filename; cout << "输入文件名: " << endl; cin >> filename; ofstream fout(filename.c_str()); fout << "梦里不知秋已深,余情岂是为他人\\n"; cout << "out DONE" << endl; fout.close(); ifstream fin(filename.c_str()); cout << "File name :" << filename << endl; char ch; while(fin.get(ch)) cout << ch; cout << "DONE" << endl; fin.close();} ### is_open() 使用isopen检查文件是否被打开;1if (!fin.is_open()) {...} ### 文件模式 文件模式描述的是文件将如何被使用: 读 写 追加等:123ifstream fin("banjo",model);ofstream fout();fout.opem("harp",mode2); ios_base类定义了一个openmode类型;可以选择ios_base类中定义的多个常量来制定模式: ○ ios_base::in 打开文件,以便读取 ○ ios_base::out 打开文件,以便写入 ○ ios_base::ate 打开文件并移动到文件尾 ○ ios_base::app 追加到文件尾 ○ ios_base::true 如果文件存在,则清空 ○ ios_base::binary 二进制文件 ○ ios_base::app 保留文件内容,并在文件尾追加新信息 ○ 文件模式 | ios_base::ate 以指定模式打开并移动到文件尾; ifstream open()使用ios_base::in作为模式参数的默认值; ofstream open()方法使用ios_base::out|ios_base::trunc作为默认值; 可以使用 | 运算符来合并格式;","tags":[{"name":"笔记","slug":"笔记","permalink":"http://yoursite.com/tags/笔记/"},{"name":"代码","slug":"代码","permalink":"http://yoursite.com/tags/代码/"},{"name":"CPP","slug":"CPP","permalink":"http://yoursite.com/tags/CPP/"}]},{"title":"C++笔记-输入,输出和文件(二)","date":"2017-05-11T07:58:06.000Z","path":"2017/05/11/C++笔记-输入,输出和文件(二)/","text":"封面作者 下面是cin の show time cin如何检查输入 不同版本的抽取运算符(>>)查看输入流的方法是相同的.他们跳过空白(空格,换行符,和制表符)直到遇到非空白字符,但在C的单字符模式下,>>它读取从非空白字符开始,到与目标类型不匹配的第一个字符之间全部内容; 例如:123int elevation;cin >> elevation;//假设键入-123Z 运算符将读取前四个.因为他们都是有效的整数.但Z不是.有的时候键入可能没有满足程序的期望,比如输入的是ZCAR,而不是-123Z,这种情况下程序不会修改elevation的值,并返回0;12345678910111213141516171819#include <iostream>int main(){ using namespace std; cout << "Enter" << endl; int sum = 0; int input; while(cin >> input) { sum += input; } cout << "" << input << endl; cout << "Sum = " << sum << endl; return 0;}/* 输入-123Z 输出: 流状态 流状态由3个ios_base元素组成: eofbit,badbit, failbit.其中没一个元素都是一位(1或0); 当cin操作达到文件末尾时,它将设置eofbit; 当cin操作未能读取到预期的字符时,他将设置failbit; 当一些无法诊断的失败破坏流时,它将设置badbit;当三个状态为都为0时表示一切顺利,(他为什么这么熟练啊); 下表列出了位和一些报告或改变流的ios_base方法; 书上的表格奉上: 其他istream类方法 ○ get(char &)和getline(void)方法读取下一个输入字符,即使他是空格,制表符,换行符; ○ get(插入, int, char)和getline(char,int,char)在默认情况下读取一行; 单字符输入: get(char &) 假设有如下代码123456789101112int ct = 0;char ch;cin.get(ch);while(ch != '\\n'){ cout << ch; ct++; cin.get(ch);}cout << ct << endl;//输入: I C++ clearly. <Enter>//输出: I C++ clearly. 通过get(ch),代码读取,显示,并考虑空格和可打印字符;假设程序使用>>:123456789101112int ct = 0;char ch;cin.get(ch);while(ch != '\\n'){ cout << ch; ct++; cin>>get(ch);}cout << ct << endl;//输入: I C++ clearly. <Enter>//输出: IC++clearly. 代码将首先跳过空格.并且循环不会停止.因为>>跳过了换行符,所以换行不会被赋值给ch,所以循环不会被终止. 成员函数get(void) get(void)尘缘函数还读取空白.但使用返回值来将输入传递给程序: 12345678910int ch = 0;char ch;ch cin.get();while(ch != '\\n'){cout << ch;ct++;ch = cin.get()}cout << ct << endl; 到达文件尾的时候.cin.get(void)都会返回值EOF(iostream的一个符号常量).可以利用此特型这样来读取输入: 12345int ch;while((ch = cin.get()) != EOF){ //...} 字符串输入: getline(),get()和ignore(); istream & get(char *, int, char); istream & get(char *, int); istream & getline(char * int, char) istream & getline(char * int) 第一个参数用于放置输入字符串的内存单元的地址.第二个参数是要读取的最大字符数+1(+1是因为要存结尾的空字符);第三个参数用于指定用作分节符的字符;上述函数都在读取最大数目的字符或遇到换行符后停止; get()和getline()最大的差距就是,get()将换行符留在输入流中,也就是截下来的输入操作首先看到的是遗留下的换行符;而getline()舍弃换行符; ignore()接受两个参数: 一个是数字,指定要读取的最大字符数; 另一个是字符,用作输入分界字符;比如: cin.ignore(255,'\\n') 调用读取并丢弃接下来的255个字符,或直到遇到个换行符为止; 1234567891011121314151617181920212223242526272829//书上例子:#include <iostream>using namespace std;const int Limit = 255;int main(){ char input[Limit]; cout << "使用getline()接受字符串: " << endl; cin.getline(input,Limit,'#'); cout << "输出: " << endl; cout << input << "\\n Done" << endl; char ch; cin.get(ch); cout << "下一个输入字符是:" << ch << endl; if(ch != '\\n') cin.ignore(Limit,'\\n'); cout << "使用get()接受字符串:" << endl; cin.get(input, Limit, '#'); cout << "输出:" <<endl; cout << input << "\\n Done" << endl; cin.get(ch); cout << "下一个输入字符是:" << ch << endl; return 0;}//注意getline() 丢弃分界字符# 而get()不会 其他istream方法 read(), peek(), gcount() putback(). read()函数读取指定数目的字节,并将它们存在指定的位置中,并返回istream & 且 read()不会在输入后加上空字符,对于处理文件的输入输出,通常和write()配合使用; 比如:12char gross[144];cin.read(gross,144); //读取144个字符,并将他们存在gross中 peek()函数能查看下一个字符,但不提取他. while((ch = cin.peek()) != '.' && ch != '\\n') //用while检查下一个字符是否是'.'或'\\n'; gcount() 方法返回最后一个非格式化抽取方法(非>>)读取的字符数; 但对于strlen()还是setlen速度较快… putback() 函数将一个字符插入到输入字符串中.被插入的字符串将是下一条输入语句读取的第一个字符..他返回istream & (可以拼接);并且他允许将字符插入到不是刚才读取的位置..是不是想到了什么? peek..没错peek()的效果相当于使用get()读取一个字符,然后使用putback()放回去….","tags":[{"name":"笔记","slug":"笔记","permalink":"http://yoursite.com/tags/笔记/"},{"name":"代码","slug":"代码","permalink":"http://yoursite.com/tags/代码/"},{"name":"CPP","slug":"CPP","permalink":"http://yoursite.com/tags/CPP/"}]},{"title":"在细雨纷飞的那天她送了我一个礼物","date":"2017-05-09T11:42:25.000Z","path":"2017/05/09/随笔-珊瑚海/","text":"“果然还是下雨了啊,果然应该拿把伞啊..”起床的时候就觉得窗外阴沉的天空能拧出水似得,不过本着嫌麻烦的精神,我还是收回了原本伸向抽屉的手.伞什么的,拿着太麻烦了啊.说不定一会儿就放晴了呢. 还好雨下的不大,淅淅沥沥的细雨落到脚下的青石砖上发出纱纱的声响混杂着自己踩在积水上的声音 现在回想起早上立的flag真想给自己一耳光,当然,是早上的自己.不过上头也是有毒,大早上开的哪门子会啊?不过我感觉这次事情可能不小,不然也不至于我话都没说完传话员就把电话挂了 唉..这才几天的太平日子啊..想到这我加快了跑向会议室的脚步 推开那沉重的会议室大门..昏暗的指挥室内充斥着水手们常抽的劣质香烟的味道,一张桌子占据了四分之一的房间,而周围一群穿着海军军装的男人围在桌边更是显得这个临时指挥室的狭小.唯一一个披着海军军装坐着的男人显得有些格格不入.稀疏的胡茬和有些凌乱的莫西干式发型说明了这个男人已经有几天没整理过自己的仪容,你肯定能从他沧桑的脸上读出战争,血,与火的味道..,眼前这个刚毅,睿智,有些许疲惫但眼神却依旧犀利紧盯着桌子上的海图的男人就是我的上级.弗莱彻上将..“现在,我们扯平了”..”在袭击图拉吉后.我们跟对面的情报就拉平了”…夜深了,我吹着海风独自走在这艘合众国新锐的航空母舰上,战争已经悄然打响..“恩哼,睡不着吗?我可爱的的指挥官?”面对突如其来的耳语我着实吓了一跳,但旋即又陷入在那温柔而又有些俏皮的语气中,无法自拔…. “咳咳..别总是捉弄我啊..太太..好歹我也是一个小指挥官啊…”“唉? 又要什么嘛?..难道太太是一个指挥官对自己下属应有的称呼嘛?我可爱的不严谨先生?”听到这话我不禁老脸一红..确实..太太是我对这艘舰的爱称…她的温柔就好像傍晚的海风….“咳咳,,指挥官,,你脸红了哦”不知不觉太太已经面对面离我很近了,,我甚至能闻到她身上独有的洗发香波的味道,,湛蓝色的大眼睛看着我..亚麻色的头发在不算亮的舰船灯光的照射下竟发出了银白色的光泽….“指挥官,,这个给你..人家花了一天的假期给你挑的,,好好珍惜哦..”话说这好像是我第一次收到女孩子的礼物,,,而且还是我下属送的,,,不不不,,这都不是重点..重点是..太太送我东西了!我打开盒子,里面的手表发出滴答滴答的声响,深褐色的表带,黄白色的表盘…好生喜欢 “啊..啊…列克星敦姐.谢谢,,,..明天,,就,..全靠你了”“指挥官,你的脸已经红的跟苹果一样了哦..”“啥? 有..有..有吗?”“有的啦..明天指挥官也要加油哦,,,我回去啦,,晚安哦~” 太太走了,,留下了在海风中还没缓过神的我,,,我现在只能感觉到手腕传来的金属独有的那种有些凉丝丝的温度..以及…脸上的温度与湿润..还有那太太头发的芳香…","tags":[{"name":"随笔","slug":"随笔","permalink":"http://yoursite.com/tags/随笔/"}]},{"title":"_>如你所见.这里都是我的随笔","date":"2017-05-09T11:33:29.000Z","path":"2017/05/09/README/","text":"欢迎来到本智障瞎写的地方,这里充斥着平时的脑洞与灵光的乍现,不过..不论是文笔还是剧情都是小学生作文级の水准,不喜勿喷.","tags":[{"name":"随笔","slug":"随笔","permalink":"http://yoursite.com/tags/随笔/"}]},{"title":"C++笔记-输入,输出和文件","date":"2017-04-25T14:58:17.000Z","path":"2017/04/25/C++笔记-输入,输出和文件/","text":"封面作者 流和缓冲区 C++程序把输入和输出看作字节流.输入时,程序从输入流中抽取字节;输出时,程序将字节插入到输出流中.输入流中的字节可能来自键盘/硬盘/程序. 输出流中的字节可以流向屏幕/打印机/硬盘/程序.换句话说,流充当了程序和流源(流的来源)或流目标之间的桥梁.C++程序处理输出的方式将独立于其去向,因此管理输入分: ○ 将流与输入去向的程序关联起来 ○ 将流与文件链接起来. 换句话说,输入流需要两个链接,每端各一个.文件端链接来源,程序段链接将流的输出部分转存到程序中; 同样对输出流的管理包括将输出流连接到程序以及将输出目标与流关联起来; 通常使用缓冲区可以更高效地处理输入输出.缓冲区是用作中介的内存块,将信息从设备传输到程序或从程序传输给设备的临时存储工具. 缓冲方法从磁盘上读取大量信息,讲这些信息存在缓冲区中,然后每次从缓冲区里读取一个字节,因为从内存中读取单个字节的速度非常快.所以这种方法更快更方便.达到缓冲区尾部之后程序将从磁盘上读取另一块数据.输出时程序首先填满缓冲区.然后把成块数据传给硬盘,并清空缓冲区,已被下一批输出使用.这被称为刷新缓冲区(flushing the buffer). 流,缓冲区和iostream文件 ○ streambuf类维缓冲区提供内存,并提供了用于填充缓冲区,访问缓冲区内容,刷新缓冲区和管理缓冲区内存的类方法. ○ ios_base 类表示流的一般特征,如是否可读取,是二进制流还是文本流等; ○ ios类基于ios_base,其中包括了一个指向streambuf对象的指针成员; ○ ostream类是从iios类派而来的, 提供了输出的方法; ○ istream 类也是从ios类派来的,提供了输入方法; ○ iostream类是基于istream和ostream类的 因此继承了输入输出方法; 一些ostream方法 除了大家喜闻乐见的方法之外,ostream类提供了put()方法和write()方法.前者用于显示字符.后者用于显示字符串. 最初,put()方法原型如下: ostream & put(char); 当前标准被模板化,以适用于wchar_t: cout.put('W'); 还可以将数值类型参数(如int)用于put,这样: cout.put(65) // 输出A cout.put(66.3) //转为66 输出B write()方法显示整个字符串,模板原型如下: basic_ostream<charT,traits>&write(const char_type* s,streamsize n) 第一个参数提供了要显示的字符串的地址,第二个参数指出要显示多少个字符串. 下面是一个及其智障的示例:123456789101112131415161718192021222324252627#include <iostream>#include <cstring>using namespace std;int main(){ const char * s1 = "Florida"; const char * s2 = "Kansas"; const char * s3 = "Euphoria"; int len = strlen(s2); cout << "Increasing LOOP" << endl; int i; for(i = 1; i <= len; i++) { cout.write(s2,i); cout <<endl; } cout << "Decreasing LOOP:"; for(i = len; i > 0; i--) cout.write(s2,i) << endl; cout << "Exceeding string length: " << endl; cout.write(s2,len+5) << endl; return 0;} 需要注意的是,write()方法并不会在遇到空字符的时候自动停止打印.而是只打印指定数目的字符.即使超出了字符的边界. 刷新输出缓冲区 在屏幕输出时,程序不必等到缓冲区被填满.列如,将换行符发送到缓冲区之后.将刷新缓冲区.多数C++实现都会在输入即将发生的时候刷新缓冲区. 可以手动控制刷新缓冲区: 控制符flush刷新缓冲区,而控制符endl刷新缓冲区并插入一个换行符.12cout << "Kazusa" << flush;cout << "Setsuna" << endl; 其实控制符也是函数 可以直接调用flush()来刷新缓冲区: flush(cout); 用cout进行格式化### 1.修改显示时使用的计数系统: ostream类是从ios类派生而来的.而后者是从ios_base派生来的.ios_base类里都是描述格式状态的信息.例如,一个类成员中某些位决定了使用的技术系统,而另一个成员决定了字段宽度.通过使用控制符(manipulator),可以控制显示整数时使用的计数系统.比如控制字段宽度,和小数尾数. 要控制整数以十进制,十六进制,八进制.可以使用dec,hex和oct控制符. 如hex(cout) 或 cout<< hex 将其技术系统格式状态设置为16进制; 下面是一个比较智障的栗子1234567891011121314151617 int main(){ cout << "Enter an integer;"; int n; cin >> n; cout << "n n*n" <<endl; cout << n << " " << n*n << "(decimal)" << endl; cout << hex; cout << n << " "; cout << n*n << " (hexadecimal)" << endl; cout << oct << n << " " << n * n << "(octal)" << endl; dec(cout); cout << n << " "<< n *n << " (decimal)" << endl; return 0;} ### 2.调整字段宽度 在上个代码示例中,输出各列没有对齐,可以使用width函数将长度不同的数字放到宽度相同的字段中: int width(); int width(int i); 第一种格式返回字段宽度的当前设置,第二种格式将宽度设置为i个空格,并返回以前的字段宽度值,且width方法只影响将显示的下一个项目.然后字段宽度将恢复为默认,下面是个书上的例子:12345678910111213141516171819202122232425262728 #include<iostream> int main() { using namespacestd int w = cout.width(30); cout << "default field width = " << w << endl; cout.with(5); cout << "N" << ':'; cout.width(8); cout <<"N*N" << endl; for(long i=1; i <= 100; i*=10) { cout.width(5); cout << i << ':'; cout.width(5); cout<<i*i<<endl; } return 0; } /*输出: default field width = 0 N: N*N 1: 1 10: 100100: 10000 */ 上诉输出中,值在字段中右对齐,右对齐时空格被插入到值的左侧,cout通过加入空格来填满整个字段,用来填充的字符叫做填充字符(fill chararcter),并且右对齐是默认的;w的值为0是因为cout.width(30)返回的是以前的字段宽度,而不是刚设置的值. ### 3.填充字符 默认使用空格填充字段中未被使用的部分,可以用fill()成员来改变填充字符,而且新的填充字段将一直有效,直到更改他为止,用星号填充:cout.fill('*')1234567891011121314151617181920 int main(){ cout.fill('*'); const char * staff[2] = {"waldo Whipsnade","Wilmarie Wooper"}; long bonus[2] = {900,1350}; for(int i = 0; i < 2; i++) { cout << staff[i] << ": "; cout.width(7); cout << bonus[i] << endl; } return 0;}/*输出waldo Whipsnade: ****900Wilmarie Wooper: ***1350*/ ### 4.设置浮点数的显示精度 浮点数精度的函数取决于输出模式.在默认模式下,他指的是显示的总位数.在定点模式和科学模式下,精度指的是小数点后面的位数.C++默认精度为6; 将精度设置为2: cout.precision(2)12345678910111213141516171819202122232425#include<iostream>using namespace std;int main(){ float p1 = 20.40; float p2 = 1.9 + 8.0 /9.0; cout << "p1:" << p1 << endl; cout << "p2:" << p2 << endl; cout.precision(2); cout << "p1(2): " << p1 << endl; cout << "p2(2):" << p2 << endl; return 0; /* 输出p1:20.4p2:2.78889p1(2): 20p2(2):2.8*/ ### 5.打印末尾的0和小数点 下面的函数调用使cout显示末尾的小数点:cout.setf(ios_base::showpoint);showpoint是ios_base类中定义的类级静态常量;123456789101112131415161718192021222324#include <iostream>using namespace std;int main(){ float p1 = 12.40; float p2 = 1.9+8.0/9.0; cout.setf(ios_base::showpoint); cout << "p1:" << p1 << endl; cout << "p2:" << p2 << endl; cout.precision(2); cout << "(2)p1" << p1 << endl; cout << "(2)p2" << p2 << endl; return 0;}/* 输出p1:12.4000p2:2.78889(2)p1:12.(2)p2:2.8*/ ### 6.setf() ios_base类有一个受保护的数据成员,其中的各位(标记) ,像开关一样分别控制着格式化的各个方面,打开开关称为设置标记,并将相应位设为1.而setf()函数提供了一种调整标记的途径: setf()有俩原型.第一个: fmtflags setf(fmtflags); fmtflags是bitmask类的typedef,用于存储标记格式;ios_base定义了代表位值的常量: ○ ios_base::boolalpha: 输入和输出bool值,可以为true或false; ○ ios_base::showbase: 对于输出,使用C++基数前缀 0 0x ○ ios_base::showpoint: 显示末尾的小数点 ○ ios_base::uppercase: 对于十六进制输出 使用大写字符E表示法 ○ ios_base::showpos: 在整数前面加上+ 1234567891011121314151617181920212223#include <iostream>using namespace std;int main(){ int temp = 63; cout.setf(ios_base::showpos); cout << "showpos: "<<temp << endl; cout << hex << "hex: " << temp << endl; cout.setf(ios_base::uppercase); cout.setf(ios_base::showbase); cout <<"uppercase & showbase: " << temp << endl; cout.setf(ios_base::boolalpha); cout << "boolalpha(true): " <<true << endl; return 0;}/*输出showpos: +63hex: 3fuppercase & showbase: 0X3Fboolalpha(true): true*/ 第二个seft()原型接受两个参数,并返回以前的设置: fmtflags setf(fmtflags, fmtflags); 函数的这种格式用于设置由多位控制的格式选项; 第一参数和以前一样,也是一个包含了所需设置的fmtlages值.第二参数指出要清除第一个参数中的哪些位; setf()函数是ios_base类的一个成员函数;由于这个类是ostream的基类,因此可以使用cout来调用该函数: ios_base::fmtflags old = cout.setf(ios::left, ios::adjustfield) 要恢复之前的可以: cou.setf(old,ios::adjustfield); 书上的例子:1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071 #include <iostream>#include <cmath>using namespace std;int main(){ //使用左对齐,显示加号,显示尾随零,精度为3 cout.setf(ios_base::left,ios_base::adjustfield); cout.setf(ios_base::showpos); cout.setf(ios_base::showpoint); cout.precision(3); //使用e表示法,保存旧的格式设置 ios_base::fmtflags old = cout.setf(ios_base::scientific,ios_base::floatfield); cout << "左对齐:" << endl; long n; for(n = 1; n < 41; n+= 10) { cout.width(4); cout << n << "|"; cout.width(12); cout << sqrt(double(n)) << "|" << endl; } //改为内对齐 cout.setf(ios_base::internal,ios_base::adjustfield); //恢复默认浮点显示样式 cout.setf(old,ios_base::floatfield); cout << "内对齐:" << endl; for(n = 1; n < 41; n+= 10) { cout.width(4); cout << n << "|"; cout.width(12); cout << sqrt(double(n)) << "|" << endl; } //右对齐,使用定点计数法 cout.setf(ios_base::right,ios_base::adjustfield); cout.setf(ios_base::fixed,ios_base::floatfield); cout << "右对齐:" << endl; for(n = 1; n < 41; n+= 10) { cout.width(4); cout << n << "|"; cout.width(12); cout << sqrt(double(n)) << "|" << endl; } return 0;}/* 输出:左对齐+1 |+1.000e+000 |+11 |+3.317e+000 |+21 |+4.583e+000 |+31 |+5.568e+000 |内对齐+ 1|+ 1.00|+ 11|+ 3.32|+ 21|+ 4.58|+ 31|+ 5.57|右对齐 +1| +1.000| +11| +3.317| +21| +4.583| +31| +5.568| */ ## 6.iomanip 如果嫌前面那些麻烦的话,头文件iomanip中提供了一些其他控制符,他们能提供前面的部分服务.setprecision(),setfill() 和 setw(); setprecision()控制符接受一个指定精度的整数参数; setfill()控制符接受一个指定填充字符的char参数;setw()控制符接受一个指定字段宽度的整数参数;并且他们都能通过cout链接起来; 书上的例子:1234567891011121314151617181920212223242526272829303132333435363738#include <iostream>#include <iomanip>#include <cmath>int main(){ using namespace std; cout << fixed << right; cout << setw(6) << "N" << setw(14) << "square root" <<setw(15) << "fourth root" << endl; double root; for(int n = 10; n <= 100; n+=10) { root = sqrt(double(n)); cout << setw(6) << setfill('.')<< n << setfill(' ') << setw(12) << setprecision(3) << root << setw(14) << setprecision(4) << sqrt(root) << endl; } return 0;}/* 输出 N square root fourth root....10 3.162 1.7783....20 4.472 2.1147....30 5.477 2.3403....40 6.325 2.5149....50 7.071 2.6591....60 7.746 2.7832....70 8.367 2.8925....80 8.944 2.9907....90 9.487 3.0801...100 10.000 3.1623*/","tags":[{"name":"笔记","slug":"笔记","permalink":"http://yoursite.com/tags/笔记/"},{"name":"代码","slug":"代码","permalink":"http://yoursite.com/tags/代码/"},{"name":"CPP","slug":"CPP","permalink":"http://yoursite.com/tags/CPP/"}]},{"title":"C++笔记-算法(STL)","date":"2017-04-21T14:53:05.000Z","path":"2017/04/21/C++笔记-算法(STL)/","text":"封面来源 算法的一些通用特征: STL文档使用模板参数名称来表示参数模型的概念.比如下面的copy()原型:12template<class InputIterator, class OutputIterator>OutputIterator copy(InputIterator first, OutputIterator last, OutputIterator result); 所以标识符InputIterator和OutputIterator都是模板参数,看名字一眼就能看出来区间参数必须是输入迭代器或更高级的,而只是结果存储的迭代器必须是输出迭代器或更高级的. STL的有些算法有两个版本: 就地算法(in-place algorithm)和复制算法(copying algorithm),顾名思义,sort()就是就地算法的例子:函数完成时,结果被存放在原始数据的位置上; 而copy()函数将结果发送到另一个位置,所以他是复制算法;STL对此的约定是,复制版本的名称将以_copy结尾. 比如replace()函数,他将所有的old_value替换为new_value,则它的复制版本为:123template<class InputIterator,class OutputIterator, class T>OutputIterator replace_copy(InputIterator first,Inputiterator last, OutputIterator result, const T& old_value, const T& old_value); 对于复制算法的约定是: 返回一个迭代器,指向复制的超尾(最后一个值后面)的位置 另一个常见变体是: 有些函数是根据函数应用于容器元素得到的结果来执行操作的.他们通常以_if结尾,比如replace_if:123456789template < class ForwardIterator, class UnaryPredicate, class T > void replace_if (ForwardIterator first, ForwardIterator last, UnaryPredicate pred, const T& new_value){ while (first!=last) { if (pred(*first)) *first=new_value; //pred()是个一元谓词,根据其真假来决定是否将new_value赋给first ++first; }} 一个使用STL的例子:假设编写一个程序,让用户输入单次,希望最后得到一个按照输入顺序排列的单次列表,一个按照字母排序的列表(忽略大小写),并记录每个单次被输入的次数(不考虑标点,数字,符号): 输入和保存我们可以用vector<>: 1234vector<string> word;string input;while(cin >> input && input != "quit") word.push_back(input); 然后我们可以创建一个set对象,然后将vector中的单词复制(使用插入迭代器)到集合中.集合自动对其排序,无需使用sort(); 且集合只允许一个键出现一次因此也无需调用unique().至于忽略大小写…可以使用transfor,将vector中的数据复制到集合中.使用一个转换函数将函数转成小写;123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051#include <iostream>#include <string>#include <vector>#include <set>#include <map>#include <iterator>#include <algorithm>#include <cctype>using namespace std;void display(const string & a){ cout << a << " ";}char toLower(char ch){ return tolower(ch);}string & ToLower(string & st){ transform(st.begin(),st.end(),st.begin(),toLower); return st;}int main(){ vector<string> words; cout << "输入单词(quit退出): " << endl; string input; while (cin >> input && input != "quit") words.push_back(input); cout << "原始数据: " << endl; for_each(words.begin(),words.end(),display); cout << endl; set<string> word_s; transform(words.begin(),words.end(), insert_iterator<set<string>>(word_s,word_s.begin()),ToLower); cout << "list: " << endl; for_each(word_s.begin(),word_s.end(),display); cout << endl; map<string,int> wordmap; set<string>::iterator si; for(si = word_s.begin();si != word_s.end(); si++) wordmap[*si] = count(words.begin(),words.end(),*si); cout << "新数据: " << endl; for_each(word_s.begin(),word_s.end(),display); return 0;} 其他库 slice类对象可用作数组索引,他们表示的是不是一个值而是一组值.slice对象被初始化为三个整数值: 起始索引,索引数,跨距.起始索引是第一个被选中的元素的索引,索引数指出要选择多少个元素,跨距表示元素之间的距离.例: slice(1,4,3)创建的对象表示选择四个元素,他们的索引分别是1,4,7,10;从索引开始加上跨距得到下一个元素的索引. 把第1,4,7,10元素设为10:1varint[slice(1,4,3)] = 10; 一个valarrat和slice混用的例子:123456789101112131415161718192021222324252627282930313233343536373839404142434445#include <iostream>#include <valarray>#include <cstdlib>using namespace std;const int SIZE = 12;void show(const valarray<int>& v, int cols){ int lim = v.size(); for(int i = 0; i < lim; ++i) { cout.width(3); cout << v[i]; if(i % cols == cols -1) cout<< endl; else cout << ' '; } if(lim % cols != 0) cout << endl;}int main(){ valarray<int> vint(SIZE); int i = 0; for(; i < SIZE; ++i) vint[i] = rand() % 10; cout << "原始数据: " << endl; show(vint,3); valarray<int> vcol(vint[slice(1,4,3)]); cout << "第二列: " << endl; show(vcol,1); valarray<int> vrow(vint[slice(3,3,1)]); cout << "第二行: " << endl; show(vrow,3); cout << "将最后一列设为10:" <<endl; show(vint,3); cout << "将第一列设置为下两个的总和:" << endl; vint[slice(0,4,3)] = valarray<int>(vint[slice(1,4,3)]) + valarray<int>(vint[slice(2,4,3)]); show(vint,3); return 0;} 模板initializer_list(C++11) 模板initializerlist(在头文件initializer中)是C++11新增的,可以使用初始化列表语法将STL容器初始化为一系列值:12vector<double> payments(45.99,39.23,19.95,89.01);//创建一个包含四个元素的容器,并用列表中的四个值来初始化这些元素; 这么做可行是因为容器包含了将initializer_list作为参数的构造函数.比如vector就包含了一个将initializer_list作为参数的构造函数,因此上述声明和下面的等同;1vector<double> payments( {45.99,39.23,19.95,89.01} ); 这是C++11新增的通用初始化语法,可以使用{}而不是()来调用构造函数,并且在类有接受initializer_list作为参数的构造函数则将优先使用:1shared_ptr<double> pd{new double}; 所有initializer_list元素的类型都必须相同,但编译器有的时候将尽心必要转换:12vector<double> payments {45.99, 39.23, 19, 89.0};//19会转为19.0,但不能进行窄化转换,也就是不能隐式的把上面的double转为int; initializer_list使用例子,另外他还包含成员函数begin() end() size():123456789101112131415161718192021222324252627282930313233343536#include <iostream>#include <initializer_list>using namespace std;double sum(initializer_list<double> il){ double s = 0; for(auto p = il.begin(); p != il.end(); p++) s += *p; return s;}double average(const initializer_list<double> & av){ double s = 0; double n = av.size(); if(n > 0){ for(auto p = av.begin(); p != av.end(); p++) s += *p; s = s/n; } return s;}int main(){ cout << "List 1: sum = " << sum({2,3,4}); cout << "ave = " << average({2,3,4}) << endl; initializer_list<double> dl = {1.1, 2.2, 3.3, 4.4, 5.5}; cout << "List 2: sum = " << sum(dl); cout << "ave = " << average(dl) << endl; dl = {16.0, 25.0, 36.0, 40.0, 64.0}; cout << "List 3: sum = " << sum(dl); cout << "ave = " << average(dl) << endl; return 0;} 可以按值传递initializer_list对象,也可以按引用传递;STL是按值传递;","tags":[{"name":"笔记","slug":"笔记","permalink":"http://yoursite.com/tags/笔记/"},{"name":"代码","slug":"代码","permalink":"http://yoursite.com/tags/代码/"},{"name":"CPP","slug":"CPP","permalink":"http://yoursite.com/tags/CPP/"}]},{"title":"C++笔记-函数对象","date":"2017-04-17T14:58:36.000Z","path":"2017/04/17/C++笔记-函数对象/","text":"作者 很多STL算法都使用函数对象也叫函数符.函数符是可以以函数方式与()结合使用的任意对象.包括函数名,指向函数的指针,和重载了()运算符的类对象(即定义了函数operator()()的类)可以定义这样一个类:12345678910class Linear{ private: double slope; double y0; public: Linear(double s = 1, double y = 0) : slope(s),y0(y){} double operator() (double x){return y0+slope * x;}}; 这样重载的()运算符将能够像函数那样使用Lineat对象:123Linear f2(2.5,10.0);double y1 = f1(12.5);double y2(0.4) 其中y1将使用表达式0+112.5来计算,y2将使用表达式10.0+2.50.4来计算.y0和slope的值来自对象的构造函数,而x的值来自于operator()()的参数; 话说回来,还记得for_each()的第三个参数吗? 通常,第三个参数可以是常规函数,也可是函数符.那么如何声明第三个参数呢?STL使用模板解决了这个问题,for_each的原型看上去:12345template<class InputIterator, class Function>Function for_each(InputIterator fist,InputIterator last,Function f);//ShowReview()的原型如下:void ShowReview(const Review &); 这样表示符ShowReview的类型为void(*)(const Review &),将其赋给模板参数f,也是Function的类型,Function是可以表示具有重载的()运算符的类类型.f是指向函数的指针,而f()调用该函数.如果参数是个对象,则f()是调用其重载的()运算符的对象; 函数符概念 ○ 生成器(generator) 是不用参数就可以调用的函数符 ○ 一元函数(unary function) 是用一个参数可以调用函数符,返回bool值的一元函数的是谓词(predicate) ○ 二元函数(binary function) 是用两个参数可以调用的函数符,返回bool值的二元函数是二元谓词(binary predicate) 例如:sort()的一个版本,将二元谓词作为其第三个参数:12bool WorseThan(const Review & r1, const Review & r2);sort(books.begin(),books.endl(),WoresThan); list模板有一个将谓词作为参数的remove_if()成员,该函数将谓词应用于区间中的所有元素,如果谓词返回true则删除这些元素,例如删除链表three中所有大于200的元素:1234bool tooBig(int n){n > 100};list<int> scores;scores.remove_if(tooBig); 下面这个类演示了类函数符适用的地方,假设要删除另一个链表中所有大于200的值,且如果能将被比较的值作为第二个参数传给tooBig(),那我们就可以使用不同的值调用该函数辣,但谓词只能有一个参数,这可咋整..:123456789template<class T>class TooBig{ private: T cutoff; public: TooBig(const T & t) : cutoff(t) {}; bool opeator()(const T & v){return v > cutoff;}} 这里,一个值(v)是作为函数参数传递的,另一个cutoff是由类的构造函数设置的. 下面的程序演示了这种技术:12345678910111213141516171819202122232425262728293031323334353637383940#include <iostream>#include <list>#include <iterator>#include <algorithm>using namespace std;template<typename T>class TooBig{public: T cutoff; TooBig(const T & t) : cutoff(t) {} bool operator()(const T & c){return c > cutoff;} //重载()};void outint(int n){cout << n << " ";}int main(){ TooBig<int> f100(30); //创建实例初始化cutoff为30 int vals[10] = {10,20,30,40,50,60,70,80,90,100}; list<int> a(vals,vals+10); //创建俩链表 list<int> b(vals,vals+10); //输出链表 for_each(a.begin(),a.end(),outint); cout << endl; for_each(b.begin(),b.end(),outint); cout << endl; a.remove_if(f100); //此时cutoff为30.即在a中删除所有大于30的数 b.remove_if(TooBig<int>(50));//创建匿名对象 for_each(a.begin(),a.end(),outint); cout << endl; for_each(b.begin(),b.end(),outint); cout << endl; return 0;} f100是一个声明的对象,而TooBig(50)是个匿名对象; 综上,我们可以将接受两个参数的模板函数转化为接受单个参数的函数对象例如:123456789101112template <typename T> //接受两个参数的模板函数bool tooBig(const T & t1,const T & t2){ return t1 > t2};template<typename T> //接受单个参数的模板函数对象class TooBig{private: T val;public: TooBig(const T & t) : val(t){} bool operator()(const T & ot) {return t > val;}} 即可以这么写:1234TooBig<int> my(30);int x;cin << x;if( my(x)){...} //和tooBig(x,100)一样 调用my(x),相当于调用了tooBig(x,100),换句话说类TooBig是个函数适配器,可以使函数满足不同的接口; 预定义的函数符: transform()函数有两个版本,第一个版本的前两个参数是指定容器区间的迭代器,第三个参数将结果复制到哪里的迭代器,第四个参数则是一个函数对象(函数符);比如:12transform(l.begin(),l.end(),out,sqrt);//你就当l是个容器实例,out是个输出迭代器,sqrt是开平方;//这就是吧容器l里的挨个开平方,并发送到输出流 第二个版本与第一个版本有所不同,版本二是一个二元函数,其第三个参数为第二个区间的起始位置,后面的参数不变,所以说如果我们想算俩容器里每个数的平均值就可以:12double add(double x, double y){return x + y};transform(l.begin(),l.end(),l2.begin(),out,add); 但这样吧….就得给每种类型都定义一个add,,,所以让我们用模板吧比如plus<>..那么让我们看看SLT预定的几个函数符: 自适应函数符合函数适配器 上表列出的预定义函数符都是自适应的,自适应函数符携带了表示参数类型和返回类型的typedef成员,他们分别是:result_type,first_argument_type和second_argument_type.比如plus对象的返回类型被表示为plus::result_type,这是int的typedef; 函数符自适应性的意义在于: 函数适配器对象可以使用函数对象,并使用typedef成员.例如,接受一个自适应函数符参数的函数可以使用result_type成员来声明一个与函数的返回类型匹配的变量. 比如现在我们想将容器l里的每个元素都乘上2.5..那么跟上面那个一样我们是用:1transform(l.begin(),l.end(),out,multiplies); 但是吧multiplies是个二元函数…所以我们得把接受两个参数的函数符(multiplies)转换成接受一个参数的multiplies.我们使用神奇的bind1st();假设现在有个二元函数对象f2(),可以把f2()的第一个参数的值跟f2()相关联,所以我们对multiplies的二元转一元就可以写成:12bind1st(multiplies<double>,2.5); //将mu和2.5相关联.2.5将用于mu的第一个参数; 第二个参数在此:12transform(l.begin(),l.end(),out,multiplies<double>,2.5);//这样容器l里的每个元素都将作为mu的第二个参数;达到乘2.5的目的","tags":[{"name":"笔记","slug":"笔记","permalink":"http://yoursite.com/tags/笔记/"},{"name":"代码","slug":"代码","permalink":"http://yoursite.com/tags/代码/"},{"name":"CPP","slug":"CPP","permalink":"http://yoursite.com/tags/CPP/"}]},{"title":"C++笔记-泛型编程(续","date":"2017-04-11T14:35:05.000Z","path":"2017/04/11/C++笔记-泛型编程(续/","text":"封面来源 容器种类 STL具有容器概念和容器类型.概念是具有名称的通用类别,容器类型是可用于创建具体对象的模板. 容器概念 容器概念描述了所有STL容器都需要满足的一些要求.他是个概念化的抽象基类,说他是一个概念化的抽象基类,是因为容器类不真正使用继承机制. 所有的容器都提供某种特定的特征和操作,下表对一些通用特征进行了总结: 上表中的复杂度表示执行操作所需的时间,从快到慢依次为: ○ 编译时间 ○ 固定时间 ○ 线性时间 如果复杂度为编译时间,则操作将在编译时执行,执行时间为0;固定复杂度意味着操作发生在运行阶段,但独立于对象中的元素数目;线性复杂度意味着时间与元素数目成正比; C++11新增的容器要求 下表列出了C++11新增的通用容器要求,其中rv表示类型为X的非常量右值,如函数返回值,另外要求X::iterator满足正向迭代器的要:; 序列(sequence) 序列概念增加了迭代器至少是正向迭代器的要求,并且要求元素按严格的线形顺序排列,即存在第一个元素,最后一个元素,除了端点的俩元素外,每个元素前后都分别有一个元素,且不会在两次迭代之间发生变化.这7个STL容器类型都是序列: deque,forward_list,list,queue,priority_queue,stack,vector; 下表表示了序列所需要的操作,t表示类型为T(存在容器中的值的类型)的值,n表示整数,p,q,i,j表示迭代器: 因为模板deque,list,queue,priority_queue,stack和vector都是序列的概念模型,所以他们中的一些还可以使用下表所列出的操作: 详细介绍 vector: 前面说过了,不做赘述 deque: deque模板类表示双端队列,实现类似vector,他支持随机访问,但不同的是:从deque对象的开始位置插入和删除元素的时间是固定的,而vector是在结尾处提供了固定时间的插入和删除,在而别的地方都是线性时间,但vector的速度似乎快一点.. list: list模板类表示双向链表.除了第一个和最后一个元素外,每个元素都与前后的元素相连接.他和vector的区别在于,list在链表中任意位置插入和删除的时间是固定的(废话).list擅长的是元素的快速插入和删除.但他不支持数组表示法和随机访问.从容器中管插入或删除元素后,链表迭代器指向的位置不变,但数据不同.然后插入新元素并不会移动已有元素,只是修改链接信息. list的成员函数,Alloc有默认值: ○ void merge(list & x) //将链表x与调用链表合并.两个链表必须排序,合并后的链表在调用链表中,x为空.这个函数复杂度为线性时间; ○ void remove(const T & val) //从链表中删除val的所有实例,复杂度为线性时间; ○ void sort() //使用<运算符排序,复杂度为NlogN; ○ void splice(iterator pos, list x) //将链表x的内容插入到pos的前面,x为空,复杂度为固定时间; ○ void unique() //将连续的相同元素压缩为单个元素(删除重复元素),复杂度为线性时间 list的示例代码 insert()和splice()之间的区别在于:insert()将原始区间的副本插入到目标地址,而splice()则将原始区间移动到目标地址,且splice()方法执行后,迭代器指向的元素不变. forward_list(C++11): forward_list容器类实现了单链表.也就是每个节点都只连接到下一个节点,而没有连接到前一个节点.所以他只需要正向迭代器,并且是不可反转容器; queue: queue模板类是个适配器类.他让底层类(默认为deque)展示典型的队列接口.但他限制比deque多,不允许随机访问队列,不允许遍历队列,他可以: ○ bool empty()const //如果队列为空,返回true,否则返回false ○ size_type size()const //返回队列中元素的数目 ○ T& front() //返回指向队首元素的引用 ○ T& back() //返回指向队尾元素的引用 ○ void push(const T& x) //在队尾插入x ○ void pop() //删除队首元素 priority_queue: 该类是另一个适配器,支持的操作和queue一样,区别是在priority_queue中最大的元素会被移动到队首; 构造函数参数:12priority_queue<int> pql;priority_queue<int> pq2(greater<int>) stack: 该类也是个适配器,和queue相似,他给底层类(默认是vector)提供了典型的栈接口;但他不允许随机访问栈元素,不允许遍历栈,只能用一些栈的基本操作,如压入,弹出,查看栈顶值,检查元素数目,测试栈是否为空,他还可以: ○ bool empty() const //如果栈为空则返回true,否则返回false ○ size_type size() const //返回栈中元素数目 ○ T& top() //返回指向栈顶元素的引用 ○ void push(const T& x) //在顶部插入x ○ void pop() //删除栈顶元素 array(C++11): 非SLT容器,其长度为固定,所以也就没有调整容器大小的操作(如push_back(),insert()),但定义了一些成员函数如operator [] ()和at().可将很多STL算法用于他(如copy(),for_each()); 关联容器(associative container) 关联容器是将值于键值关联在一起,并使用键来查找值.表达式X::key_type指出了键的类型.他们提供了对元素的快速访问(查找).STL提供了四种关联容器: set, mulitiset,map,multimap. 前两种在文件set.h中,后两者在map.h中定义. 最简单的关联容器是set,其值类型与键值相同,键是唯一的,对于set来说值就是键; multiset类似set,只是可以有多个键值相同,比如: 如果建和值的类型为int,则multiset对象包含的内容可以有1,2,2,2,3,5等.而在map中,值于键的类型不同,键和值是一一对应的;而multimap和map差不多,只是一个键可以与多个值相关联; set示例:12345 const int N = 6; string s1[N] = {"ass","we"."can","booy","next","can"}; set<string> A(s1,s1+N); ostream_iteratior<string,char> out(cout," "); copy(A.begin(),A.end(),out); 和别的容器相似set也使用模板来制定要储存的值类型,与其他容器相似,set也有一个将迭代器区间作为参数的构造函数.上述代码片段输出表明键是唯一的,虽然数组里有俩for,但如果两个集合包含相同的值,则这个值将在并集中只出现一次,所以for在集合中只出现了一次,这是因为set的键值是唯一的,且集合是经过排序的:cout: ass booy can next we set_union()函数接受五个迭代器参数,前两个迭代器定义了第一个集合的区间,接下来的两个参数定义了第二个集合的区间,最后一个参数是输出迭代器,指出将结果集合复制到什么位置.举个栗子,要显示集合A,B的并集可以:1set_union(A.begin(),A.end(),B.begin(),B.end(),ostream_iterator<string,char> out(cout," ")); 假设要将记过放到C集合中,而不是现实他,则最后一个参数应该是个匿名insert_iterator,将信息复制给C:1set_union(A.begin(),A.end(),B.begin(),B.end(),insert_iterator<set<string>>(C,C.begin())); 方法lower_bound()将键作为参数并返回一个迭代器,迭代器指向集合中的第一个不小于键参数的成员,upper_boun()将键作为参数并返回一个指向集合中第一个大于键参数的成员.函数set_intersection()和set_difference()粉蝶查找交集和获得两个集合的差,参数与set_union()相同; multimap示例: 基本的multimap声明使用模板参数指定键的类型和存储的值类型.他一个键可以与多个键值相关联.下面声明创建一个multimap对象,键类型为int,值类型为string,第三个模板参数是可选的,用于对键排序,默认使用less<>:1multimap<int,string> codes; 如果要用区号作为键来存城市名字,一种方法是创建个pair,再将它插入:12345pair<const int, string> item(213,"Los Angeles");codes.insert(item);//或创建匿名pair对象:codes.insert(pair<const int, string> (213,"Los Angeles");) 对于pair对象,可以使用first和second访问其两个部分:12pair<const int, string> item(213,"Los Angeles");cout << item.first << item.second << endl; 成员函数count()接受键作为参数,返回具有该键的元素数数目.lower_bound()和upper_bound()将键作为参数,并分别返回集合中第一个不小于键参数的成员和第一个大于键参数的成员;equal_range()用键做参数,返回两个迭代器,表示的区间与该键匹配. 无序关联容器 无需关联容器也是将值与键关联起来,并用键找值,但底层差别在于关联容器基于树形结构,而无序关联容器基于哈希表,速度奇快.比如unordered_set,unordered_map,unordered_multiset,unordered_multimap;","tags":[{"name":"笔记","slug":"笔记","permalink":"http://yoursite.com/tags/笔记/"},{"name":"代码","slug":"代码","permalink":"http://yoursite.com/tags/代码/"},{"name":"CPP","slug":"CPP","permalink":"http://yoursite.com/tags/CPP/"}]},{"title":"C++笔记-泛型编程","date":"2017-04-05T14:20:46.000Z","path":"2017/04/05/C++笔记-泛型编程/","text":"封面作者 STL是一种泛型编程.面向对象编程关注的是程序的数据方面,而泛型编程关注的是算法.泛型编程旨在编写独立于数据类型的代码.在C艹中通常使用的工具是模板,模板是的能够按照泛型定义函数和类,而STL通过通用算法更近了一步. 为何使用迭代器 模板使得算法独立于存储类型,而迭代器使算法独立于使用的容器类型.所以他们都是STL通用方法的组成部分. 如果在一个double数组中搜索特定值可以这样写:1234567double * find_ar(double * ar, int n, const double & val){ for(int i = 0; i < n; i++) if(ar[i] == val) return &ar[i];//如果在数组中找到val则返回val在数组中的地址; return nullptr; //否则返回一个空指针} 在这里我们基于了特定的数据结构(数组)可以使用模板来将这种算法推广到包含==运算符的任意类型的数组. 不过我们也可以使用链表:12345struct Node{ double item; Node * p_next;} 假设有一个指向链表第一个节点的指针,每个节点的p_next都指向下一个节点,链表最后一个节点的p_next被设置为nullptr…则可以这么写:12345678Node * find_ll(Node * head,const double & val){ Node * start; for(start= head; start!=nullptr; start = start->p_next) if(start->item == val) return start; return nullptr;} 他俩一个使用数组,一个将start重置为start->p_next,广义上他们都一样:将值于容器中的值比较,直到找到匹配为止.但在这里我们基于了特定的数据结构(链表) 而泛型编程旨在使用同一个find函数来处理数组,链表,或其他容器类型.函数不仅独立于容器中储存的类型,而且独立于容器本身的数据结构.模板为我们提供了存储在容器中的数据类型的通用表达,所以我们还需要遍历容器中的值的通用表达.而迭代器正好是干这活的… 要实现find函数,我们的迭代器应该具备以下特征: 应能够对迭代器执行解引用操作,一边能够访问他引用的值,即如果p是个迭代器,则能对*p进行定义; 应能够将一个迭代器赋值给另一个.即如果p和q是迭代器.则能够对p=q进行定义; 应能够使用迭代器遍历容器中的所有元素,这可以为迭代器定义++p和p++来实现; 应能够将一个迭代器和另一个迭代器进行比较,看他们是否相等.即如果p和q都是迭代器则能对p==q和p!=q进行定义 对于find函数来讲,有上述功能就够了,STL按照功能的强弱定义了不同等级的迭代器.另外常规指针就能满足迭代器的要求,可以使之接受两个指示区间的指针参数,其中一个指向数组的起始位置另一个指向数组的超尾;并且如果我们的函数没找到指定的值,那么让他返回尾指针,因此可以这样重写find_ar():123456789typedef double * iterator;iterator find_ar(iterator begin,iterator end,const double & val){ iterator ar; for(ar = begin; ar!=end; ar++) if(*ar == val) return ar; return end;} 对于find_ll()函数,可以定义一个迭代器类,其中定义了运算符*和++;123456789101112131415161718192021struct Node{ double item; Node * p_next;}class iterator { Node * pt;public: iterator() : pt(nullptr){} iterator(Node * p) : pt(p){} double operatror*(){return pt->p_next;} iterator & operator++(){ //++pt pt = pt->p_next; return *this; } iterator & operator++(int){ //int表示后缀版++运算符,该int参数永远不会用到只做区分 iterator temp = *this; pt = pt->p_next; return temp; } //你就假装我这里重载了==和!=} 废了这么大劲之后,我们的find函数改就可以这样写:12345678iterator find_ll(iterator head, const double & val){ iterator start; for(start = head; head!=0; ++start) if(*start = val) return start; return nullptr;} 机智的你一定发现了我这不学好的一天净扯淡: 这踏马不废话么这俩玩意(函数)一毛一样啊,差别就是find_ar用了超尾.find_ll返回的是nullptr啊..其实我们还可以让链表的最后一个元素后面还有个空白元素..这样链表也有了超尾.他们俩就成了真·一个(功能的)算法; //我是第一个分割线↓ 前面的废话都是为了下面的废话做铺垫的: STL其实就是遵循上面的方法.首先每个容器类(vector, list等)定义了相同的迭代器类型,对于其中的某个类,迭代器可能是指针;但对于另一个类,迭代器可能是对象.但他们都将提供列如 * 和++等这样的操作.其次每个容器类都有个超尾标记,当迭代器递增到容器最后一个值的后面的时候把这个值赋值给迭代器.每个容器类都有begin()和end()方法,begin返回指向第一个元素的迭代器,end返回一个指向超尾的迭代器.每个容器类都有++操作用来遍历容器 尽是废话 使用迭代器的时候不用管他是咋实现的..知道咋用就成(如果你碰到需要自己定义迭代器的情况当我放屁):观众: 你智障吧废话谁踏马不会用这玩意 哎呦你咋知道的…没错我就是智障.在知道了迭代器大概是咋构造的之后的我们可以更加自信满满的并没有雄赳赳气昂昂跨..啊不..写出迭代器:123vector<double>::iterator pr; //这玩意是输出vector<double>里的元素for(pr = sco.begin(); pr != sco.end(); pr++) cout << *pr << endl; 这里本萌妹使用了C艹11,做一个炫酷的紧跟潮流个屁的智障: 12for(auto pr = sco.begin(); pr != sco.end(); pr++) cout << *pr << endl; 作为一种编程风格,最好直接使用STL函数来处理细节,如for_each(),或C艹11的迷之基于范围的for循环:1for(auto x: sco) cout << x << endl; 迭代器类型 STL定义了五中迭代器,分别是: 输入迭代器,输出迭代器,正向迭代器,双向迭代器,随机访问迭代器;他们都可以执行解引用操作 输入迭代器: 从程序的角度来说,即来自容器内部的讯息被视为输入,因此输入迭代器可被程序用来读取容器中的信息,但不一定能让程序修改容器里的值. 输入迭代器必须能访问容器中所有的值(废话),可以通过++运算符来实现(废话).但输入迭代器并不保证第二次遍历容器时顺序不变,另外当迭代器被递增后,也不保证先前的值可以被解引用.基于输入迭代器的算法都应该是单通行(single-pass)的,输入迭代器是单项迭代器,可以递增,但不能递减. 输出迭代器: 输出指的是将信息从程序传输给迭代器,因此程序的输出就是容器的输入,和输入迭代器差不多,他只能够解引用让程序能修改容器的值,也就是只能写,而不能读. 对于单通行,只读算法可以使用输入迭代器,对于单通行,只写算法,则可以使用输出迭代器. 正向迭代器: 正向迭代器与输入输出迭代器不同的是,他总是按照相同的顺序遍历一系列值.另外正向迭代器递增后仍可以怼前面的迭代器值解引用,并得到相同的值. 既可以读写又可以只读:12int * pirw; //读写const int * pir;//只读 双向迭代器:具有正向迭代器全部特征,并且支持(前后缀)递减运算符; 随机迭代器:具有全部双向迭代器的特征,并且能够根据要求直接跳到容器中任何一个元素; 概念,改进和模型(完全不知道书上在说啥)下面是咬文嚼字时间 迭代器是一系列要求,而不是类型.STL算法可以使用任何满足要求的迭代器所实现,STL术语叫概念(concept)来描述一系列要求.概念可以具有继承的关系,例如双向迭代器继承了正向迭代器的功能,然而不能将继承机制用于迭代器: 假设我们将双向迭代器实现为一个常规指针,而指针属于C++内置类型,不能从派生而来.但从概念上将他确实能够继承.所以有些STL文献使用术语改进(refinement)来表示这种概念上的继承.so,双向迭代器是正向迭代器概念的一种改进.而概念的具体实现叫做模型(model). 将指针用于迭代器 迭代器是广义指针,指针满足所有迭代器要求,因此STL算法可以使用指针来对基于指针的非STL同期进行操作其实就是可以将STL用于数组…..123const int SIZE = 100;double Rec[SIZE];sort(Rec,Rec+100); sort()函数接受指向容器第一个元素的迭代器和指向超尾的迭代器作为参数.Rec或&Rec[0]是第一个元素的地址,Rec+SIZE是最后一个元素后面的元素的地址;由于指针是迭代器,而算法是基于迭代器的,所以可将STL算法用于常规数组. 假设要把信息输出到显示器上,可以使用一个表示输出的迭代器则可以使用copy();STL有一个ostream_iterator模板是输出迭代器的一个概念模型:123#include <iterator>...ostream_iterator<int,char> out_iter(cout," "); //注意这双引号里有个空格 out_iter是个接口,能够使用cout来显示信息,第一个模板参数指出被发送给输出流的数据类型,第二个参数(car)值出了输出流使用的字符串类型; 将copy()用于迭代器:12345copy(dice.begin(),dice.end(),out_iter);//这意味着将dice容器整个区间复制到输出流中,即显示容器里的内容//也可以直接构建个匿名的迭代器:copy(dice.begin(),dice.end(), osteram_iterator<int,char>(cout," ")); 这样用表示将由15和空格组成的字符串发送到cout的输出流中:1*cout_iter++ = 15; // 和 cout << 15 << " "; 一样 有输出就有输入,可以用俩isteram_iterator模板对象来定义copy()的输入范围,如下,isteram_iterator的第一个参数(int)是指出要读取的数据类型,第二个参数指出输入流使用的字符串类型(char),构造函数参数cin意味着使用由cin管理的输入流,构造函数参数为空则表示输入失败:12copy(istream_iterator<int,char>(cin), istream_iterator<int,char>(), dice.begin() ); //从输入流中读取,直到失败; 下面的代码演示了如何使用copy和istream迭代器以及反向迭代器:1234567891011121314151617181920212223242526#include<iostream>#include<iterator>#include<vector>using namespace std;int main(){ using namespace std; int casts[10] = {6,7,2,9,4,11,8,7,10,5}; vector<int> dice(10); copy(casts,casts+10,dice.begin()); cout << "原始数据: " << endl; ostream_iterator<int,char> out_iter(cout," "); copy(dice.begin(),dice.end(),out_iter); cout << endl; cout << "使用了反向迭代器重新输出:" << endl; copy(dice.rbegin(),dice.rend(),out_iter); cout << endl; cout << "使用了vector类的reverse_iterator: " << endl; vector<int>::reverse_iterator ri; for(ri = dice.rbegin(); ri != dice.rend(); ++ri) cout << *ri << ' '; cout << endl;return 0;} 但是上述代码是在其已知了dice的大小的情况下进行的,如果不知道容器的大小呢?,并且还是向容器中刚添加元素(而不是像上述代码覆盖已有内容),这种情况下咋整?有三种插入迭代器可以将复制转为插入解决问题,他们使用动态内存分配插入新元素: back_insert_iterator; //将元素插入到容器尾,但只能用于允许在尾部快速插入的容器; (vector符合) front_insert_iterator; //将元素插入到容器前面,但只能用于允许在起始位置做时间固定插入的容器; insert_iterator; //将元素插入到insert_iterator构造函数的参数指定位置之前; 这些迭代器将容器类型作为模板参数,将模板名作为构造函数参数:1back_insert_iterator<vector<int>>back_iter(dice); //为名为dice的vector<int>容器创建个back_insert_iterator 下列程序演示了两种迭代器的使用方法,并且使用for_each()输出:1234567891011121314151617181920212223242526272829#include <iostream>#include <string>#include <iterator>#include <vector>#include <algorithm>using namespace std;void output(const string & s){cout << s << " ";}int main(){ string s1[4] = {"F","A","♂","Q"}; string s2[2] = {"B","L"}; string s3[2] = {"M","J"}; vector<string> words(4); copy(s1,s1+4,words.begin());//words地方够,可以复制进来 for_each(words.begin(), words.end(), output); cout << endl; copy(s2,s2+2,back_insert_iterator<vector<string>>(words));//从后面插♂入 for_each(words.begin(),words.end(), output); cout << endl; copy(s3,s3+2,insert_iterator<vector<string>>(words,words.begin())); //插♂前面 for_each(words.begin(),words.end(), output); cout << endl; return 0;}","tags":[{"name":"笔记","slug":"笔记","permalink":"http://yoursite.com/tags/笔记/"},{"name":"代码","slug":"代码","permalink":"http://yoursite.com/tags/代码/"},{"name":"CPP","slug":"CPP","permalink":"http://yoursite.com/tags/CPP/"}]},{"title":"C++笔记-Vector","date":"2017-03-28T13:34:47.000Z","path":"2017/03/28/C++笔记-Vector/","text":"画师P站ID:61403923 标准库模板 STL提供了一组表示,容器,迭代器,算法,函数对象的模板.容器是一个与数组类似的单元,可以储存若干个类型相同的值.算法用来完成特定任务(如对数组进行排序)的处方;迭代器用来遍历容器里的对象,与能够遍历数组的指针类似,是广义的指针.函数对象是类似于函数的对象,可以是类对象或函数指针. 模板类Vector 可以创建vector对象; 将一个vector对象赋值给另一个,或者使用[]运算符来访问vector中的元素.头文件vector中定义了vector模板举个书上的栗子,输入书名和评分:123456789101112131415161718192021222324252627#include <iostream>#include <string>#include <vector>using namespace std;const int NUM = 5;int main(){ vector<int> vi(NUM); vector<string> vs(NUM); cout << "请输入" << NUM << "个书名和你的评分" << endl; int i = 0; for(;i < NUM;i++) { cout << "#第" << i+1 << "本:"; getline(cin,vs[i]); cout << "\\n 请输入评分:"; cin >> vi[i]; cin.get(); } for(i = 0; i < NUM; i++) { cout << "名字: " << vs[i] << " 评分:" vi[i] << endl; } return 0;} 对vector的操作 size(): 返回容器中的元素数目; swap(): 交换两个容器的内存; begin(): 返回一个指向容器中第一个元素的迭代器; end(): 返回一个表示超过容器尾的迭代器; 什么是迭代器? 他是一个广义的指针(恩没错就是指针),可以对其执行类似指针的操作比如解引用,递增;每个容器都定义了适合的迭代器,一般是名为iterator的typedef,其作用域为整个类.比如声明一个vector的迭代器可以这样做:1234567891011vector<double>::iterator pd; //pd是一个迭代器vector<double> sum;//sum是一个vector对象pd = sum.begin(); //将pd指向sum的第一个元素*pd = 22.3; //对pd解引用,把22.3赋值给pd所指向的元素(也就是sum的第一个元素);++pd; //使pd指向下一个元素//另外可以不这么写: vector<double>::iterator pd = sum.begin();//而是这样做:auto pd = sum.begin(); //C++11 前面说过超过容器尾的迭代器,那么什么是超过结尾呢(past-the-end)?它是一种迭代器,指向容器最后一个元素后面的那个元素的指针.end()成员函数表示超过结尾的位置,如果将迭代器设为容器的第一个元素,然后自加.则最终可将它到达容器结尾,从而遍历整个容器.12for(pd=sum.begin(); pd != sum.end(); pd++)cout << *pd << endl; push_back()是一个方便的方法,他讲元素添加到矢量末尾,这样他讲负责管理内存,增加矢量的长♂度,使之容纳下新成员:12345vector<double> sco;double temp;while(cin >> temp && temp >= 0) sco.push_back(temp); //只要有足够的内存,程序可以根据需要增加sco的长度 cout<< "你输入了:" << sco.size() << "个元素"; erase()方法可以删除是两种给定区间的元素.他接受两个迭代器的参数,这俩参数定义了要删除的区间.第一个迭代器指向区间的起始处,第二个指向区间的末尾.例如下列代码删除了[sco.bgein(),sco.begin()+2)区间内的元素:1sco.erase(sco.begin(),sco.begin()+2); inster()方法的功能与erase()相反,他接受三个迭代器参数,第一个指向了新元素的插入位置,第二三个迭代器定义了被插入区间,这个区间通常是另一个容器对象的一部分;就是把A容器的一部分复制出来,插入到B容器的某一位置;列入下列代码将new_v中的除了第一个元素之外所有的元素查到old_v矢量的第一个元素前面:1234vector<int> old_v;vector<int> new_v;old_v.inster(old_v.begin(),new_v.begin()+1,new_v.end()); 对Vector的其他操作 矢量模板并不包括如搜索,排序,随机排序等.STL从更广泛的角度定义了非成员函数(non-member)来执行这些操作.但有的时候,即使有执行相同任务的非成员函数,STL还会定义一个成员函数,因为有的操作使用特定的算法比使用通用的算法效率更高.比如Vector的swap()效率比非函数成员的swap()高;但非函数成员能交换俩类型不同的容器的内容; 三个具有代表性的STL函数: for_each(); fandom_shuffle(); sort(); for_each()接受三个参数,前两个是定义容器中区间的迭代器,最后那个是指向函数的指针(函数对象).被指向的函数不能修改容器元素的值,这玩意可以代替for用:12345678void ShowStr(const string &str){ cout << str << endl;}vector<string> books;vector<string>::iterator pr;for(pr = books.begin(); pr != books.end(),pr++) //正常的for ShowStr(*pr); //上面的for可替换为: for_each(books.begin(),books.end(),ShowStr) //这样可以避免显示使用迭代器 random_suffle()该函数接受两个指定区间的迭代器参数,并随机排列该区间的元素,前提是该函数要求可以对容器任意访问,显然vector满足:1random_shuffle(books.begin(),books.end()); sort()函数也要求容器资呲随机访问.该函数有俩重载,一个接受两个定义区间的迭代器参数,并使用容器定义的<运算符容器内容进行升序排序.12 vector<int> myint; sort(myint.begin(),myint.end()); 但如果容器元素是用户自定义的,那么则要给你自定义的类填个<的重载,也就是operator<()..比如我自定义了个结构或类Rev:12345678910111213struct { string title; int rating;};bool operator<(const Rev & rl, const Rev & r2){ if(r1.title < r2.title) return true; eles if(r1.title == r2.title && r1.rating < r2.rating) return true; else return false;} 然后就可以对包含Review对象(如books)的矢量进行排序了:1sort(books.begin(),books.end()); 上述程序是按照title成员的字母顺序排序的,如果title一样就按rating排序.如果想降序排序或者按照rating排序,可以使用另一种格式的sort().他接受三个参数,前两个参数也是指定区,间的迭代器,最后一个参数指向要使用的函数的指针(函数对象) 说白了就是函数名 而不去使用operator<().:123456bool WorseThan(const Rev & r1, const Rev & r2){ if(r1.rating < r2.rating) return true; eles return false;} 有了这函数之后就可以….将Rev对象的books矢量按照rating升序排列(你不说降序么)..:1sort(books.begin(),books.end(),WorseThan); 上述代码与operator<()相比,WorseThan()函数对Rev的排序工作不是那么完整,WorsThan()中如果俩对象的title一样,则视为相同,这叫完整弱排序(strict weak ordering).而operator()中如果俩对象的title一样则比较rating,这叫全排序(total ordering); 基于范围的for循环 基于范围的for循环是为了STL而设计的:123double prices[5] = {4.66,10.99,6.54,6.55,8.48};for(double x : prices) cout << x << endl; 在这种for循环中,括号内的代码声明一个与容里内容类型相同的变量,然后指出了容器名称,循环将使用指定的变量x依次访问容器的每个元素…. 比如 for_each(books.begin(),books.end(),ShowStr);可以写成:1for(auto x : books) ShowStr(x); //根据books将推断出x的类型为Rev 不同于for_each(),基于范围的for循环能修改容器里的内容,你只需要指定一个引用参数:12void reRev(Rev & r){r.rating++;}for(auto & x: books) reRev(x);","tags":[{"name":"笔记","slug":"笔记","permalink":"http://yoursite.com/tags/笔记/"},{"name":"代码","slug":"代码","permalink":"http://yoursite.com/tags/代码/"},{"name":"CPP","slug":"CPP","permalink":"http://yoursite.com/tags/CPP/"}]},{"title":"C++笔记-智能指针","date":"2017-03-26T14:45:49.000Z","path":"2017/03/26/C++笔记-智能指针/","text":"封面来源 使用智能指针 C++有三个智能指针模板(auto_ptr,unique_ptr,shared_ptr),都定义了类似于指针的对象,可以将new获得的地址赋值给这种对象.(其中auto_ptr是C++98提供的解决方案,C++11已经摒弃,不建议使用).当智能指针过期时,其析构函数将使用delete来释放内存.另外share_ptr和unique_ptr的行为与auto_ptr相同. 要使用智能指针首先包含头文件memory.1auto_ptr<double> pd(new double); new double是new返回的指针,指向新分配的内存块.他是构造函数auto_ptr<double>的参数.即对应于原型中的形参p的实参.同样new double也是构造函数的实参.其他两种智能指针的写法:12unique_ptr<double> pdu(new double);shared_ptr<string> pss(new string); 下列代码举例了全部三种智能指针:123456789101112131415161718192021222324252627282930#include <iostream>#include <string>#include <memory>using namespace std;class Report{private: string str;public: Report(const string s) : str(s) {cout << "Objectt created:" << endl;} ~Report() {cout << "Object deleted" << endl;} void comment() const {cout << str << endl;}};int main(){ { auto_ptr<Report> ps(new Report("auto_ptr")); ps->comment(); } { shared_ptr<Report> ps(new Report("shared_ptr")); ps->comment(); } { unique_ptr<Report> ps(new Report("unique_ptr")); ps->comment(); } return 0;} 智能指针很多地方和正常指针类似,可以对他进行解引用操作( *p),用它来访问成员(p->fun),将他赋值给指向相同类型的常规指针,或者赋值给另一个同类型的只能指针; unique_ptr为何优于auto_ptrauto:123auto_ptr<string> p1(new string("auto"));auto_ptr<string> p2;p2 = p1; 在第三句,p2接管string对象所有权后,p1的所有权被剥夺,但如果程序随后视图使用p1,则是件坏事因为p1已经不再指向有效的数据;unique_ptr:123unique_ptr<string> p3(new string("auto"));unique_ptr<string> p4;p4 = p3; 编译器认为第三句非法,避免了p3不再指向有效数据的问题; 但有时候将一个智能指针赋值给另一个不会出现悬挂指针的危险:12345678unique_ptr<string> demo(const char * s){unique_ptr<string> temp(new string);return temp;}...unique_ptr<string>ps;ps = demo("2333333"); demo()返回一个临时unique_ptr,然后ps接管了demo的临时返回值,ps拥有了string对象的所有权.因为demo返回的临时对象很快就会被销毁.所以没有机会使用它来访问无效的数据,所以编译器允许这种赋值.所以就是,如果源unique_ptr是个临时右值,编译器将允许赋值,但如果源unique_ptr将存在一段时间,编译器将禁止这样做..….. 如果一定要写类似于p3p4那样的代码.要安全的使用指针的话,可以给他赋新值,C++有个标准库函数std::move(),能够让你将一个unique_ptr赋给另一个.:12345using namespace std;unique_ptr<string> ps1, ps2;ps1 = demo("23333");ps2 = move(ps1);cout << *ps2 << *ps1 << endl; unique_ptr还可以当数组用:1unique_ptr<double[]>pda(new double(5)); 为啥unique_ptr能知道安全和不安全的用法呢? 因为它使用了C++11的移动构造函数和右值引用…. 选择智能指针 如果程序要使用多个指向同一个对象的指针,应选择shared_ptr,必去有个指针数组,并使用一些辅助指针来标示特定的元素(比如最大值最小值);比如两个对象都指向第三个对象的指针; stl容器;这些操作都可以使用shared_ptr;但不能用unique_ptr和auto_ptr; 如果程序不需要多个指向同一个对象的指针,则可以使用unique_ptr.如果函数使用new分配内存并返回指向该内存的指针.则其返回类型声明为unique_ptr是个不错的选择;这样所有权将转让给接受返回值的unique_ptr,而该智能指针将负责调用delete;12345678910111213141516unique_ptr<int> make_int(int n){ return unique_ptr<int> (new int (n)); }void show(unique_ptr<int> & pi){ cout << *a << " ";}int main(){ vector<unique_ptr<int>> vp(size); for(jint i = 0; i < vp.size(); i++) vp[i] = make_int(rand() % 1000); vp.push_back(make_int (rand() % 1000)); for_each(vp.begin(),vp.end(),show());} 其中的push_back()调用没毛病,因为他返回一个临时的unique_ptr对象.另外如果安置而不是按引用给show()传递对象,则for_each()将会非法,因为这将导致使用一个来自vp的非临时unique_ptr初始化pi,前面说过,这是不行的. 在unique_ptr为右值的时候,可将其赋值给shared_ptr,要求和赋值给另一个unique_ptr一样:123unique_ptr<int> pup(make_int(rand%1000)); //假设make_int返回类型是unique_ptrshared_ptr<int> spp(pup);shared_ptr<int> spr(make_int(rand() % 1000)); //假设make_int返回类型是unique_ptr 模板shared_ptr有个显示构造函数,可将右值的unique_ptr转化为shared_ptr.shared_ptr将接管原来的unique_ptr所有的对象;","tags":[{"name":"笔记","slug":"笔记","permalink":"http://yoursite.com/tags/笔记/"},{"name":"代码","slug":"代码","permalink":"http://yoursite.com/tags/代码/"},{"name":"CPP","slug":"CPP","permalink":"http://yoursite.com/tags/CPP/"}]},{"title":"C++笔记-string类","date":"2017-03-25T14:51:53.000Z","path":"2017/03/25/C++笔记-string类/","text":"string类的构造函数:size_type是一个依赖于实现的整形在头文件string中定义的.string类将string::npos定义为字符串的最大长度,通常为unsigned int的最大值.NBTS(null-terminated-string)来表示空字符结束的字符串.12345678910111213141516string(const char *s); //将string对象初始化为s指向的NBTSstring(size_type n, char c) //创建一个包含n个元素的string对象,其中每个都被初始化为cstring(const string &str) //将一个string对象初始化为string对象str(复制构造函数)string() //创建一个长度为0的默认string对象string(const char * s,size_type n) //创建一个裁剪了s的前n个字符串的对象.template<class Iter>string(Iter begin, Iter end) //将一个string对象初始化为区间[begin,end)内的字符串,begin和end就像指针,用于指定位置string(const string & str, string size_type pos = 0, size_type n = npos)//将一个string对象初始化为对象str中从位置pos开始到结尾的字符,或从pos的位置到第n个字符串;string(string && str) noexcept//C++11新增,将一个string对象初始化为string对象str,并且可能修改str(移动构造函数)string(initializer_list<char>il) //C++11新增,将一个string对象初始化为列表il中的字符 第五个构造函数将一个C-风格字符串和一个整数作为参数,其中的整数参数来表示要复制多少个字符串(如果20被改成40则将会继续复制字符串..将导致十五个无用的字符串被复制到five的结尾处):12char alls[] = "All's well that ends well";string five(alls,20);//这里只是用了前二十个字符来初始化five对象. 第六个构造函数有一个模板参数:1template<class Iter>string(Iter begin,Iter end); begin和end想指针那样指向内存中的两个位置,构造函数将使用begin和end之间的值对string对象初始化.1string six(alls+6,alls+10) //six被初始化为well; 在这里数组名相当于指针,所以alls+6和alls+10的类型都是char,因此类型char将替换Iter.第一个参数指向数组(从零开始)alls中的第六个字符串(w),第二个参数指向alls的第十个(well后面的空格); 现在假设要用该构造函数将对象初始化为另一个string对象(假设为five)的一部分内容,则下面这句不管用:1string seven(five+6,five+10); 原因在于对象名并不是数组名,所以five不是个指针,但five[6]是一个char值,&five[6]则是个地址,因此可以被用作该构造函数的一个参数:1string seven(&five[6],&five[10]); 第七个构造函数将一个string对象的部分复制到构造的对象中:1string eight(four,7,16);//从four的第八个字符开始将16个字符复制到eight中 C++11新增的构造函数: 构造函数string(string && str)类似于复制构造函数,导致新的string为str的副本,但跟复制构造函数不一样的是,他不保证将str视为const.这种构造函数被称为移动构造函数(move constructor). 构造函数string(initialzer_list)使得下面的声明是合法的:12string p = {'L','U','C','K'};string c{'L','U','C','K'}; string类输入: 对于string有两种输入方式:123456string stuff;cin >> stuff;getline(cin,stuff);//getline有个可选参数,用于指定使用哪个字符串来确定输入的边界:getline(stuff,':'); //string的getline会自动调整大小使得正好容得下输入的字符串' 虽然string的getline能自动调节大小,但是有一些限制,如过想要读取一个文本,那么string对象所允许的最大长度(大小为unsigned int的值)可能不够. string的getlin从输入中读取字符串并将其储存到目标string中,直到发生下面三种情况之一: 到达文件尾. 遇到分界字符(如 \\n) 读取的字符数达到最大值; 使用字符串 可以比较字符串.string类对于全部六个关系运算符都进行了重载.如果在机器排列序列中,一个对象位于另一个对象的前面,则前者小于后者.如果机器排列序列为ASCII码,则数字江小鱼大写字符,大写字符小于小写字符.12345678910string str1 = "cobra";string str2 = "coral";string str3[20] = "anaconda";if(str1 < str2) ...if(str1 == str3) ...if(str3 != str2) ...//可以确定字符串的长度.size()和length()成员函数都返回字符串中的字符数:if(str1.length() == str2.size()) ... 可以以多种方式在字符串中搜索给定的字符串或字符.重载的find方法:12345678size_type find(const string & str, size_type pos = 0) const;//从字符串的pos位置开始,查找字符串str,找到了则返回该字符串首次出现时其首字符的索引,没有就返回string::npossize_type find(const char* s,size_type pos = 0) const; //同上size_type find(char ch, size_type pos = 0)const; //同上size_type find(const char* s,size_type pos = 0, size_type n);//从字符串的pos开始,查找s的前n个字符串组成的子字符串.找到则返回子字符串首次出现的首字符的索引,否则返回string::npos 除此之外还有: rfind()方法查找子字符串或字符串最后一次出现的位置; find_first_of()方法在字符串中查找参数中任何一个字符串首次出现的位置; find_last_of()方法用法相同,查找的是最后一次出现的位置 find_first_not_of()方法在字符串中查找第一个不包含在参数中的字符串","tags":[{"name":"笔记","slug":"笔记","permalink":"http://yoursite.com/tags/笔记/"},{"name":"代码","slug":"代码","permalink":"http://yoursite.com/tags/代码/"},{"name":"CPP","slug":"CPP","permalink":"http://yoursite.com/tags/CPP/"}]},{"title":"C++笔记-类型转换运算符&友元,异常の总结","date":"2017-03-23T14:51:04.000Z","path":"2017/03/23/C++笔记-类型转换运算符&友元,异常的总结/","text":"图片来源 类型转换运算符 C++对于类型转换采取更严格的限制.并添加了四个类型转换运算符: dynamic_cast const_cast static_cast reinterpret_cast; dynamic_cast运算符前面介绍过.假设有俩类High和Low,而ph和pl的类型分别为High和Low,则仅当Low是High的可访问基类(直接或者间接)时,下述语句才能将Low*指针赋给pl:1pl = dynamic_cast<Low*>ph; 该运算符的作用是能够在类层次结构中进行向上类型转换. const_cast运算符只有一种用途的类型转换,即改变值为const或volatile(去掉/增加const/volatile特性):1const_cast<type-name>(expression) 如果类型的其他地方也被修改则上述类型将出错.除了cosnt或volatile特征(有或无)可以不同外,type_name和expression的类型必须相同.再次假设High和Low:12345High bar;const High * pbar = &bar;...High * pb = const_cast<High*>(pbar); //没毛病,将删除pbar的const标签const Low * pl = const_cast<const Low*>(pbar);//不行,因为他尝试将const High*改为const Low* 然而其实也可以不用const_cast:12345High bar;const High * pbar = &bar;...High * pb = (High *)(pbar);Low * pl = (Low *)(pbar); 但修改const值的结果可能是不确定的,请看示例:123456789101112131415161718void change(const int * pt, int n){ int *pc; pc = const_cast<int * >(pt); *pc += n;}int main(){ int popl = 38383; const int pop2 = 2000; cout << "pop1,pop2: " << pop1 << "," << pop2 << endl; change(&pop1,-103); change(&pop2,-103); cout<< "pop1,pop2"<< pop1 << "," << pop2 << endl; return 0;} 调用change()时修改了pop1,但没有修改pop2.在change()中,虽然指针pt被声明为const int ,但const_cast去掉了pt的const标签,并赋给了pc,所以pc能修改pop1的值.但仅当指针指向的值不是const时才可行,所以pc不能修改pop2的值. *static_cast语法:1static_cast<type-name>(expression) 仅当typename能被隐式的转换成expression所属类型或expression能被隐式的转换成typename所属的类型的时候,上述转换才是合法的.假设Low是High的基类,而P是一个无关的类,则从High转换到Low,或者从Low转换到High的时候是合法的,而从Low转到p则是非法的.123456High bar;Low blow; High * pb = static_cast<High*>(&blow); //可以 Low * pl = static_cast<Low *>(&bar); //可以 P * pmer = static_cast<Pond *>(&blow) //不行 reinterprete_cast几乎能资呲所有的类型转换,比如可以将指针类型转化为能存下这个指针的整形,但不能将指针转化为更小的整形或浮点型.并且不能讲函数指针转化为数据指针,且不能去掉const标签;语法:1reinterpret_cast<type-name>(expression); 示例:1234struct dat(short a, short b);long value = 0xA224B118;dat * pd = reinterpret_cast<dat *>(&value);cout << hex << pd->a;//显示 前两个字节的值 友元,异常总结 类可以将其它函数,其他类的程序作为自己的友元.在一些情况下需要使用向前声明.并需要注意正确的组合类和方法的顺序; 嵌套类是声明在其他类中的类,但不比是其公有接口的组成部分. 当异常触发时,程序将控制权转交给匹配的catch块,catch块里面的是解决异常或终止程序的代码,在catch块之前的是try块,直接或间接导致异常的函数调用必须放在try块中. RTTI可以检测对象的类型.dynamic_cast运算符可以用于将派生类指针转化为基类指针,其可以安全的调用虚函数.typeid运算符返回一个type_info对象.可以对两个typeid的返回值进行比较看看是不是特定的类型.而type_info对象可用于获得关于对象的信息 而dynamic_cast,static_cast,const_cast,和retinterpret_cast提供了安全的明确的类型转换.","tags":[{"name":"笔记","slug":"笔记","permalink":"http://yoursite.com/tags/笔记/"},{"name":"代码","slug":"代码","permalink":"http://yoursite.com/tags/代码/"},{"name":"CPP","slug":"CPP","permalink":"http://yoursite.com/tags/CPP/"}]},{"title":"C++笔记-RTTI(运行阶段类型识别:Runtime Type Identification)","date":"2017-03-20T14:53:04.000Z","path":"2017/03/20/C++笔记-RTTI/","text":"霞之丘诗羽 RTTI这个听上去银瓶乍破水浆迸铁骑突出刀枪鸣的名字正是我大C艹的运行阶段类型识别(Runtime Type Identification)的简称 RTTI可以通过基类的指针和引用来检测这些指针和引用所指向的派生类对象的具体类型. 假设有一个类层次结构,其中的类都是从一个基类派生而来的,则可以让基类指针指向其中的任何一个类的对象.在处理一些信息之后,选则一个类,并创建这种类型对象,然后返回这个对象的地址,而这个地址可以赋值给基类指针,那么如何确定这个对象的类型? 只有知道了类型,才可能调用类方法的正确版本,如果在这个类结构层次中,所有的成员都拥有虚函数,在这个时候就不需要知道对象的类型.但派生类可能包含一些新的方法,在这种情况下只有某些类型可以使用该方法.这时候就可以使用RTTI提供解决方案. dynamic_cast 运算符: 这个运算符可以检测是否可以安全的将对象的地址赋值给特定的类型指针. 假设有下列类层次结构:12345678910111213class Grand{};class Superb: public Grand(){}class Magn : public Superb(){}//假设有下列指针:Grand * pg = new Grand;Grand * ps = new Superb;Grand * pm = new Magn;//假设有下列类型转换,那么谁是比较安全的?:Magn * p1 = (Magn *) pm; //安全,因为相同类型的指针指向了相同类型的对象.Magn * p2 = (Magn *) pg; //不安全,因为派生类指向基类,而派生类可能有些方法是基类没有的.Superb * p3 = (Magn *) pm;//安全,因为基类指向派生类. 所以,问题”指针指向的是那种类型”和”类型转换是否安全”,类型是否安全更通用点.原因在于:要调用类方法.类型并不一定要完全匹配,儿科一是定义了方法的与你版本的基类类型. dynamic_cast语法:12 Superb * pm = dynamic_cast<Superb *>(pg); //指针pg的类型如果可以被安全的转换为Superb*则运算符将返回对象的地址,否则返回一个空指针 下列代码演示了这种处理,首先他定义了三个类.Grand类定义了一个虚函数Speak(),并且其他类都重定义了这个虚函数,Superb类定义了一个虚函数Say(),而Magn也重定义了他.程序定义了GetOne()函数用来随机创建这三种类中的某种类对象,并对其初始化,然后将地址作为Grand*指针返回并赋给pg.然后使用pg调用Speak().因为这个函数是虚的随意代码能够正确的调用指向的对象的Speak()版本.1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556class Grand{private: int hold;public: Grand(int h = 0) : hold(h){} virtual void Speak() const{cout << "Grand" << endl;} virtual int Value() const{return hold;}};class Superb : public Grand{public: Superb(int h = 0) : Grand(h){} void Speak() const {cout << "Superb" << endl;} virtual void Say() const{cout << "superb value of " << Value() << endl;}};class Magn : public Superb{private: char ch;public: Magn(int h = 0,char c = 'A') : Superb(h),ch(c){} void Speak() const {cout << "Magn" << endl;} void Say() const{cout << "I hold the character " << ch << "and the integer " << Value() << endl;}};Grand * GetOne(){ Grand * p; switch(std::rand() % 3) { case 0 : p = new Grand(std::rand() % 100); break; case 1 : p = new Superb(std::rand() % 100); break; case 2 : p = new Magn(std::rand() % 100,'A' + std::rand() % 26); break; } return p;}int main(){ std::srand(std::time(0)); Grand * pg; Superb * ps; for(int i = 0; i < 5; i++) { pg = GetOne(); pg->Speak(); if(ps = dynamic_cast<Superb *>(pg)) //如果类型转换成功,则ps为非零.失败则返回空指针0. ps->Say(); } return 0;} typeid运算符和type_info类 typeid运算符使得能够确定两个对象是否为同种类型.他接受两种参数: 类名 结果为对象的表达式 typeid运算符返回一个对type_info对象的引用,其中type_info是在头文件typeinfo中定义的一个类.这个类重载了==和!=运算符,以便可以使用这些运算符来对类型进行比较.1typeid(Magn) == typeid(*pg); //如果pg指向的是一个Magn对象,则表达式结果为bool值.true,否则为false. 如果pg是一个空指针,则程序将引发bad_tyoeid异常.type_info包含了一个name()方法,该函数返回一个随着实现而异的字符串…通常是类名.1 cout<< "Class Name:" << tyoeid(*pg).name() << endl; 有瑕疵的RTTI例子: 不讨论大家对RTTI的争论,介绍一个应该避免的编程方式:123456789 Grand * pg;Superb * ps;for(int i = 0; i < 5; i++){ pg = GetOne(); pg->Speak(); if(ps = dynamic_cast<Superb *>(pg)) //如果类型转换成功,则ps为非零.失败则返回空指针0. ps->Say();} 通过不使用dynamic_cast和虚函数,而使用typeid(),可将上述代码重写为:1234567891011121314151617181920Grand * pg;Superb * ps;for(int i = 0; i < 5; i++){ pg = GetOne(); if(typeid(Magn) == typeid(*pg)) { pm = (Magn*) pg; pm->Speak(); pm->Say(); } else if(typeid(Superb) == typeid(*pg)) { pm = (Superb*) pg; pm->Speak(); pm->Say(); } else pg->Speak();} 上述代码不仅sb而且还有毛病..如果从Magn类派生出一个Insu的类,而后者需要重新定义Speak()和Say().则必须修改for循环,添加一个else if.但下面的语句适合所有从Grand派生出的类:1234pg->Speak();//下面语句适合所有从Superb派生而来的类:if(ps = dynamic_cast<Superb *>(pg))ps->Say(); 所以说如果发现ifelse中使用了typeid,则应该考虑是否使用虚函数和dynamic_cast","tags":[{"name":"笔记","slug":"笔记","permalink":"http://yoursite.com/tags/笔记/"},{"name":"代码","slug":"代码","permalink":"http://yoursite.com/tags/代码/"},{"name":"CPP","slug":"CPP","permalink":"http://yoursite.com/tags/CPP/"}]},{"title":"C++笔记-异常(3)","date":"2017-03-19T14:51:16.000Z","path":"2017/03/19/C++笔记-异常(3)/","text":"图片来源 异常何时会迷失方向异常被引发之后,有两种情况会导致问题: 意外异常(unexpected exception): 如果异常是在带异常规范的函数中引发的,则必须与规范列表中的某种异常匹配(在继承层次结构中,类类型与这个类机器派生类的对象匹配)就是得有个catch块能跟你写的异常符合/能接受你那异常,否则则成为意外异常.(C++11已经摒弃这东西了,然而有的代码还在用它) 举个正确的栗子: 通过给函数制定异常规范,可以让函数的用户知道要捕获那些异常.放屁,真水1234567891011 double Argh(doouble,double) throw(out_of_bounds) { ... try{ x = Argh(a,b); } catch(out_of_bounds & ex){ ... } .. } 未捕获异常(uncaught exception): 如果异常不是在函数中引发,则必须捕获他.如果没有捕获(在没有try块或没有catch块时,将出现) 在默认情况下,出现上述情况程序将被终止,然而可以修改程序对上述异常的反应…未捕获异常不会导致程序立刻停止,首先会调用terminate()函数,terminate()会调用abort()函数,但可以修改terminate()调用的函数(不让他调用abort()).可以使用set_terminate()函数来修改.(在#include中):12345typedef void (*terminate_handler)();terminate_handler set_terminate(terminate_handler f) throw(); //C++ 98terminate_handler set_terminate(terminate_handler f) noexcept; //C++ 11void terminate(); //C++ 98void terminate() noexcept; // C++ 11 typedef使得terminate_handler成为一个指向没有参数和返回值的函数的指针.set_terminate()将这个不带任何参数且返回类型为void的函数的名字(地址)作为参数,并且返回这个函数的地址,如果set_terminate()被多次调用,则terminate()将调用最后一次set_terminate调用设置的函数. 举个栗子:一个未被捕获的异常导致程序打印一条消息,然后调用exit()函数.12345678910111213#include<exception>using namespace std;void myQuit(){ cout << "由于出现未捕获异常,程序终止"; exit(5);}int main{ set_terminate(fun);//如果出现未捕获异常,将终止操作指定为调用fun(); throw; //触发未捕获异常} 现在如果程序引发未捕获异常,则将调用terminate(),而terminate()将会调用myQuit(). 原则上,异常规范应该包含函数调用的其他函数所引发的异常.比如A()调用了B(),而B()可能引发retor对象异常,则A(),B()的异常规范中都应该包含retort. 如果函数引发了其异常规范中没有的异常呢?这样处理起来就比较繁琐,所以C++11也将其摒弃.所以说这玩意咋看都像是坑 那么在这种情况下,行为与未捕获异常极其相似,程序将调用unexpected().这函数将调用terminate(),后者在默认情况下调用Abort().跟terminate()一样,有一个可以修改其行为的set_terminate()一样,也有一个用于修改unexpected()的set_unexpected():12345typedef void (*unexpected_handler)();unexpected_handler set_unexpected(unexpected_handler f) throw(); //C++98unexpected_handler set_unexpected(unexpected_handler f) noexcept; //C++11void unexpected(); //C++98void unexpected() noexcept;//C++0x 然而set_unexpected()比set_terminate()更加严格,unexpected_handler函数可以: 通过调用terminate()(默认行为).abort(),exit()等来终止程序 引发异常 如果新引发的异常原来的相匹配,则程序将开始寻找引发新异常的函数规范; 如果新引发的异常跟原来的不一样,且异常规范中没包括sed::bad_exception则将调用terminate(). 如果新引发的异常跟原来的不一样,且异常规范中包括了sed::bad_exception则不匹配的异常会被sed::bad_exception异常取代. 有关异常的注意事项 使用异常会降低程序运行速度.(废话 异常规范不适用于模板. 因为模板函数引发的异常随特定的具体化而异. 异常和动态内存分配并非总能协同工作 动态内存分配和异常:正常:1234567void fun(){ string mesg("Boy Next Door"); ... if(..) throw exception(); ... return;} 当函数结束时,将为mesg调用string的析构函数,虽然throw过早的终止了函数,但因为栈解退的存在使得析构函数仍然被调用完成清理. 有瑕疵:12345678void fun(int n){ string * mesg = new string[n]; ... if(..) throw exception(); ... delete [] mesg; return;} 这里有个瑕疵: 当栈解退时,将删除栈中的变量mesg,但函数过早的终止意味着句尾的delete [] mesg;被忽略.指针虽然没了,但内存还没被释放且不可访问…这就容易造成一些问题.. 修改版:123456789101112131415void fun(int n){ string * mesg = new string[n]; ... try{ if(..) throw exception(); //捕获异常 } catch(exception & e){ delete [] mesg; //清理 throw; //重新引发 } ... delete [] mesg; return;} 然而可以用智能指针解决该问题","tags":[{"name":"笔记","slug":"笔记","permalink":"http://yoursite.com/tags/笔记/"},{"name":"代码","slug":"代码","permalink":"http://yoursite.com/tags/代码/"},{"name":"CPP","slug":"CPP","permalink":"http://yoursite.com/tags/CPP/"}]},{"title":"C++笔记-异常(2)","date":"2017-03-14T14:46:57.000Z","path":"2017/03/14/C++笔记-异常(2)/","text":"封面来源 其他异常特型(一些碎碎念 虽然throw-catch机制类似于函数参数和返回机制,但还是有所不同. 其一便是函数中的返回语句是将控制权返回到调用函数的函数,但throw语句将向上寻找,并将控制权返回给一个能够捕获相应异常的try-catch组合. 另一个不同便是,引发异常是编译器总是创建一个临时拷贝.举个栗子:123456789101112131415class problem{...}void fun() throw (problem) // 表示函数只能抛出problem类型的异常.更简单的写法: throw problem(){ if(...){ problem opps; throw oops; }}...try{ super();}catch(problem &p){ // 这里的p指向的是oops的副本而不是其本身,因为函数执行完毕之后 oops当不复存在; ....} 对于接在函数名后面的throw(something): void fun() throw(); //表示fun函数不允许抛出任何异常,即fun函数是异常安全的 void fun() throw(…); //表示fun函数可以抛出任何形式的异常 void fun() throw(exceptionType); // 表示fun函数只能抛出exceptionType类型的异常 使用引用传递参数更重要的原因是,基类可以使用派生类对象.现在假设有个异常类层次结构,冰妖分别处理不同的异常类型,则使用基类引用能够捕获任何异常对象;而使用派生类对象则只能捕获他所属类以及他的派生类的对象. 因为基类可以使会用派生类对象,而且引发异常的对象将被第一个与之匹配的catch块捕获,那么所以catch块的排列顺序应该与派生类顺序相反:这又是一个一不留神就会留下bug的功能123456789101112131415161718class bad_1{...};class bad_2 : public bad_1{..};class bad_3 : public bad_2{...}...void duper(){ ... if(...) throw bad_1(); if(...) throw bad_2(); if(...) throw bad_3();}...try { duper();}catch (bad_3 & b3){...}catch (bad_2 & b2){...}catch (bad_1 & b1){...} 如果将catch(bad_1 & b1)放在最前面,他将捕获 bad_1,bad_2,bad_3, 只能通过相反的顺序排列,bad_3才会被bad_3处理程序所捕获. 所以说,如果有一个异常类继承层次结构,应该这样排列catch块: 将捕获位于层次结构最下面的异常类的catch语句放在最前面,将捕获基类异常的catch语句放在最后面;也就是倒着写 exception类 exception头文件定义了exception类,C++可以把它用作于其他异常类的基类.使代码可以引发exception异常,他有一个名曰what()的虚方法,因为是个虚方法所以你可以根据你的实现重定义他. 12345678910111213#include <exception>class bad_hmean : public std:: exception{ public: const char * what() {return "不要总想搞个大新闻";}}//如果不想以不同的方式处理这些派生类的异常,可以在同一个基类中捕获他们:try{ ...}catch(std::exception & e){ cout << e.what() << endl;} 当然你也可以分开捕获他们,去吧大师球 1.stdexcept异常类 头文件sedexcept定义了几个异常类.比如logic_error和runtime_error类,他们都是以公有集成的方式从exception类继承过来的:12345678910class logic_error: public exception{ public: except logic_error(const string & what_arg);}class runtime_error: public exception{ public: except runtime_error(const string & what_arg);}//注意 这些类的构造函数都接受一个string对象作为参数,他们提供了what()方法并且返回C-风格字符串 这两个新的类又被作为两个派生类系列的基类,其中logic_error类描述了典型的逻辑错误,这些逻辑错误是可以通过合理编程避免的,但还是可能发生.下面每个类的名称指出了他们用于报告的错误类型: logic_error类: domain_error: invalid_argument: length_error out_of_bounds: doormain_error: 数学函数值域(range)和定义域(domain),定义域由函数可能的参数组成,值域由函数可能的返回值组成,函数在输入参数或返回值不在制定范围的情况下将会引发domain_error异常; incalid_argument: 异常incalid_argument指出了给函数传递了一个意料之外的值.这个和定义域(domain)都有点不一样.例如如果希望输入的每个字符串要么是0要么是1,那么当输入的字符串中包含其他字符的时候,incalid_argument会被触发. length_error: 异常length_error指出了由于没有足够的空间类执行所需操作.比如string类的append()方法在合并得到的字符串长度超了的时候; out_of_bounds: 异常out_of_bounds通常用于指示索引错误,比如定义了个数组类,其operator()[]在使用的索引无效时引发out_of_bounds异常 runtime_error类这个类描述了可能在运行期间发生的难以预料的错误: range_error: overflow_error: underflow_error: 下溢(underflow) 存在浮点类型可以表示的最小非零值,当计算结果小于这个值的时候,将导致下溢错误.上溢(overflow) 存在计算结果超过了某种类型能够表示的最大数值时,将导致上溢.对于计算结果可能不在函数允许范围之内,但没有发生上下溢的时候可以用range_error异常; 继承关系可以使程序员一起处理他们(如果你愿意的话): 下面代码分别处理每种异常,先单独捕获out_of_bounds,然后统一不过其他logic_error系列异常,最后统一不过exception异常,runtime_error,以及其他从exception派生而来的异常: 1234567try{...}catch(out_of_bounds & oe){...}//捕获out_of_boundscatch(logic_error & oe){...}//捕获logic_error类catch(exception & oe){...}//捕获runtime_error,exception,和其他从exception派生而来的异常//↑↑如果你很不爽这么做的话可以给runtime_error,exception派生出来俩异常类,使异常类可以归入同一个继承层次中. bad_alloc异常和new 对于使用new导致的内存分配问题,C++比较新的处理方式是让new引发bad_alloc异常,头文件new包含bad_alloc的声明.他是从exception类公有派生而来,但在以前当无法分配请求的内存量时new返回一个空指针. 举个栗子:123456789101112131415161718192021222324252627282930313233343536#include <iostream>#include <new>#include <cstdlib>using namespace std;struct Big{ double stuff[20000];};int main(){ Big* pb; try{ cout << "试着申请一大块儿内存" << endl; pb = new Big[100000]; /*pb = new (std::nothrow) Big[10000]; //这样在内存请求失败的时候会返回空指针 if(){ cout << "请求失败" << endl; exit(EXIT_FAILURE); }*/ cout << "请求通过" << endl; } catch(bad_alloc & ba){ cout << "捕捉到异常" << endl; cout << ba.what() << endl; exit(EXIT_FAILURE); } cout << "成功分配内存" << endl; pb[0].stuff[0] = 4; cout << pb[0].stuff[0] << endl; delete [] pb; return 0;} 如果内存申请失败了则方法what()将会返回字符串std::bad_alloc.(在我的MinGW5.5下返回std::bad_array_new_length)如果你的程序没触发异常清加大请求分配内存量 另外还有一种是在new处理失败时返回空指针的:12int *p = new(std::nothrow) int;int *b = new (std::nowthrow) int[500];","tags":[{"name":"笔记","slug":"笔记","permalink":"http://yoursite.com/tags/笔记/"},{"name":"代码","slug":"代码","permalink":"http://yoursite.com/tags/代码/"},{"name":"CPP","slug":"CPP","permalink":"http://yoursite.com/tags/CPP/"}]},{"title":"C++笔记-异常","date":"2017-03-07T15:14:32.000Z","path":"2017/03/07/C++笔记-异常/","text":"图片来源: 不好意思本智障找不到了.请在p站搜雪染ちさ.. 在(keng)神(te)奇(duo)的C++中, 本智障经常写出一些正常的代码导致我的bug编译不过去. 举个书上的栗子,计算两个数的调和平均数的定义为: 两个数字倒数的平均数, 表达式: 2.0 * x * y / (x + y) 这样的话如果xy互为相反数的情况下岂不是很尴尬? abort() 对于这种问题,处理方式之一是如果检查到xy互为相反数则调用 abort() 函数(abort处于 cstdlib.h ).他会想标准错误流发送 abnormal program termination (程序异常终止),而且返回一个由时间决定的值,告诉操作系统处理失败..当然也可以用exit(),只不过不显示消息而已; 1234567891011121314151617181920212223242526 #include <iostream>#include <cstdlib>double hmean(double a,double b){ if(a == -b) { std::cout << "这俩数有毛病..." << std::endl; //在MinGW 5.3.0 32bit 下不使用abort的情况输入10和-10,调和平均数是 -inf std::abort(); //运行到这儿直接退出 所以不会显示下面bye的那句 } return 2.0 * a * b / (a+b);}int main(){ std::cout<< "输入俩数以计算调和平均数:"; double x, y, z; while(std::cin >>x >> y) { z = hmean(x,y); std::cout<< x <<" 和 "<< y << " 的调和平均数是 " << z << std::endl; std::cout << "输入任意按回车退出" << std::endl; } std::cout << "bye 再次按回车退出."; return 0;} 异常机制对于异常的处理有三个部分: 引发异常(throw) 使用处理程序捕获异常(catch) 使用 try 块(try) throw关键字表示引发异常,紧随其后的值(如字符串或对象)指出异常的特征;catch关键字表示使用异常处理程序(exception handler)捕获异常,括号内的表示异常要处理的类型,花括号内的表示遇到异常所采取的措施,虽然catch长的像个自带定义的函数,然而他并不是.try关键字表示其中的代码可能会出现异常,他后面一般跟着一个或多个catch. 如码所示:1234567891011121314151617181920212223242526#include <iostream>using namespace std;double hmean(double a, double b){ if(a == -b) throw "异常:这俩数有毛病"; //异常被触发 return 2.0 * a * b /(a + b);}int main(){ double x , y, z; cout << "请输入俩数: "; while(cin >> x >> y) { try { z = hmean(x,y); //try块里的表示需要注意这些代码可能触发异常 } catch (const char * s){ //捕捉到异常 程序跳到这儿 发现char类型与 throw后面的字符串匹配 cout << s << endl; //匹配之后执行代码块内的代码处理异常 cout << "请重试: "; continue; } cout << x << " 和 " << y << " 这俩数的调和平均数是: " << z << endl; cout << "输入任意字符串退出: "; } cout << "bye,再按回车退出" << endl;} 现在假设异常被触发,hmean()引发异常,被引发的异常是常量字符串:”异常:这俩数有毛病”,于是throw终止函数hmean()的执行,沿着函数调用序列往后查找,发现hmean()函数是从main()中的try块中调用的,于是throw把控制权返回给main函数,程序将在main里寻找与引发的异常类型所匹配的异常类型处理程序(说白了就是找参数类型跟throw后面的类型一样的catch块),程序找到唯一匹配的参数为char* 的catch块:类似下面的12345catch (const char * s){ //捕捉到异常 程序跳到这儿 发现char类型与 throw后面的字符串匹配 cout << s << endl; //匹配之后执行代码块内的代码处理异常 cout << "请重试: "; continue;} 于是,程序吧字符串:”异常:这俩数有毛病”赋值给s,然后执行catch(const char* s)内的代码.如果函数引发了异常而没有try块或没有匹配的catch时程序将调用abort()函数 将对象作用异常类型通常,引发异常的函数将传递一个对象,这就可以通过不同的异常类型来区分不同的函数在不同的情况下引发的异常,另外对象可以携带信息,同时catch块可以根据这些信息来决定采取什么样的措施.请查看具体代码举个栗子:12345678910111213void hmean(int a, int b) { if(...) throw _Error(a,b); //你就假装_Error是个构造函数并且异常被触发,此时调用构造函数初始化对象并存储参数;}try{ hmean(2,3); //这是个可能触发异常的函数}catch (_Error & e){ e.mesg(); //你就假装这里是调用了_Error类的消息输出方法并告诉你代码有毛病了;} 异常规范和C++11C++98新增了一种不受待见(最好别用这玩意)的异常规范(exception specification),他长这样:12double harm(double a) throw(bad_thing); //可能会抛出bad_thing异常double marm(double) throw(); //不会抛出异常 throw()部分就是异常规范,他可能出现在函数原型和函数定义中,他可以包含类型列表.这玩意的作用之一是告诉用户可能需要使用try块(然而直接写注释更方便),另一作用是让编译器添加执行运行阶段的代码,使劲检测是否违反了异常. C++11资呲一种特殊的异常规范,使用noexcept指出函数不会引发异常,不过对于这个还是存在争议的:1double marn() noexcept; //marn() 不会抛出异常 栈解退其实一张图就可以解释栈解退不过我还是要哔(chao)哔(xi)两句 假设try块没有直接调用引发异常的函数,而是调用了对引发异常函数进行调用的函数,则程序将从引发异常的函数直接跳到包含try块的函数. C++是如何处理函数的调用和返回的? 程序将调用函数的指令的地址(返回地址)放到栈中.当被调用的函数执行完毕之后程序将通过地址来确定从哪里开始继续执行.函数调用将函数参数也放到了栈中,他们被视为自由变量,如果被调用的函数又调用了另一个函数,那么后者的信息也会被添加到栈中,以此类推.当函数结束时,程序流程将跳到调用函数时储存的地址处(也就是返回到调用他的那个函数里),同时栈顶元素被释放,以此类推.并在结束时释放自由变量,如果自动变量是类对象,那么他的析构函数将被调用(如果有析构函数的话). 现在假设异常被触发(程序终止),则程序也将释放栈中的内存,但不会在释放栈的第一个返回地址后停止,而是继续释放栈,直到找到一个位于try块中的返回地址才停止.随后控制权将转到块尾的catch里,而不是调用函数后面的第一条语句,这个个过程被称为栈解退. 然而栈解退有个和函数返回一样的特征. 对于栈中的自动类型对象,类的析构函数将被调用.不同的是,函数返回仅仅处理放在栈中的对象,而throw则是处理try块和throw之间整个函数调用序列放在栈中的对象. 如果没有栈解退这种特性,则引发异常后,对于中间函数调用放在栈中的对象,他们的析构函数不会被调用. (现在上图: throw 与 return","tags":[{"name":"笔记","slug":"笔记","permalink":"http://yoursite.com/tags/笔记/"},{"name":"代码","slug":"代码","permalink":"http://yoursite.com/tags/代码/"},{"name":"CPP","slug":"CPP","permalink":"http://yoursite.com/tags/CPP/"}]},{"title":"C++笔记-友元类","date":"2017-03-05T15:07:46.000Z","path":"2017/03/05/C++笔记-友元类/","text":"类并非只能拥有友元函数还可以将类作为友元,友元被赋予从类外访问类的私有部分的权限,在这种情况下,友元类的所有方法都可以访问原始类的公有/私有/保护成员,也能指定特定的成员函数为一个类的友元. 什么时候希望一个类成为另一个类的友元? 书上举得一个栗子:完整代码 假设编写一个模拟遥控器和电视机的程序,决定有TV和Remote类表示电视机和遥控器,显然这两个类存在着某种关系(净说废话).但电视机不是遥控器,反之亦然,所以正常的公有继承is-a关系并不适用.遥控器并非电视机的一部分.所以,包含私有和保护集成的has-a关系也不适用.但众所周知遥控器可以改变电视机的状态.这说明遥控器应该是电视机类的一个友元. 友元的声明:友元声明可以位于公有,私有或者保护部分,所在其位置无关紧要.但由于Remote类提到了TV类所以必须先定义Tv类,或者使用前向声明.这样来声明一个友元类:1friend class Remote; 1234567891011121314151617181920212223242526272829303132333435363738394041class Tv{public: friend class Remote; enum{off,on}; enum{MinVal,MaxVal = 20}; enum{Antenna,cable}; enum{tv,dvd}; Tv(int s = off,int mc = 125):state(s),volume(5), maxchannel(mc),channel(2),mode(cable),input(tv){} void onoff(){state = (state == tv) ? off: on;} bool ison() const {return state == on;} bool volup(); bool voldown(); void chanup(); void chandown(); void set_mode(){mode = (mode = Antenna) ? cable: Antenna;} void set_input(){input = (input = tv) ? dvd : tv;} void settings() const;private: int state; int volume; int maxchannel; int channel; int mode; int input;};class Remote{private: int mode;public: Remote(int m = Tv::tv):mode(m){} bool volup(Tv & t){return t.volup();} bool voldown(Tv &t){return t.voldown();} void onoff(Tv & t){t.onoff();} void chanup(Tv &t){return t.chanup();} void chandown(Tv &t){return t.chandown();} void set_mode(Tv &t){return t.set_mode();} void set_input(Tv &t){return t.set_input();} void set_chan(Tv &t, int c){t.channel = c;}}; 从上面的从书中摘抄的毫无诚意的代码中可以看出,所有的Remote方法都是Tv类的友元,似乎Remote类除了构造函数都使用了Tv类的公有接口,但是事上唯一直接访问了Tv类成员的Remote类方法是Remote::set_chan(),那么就可以选择仅特定的类成员成为另一个类的友元.但这样就要小心的排列声明和各种定义;让Remote::set_chan()成为Tv类的友元的方法是,在Tv类声明中将其定义为友元: 123class Tv{ friend void Remote::set_chan();} 就酱,但是吧……编译器要处理这句话首先得知道Remote的定义,不然编译器不知道Remote是个类,所以这就要把Remote类的声明挪到Tv类声明前面,但Remote类用了Tv对象..这就又得把Tv类定义挪到Remote类定义前面去.咦等等.那Remote咋办他需要在Tv类的前面啊.这..这就很尴尬了,呐,避免这种死循环的方法是使用 前向声明(forward declaration):123class Tv ; //前向声明class Remote{...}class Tv{...}; 那能不能这样?:123class Remote;class Tv{...};class Remote{...}; 这是不行的,在编译器在Tv类中的声明中看到Remote类的一个方法称为Tv类的友元之前,该先让编译器看到Remote类的声明和Remote::set_chan()函数的声明. 好了,但在Remote类中可以看到,有些方法包含了内联代码,例如: void onoff(Tv & t){t.onoff();}由于它使用了一个Tv的方法,所以在此之前编译器必须看到Tv类声明,但是Tv类在Remote类后面声明..解决方法就是把函数定义放在Tv类之后就成.. 吼,现在只有一个Remote方法是Tv类的友元了; 编译器一开始通过前向类型得知了Tv是个类,在读取声明并编译了这些方法之后,使用lnline关键字仍然可以使Remote类未定义的函数称为内联方法.完整代码 其他友元关系遥控器能影响电视.现在你想通过电视对遥控器产生某种影响,这可以让类彼此成为对方的友元来实现; 需要记住的是对于使用Remote类对象的Tv方法,其 函数原型 可以在Remote类声明之前声明,但必须在Remote类之后定义,这样编译器才有足够的信息来编译该方法.12345678910111213class TV{friend class Remote;public: void buzz(Remote & r); ...};class Remote{ friend class Tv; public: void bool volup(Tv $ r){t.volup();}};inline void Tv::buzz(Remote & r){...} 由于Remote声明在Tv后面,所以可以在类声明中定义volup();buzz()可以再Tv中声明,但必须在Remote后面定义; 共同友元函数需要访问两个类的私有数据,函数可以是一个类的成员,另一个类的友元; 也可以是两个类的友元:12345678910111213class A; //前向声明class B { friend void fun(A & a,const B & b); //编译器发现前向类型A 得知A是一个类型 friend void fun(B & b,const A & a);}class A{ friend void fun(A & a,const B & b); friend void fun(B & b,const A & a);}//定义友元函数inline void fun(A & a,const B & b){...}inline void fun(B & b,const A & a){...}","tags":[{"name":"笔记","slug":"笔记","permalink":"http://yoursite.com/tags/笔记/"},{"name":"代码","slug":"代码","permalink":"http://yoursite.com/tags/代码/"},{"name":"CPP","slug":"CPP","permalink":"http://yoursite.com/tags/CPP/"}]},{"title":"C++笔记-嵌套类","date":"2017-03-04T15:04:38.000Z","path":"2017/03/04/C++笔记-嵌套类/","text":"封面来源 在(quan)神(shi)奇(keng)的C++中,放在另一个类中声明的类被称为嵌套类(nested class),它通过提供新的类型类作用域来避免名称混乱.包含类的成员和函数可以创建和使用被包含类的对象; 仅当嵌套类声明位于包含类的公有部分时,才可以在包含类外通过作用域解析运算符使用嵌套类; 不不不,这个和包含不一样,包含是将一个类的类对象作为另一个类的类成员; 而嵌套类则是定义了一种类型且仅在包含嵌套类声明的类中有效 12345678910//在包含类的私有部分声明了个嵌套类class Queue{private: class Node { public: Node(); }} 使用两次作用域解析运算符就可以定义Node()辣:1Queue::Node::Node(){...} 嵌套类和访问权限如果嵌套类是在另一个类的私有部分声明的,则只有包含他的类知道他的存在,且对于从包含类派生出来的类来讲,因为派生类不能直接访问基类私有部分,所以嵌套类也是不可见的. 如果嵌套类是在另一个类的保护部分声明的,则对于包含他的类来说是可见的,而对于外部来讲嵌套类是不可见的,但后者的派生类可以直接创建这种类对象. 如果嵌套类是在另一个类的公有部分声明的,因为他是公有的,则对于包含他的类,对与包含他的类的派生类以及外部世界都可以使用它. 访问控制在Queue类中声明Node类并没有赋予Queue类任何访问权限,Node也没有赋予Queue任何访问权限,所以Queue只能显示的访问Node成员,所有我将Noede类所有成员声明为公有,不过没关系,虽然Queue的方法可以直接访问Node类,但被Queue声明为私有的Node对于外部来讲是不可见的. 模板类中的嵌套(并不会发生什么奇怪的问题; 完整代码","tags":[{"name":"笔记","slug":"笔记","permalink":"http://yoursite.com/tags/笔记/"},{"name":"代码","slug":"代码","permalink":"http://yoursite.com/tags/代码/"},{"name":"CPP","slug":"CPP","permalink":"http://yoursite.com/tags/CPP/"}]},{"title":"C++笔记-成员模板&将模板用作于参数","date":"2017-01-08T15:43:01.000Z","path":"2017/01/08/C++笔记-成员模板&将模板用作参数/","text":"模板成员:模板类将另一个模板类和模板函数作为其成员1234567891011121314151617181920212223template <typename T>class beta{private: template<typename V> class miao { private: V val; public: miao(V v = 0) : val(v){} void show() const {cout << val << endl;} V value() const {return val;} }; miao<T> q; miao<int> n;public: beta(T t, int i) : q(t),n(i) {} template <typename U> U blab(U u,T t) {return ((n.value() + q.value()) * u/t);} void bshow() const {q.show(); n.show();}}; 12hold<T> q;hold<int> n; n 是基于int 类型的hold 对象,q 的基于T 类型的hold 对象,下述声明使得T表示的是double,因此q 的类型是 hold< double>:1beat<double> guy(10,2.5); blab() 方法的U 类型由该方法被调用时的参数决定,T 类型由对象的实例化决定,下述例子中,guy 的声明将T 类型设置为double,U 的类型则为int.1cout<<guy.blab(10,2.5); 虽然混合类型所引起的自动类型转换导致blab() 函数中的计算以double 类型进行,但返回值的类型为U (即int),因此上述输被截断为28. 如果使用guy.blab()时,使用10.0代替10,那么U 的类型将会设置为double ,使得返回值也为double,因此输出为28.2608. 将模板类用作参数模板类可以包含类型参数(如 typename T )和非类型参数(如 int ),还可以包含本身就是模板的参数. 举个书上的栗子:12345678910111213141516171819202122232425262728293031template<template <typename T>class Thing>class Crab{private: Thing<int> s1; Thing<double> s2;public: Crab(){} bool push(int a,double x){ return s1.push(a) && s2.push(x);} bool pop(int & a,double & x){return s1.pop(a) && s2.pop(x);}};int main(){ Crab<Stack> cs; int ni; double nd; cout << "Enter int double pairs,such as 4 3.5(0 0 to end):" << endl; while (cin >> ni >>nd && ni > 0 && nd > 0) { if(!cs.push(ni,nd)) break; } while(cs.pop(ni,nd)) cout << ni << " , " << nd << endl; cout << "DONE" <<endl; return 0;} 12template <template <typename T>class Thing>class Crab{} 如上, 上述模板参数类型为template < typename T> class Thing,其中Thing 为参数. 这意味着为了使 Crab< King> legs 被接受,模板类参数King 必须是个模板类:12template <typename K> class King{...} legs 声明将用King< int> 替换 Thing< int> ,用 King< double> 替换 Thing< double>,但是在下面的代码中就有所不同. 在Crab 类中,有两行代码声明了两个类对象:12Thing<int> s1;Thing<double> s2; 而mian 函数中包含以下声明,因此,Thing< int> 将被实例化为 Stack< int> ,而Thing< double>将被实例化为Stack< double>.总之,模板参数Thing 将被替换为声明Crab 对象时被用作模板参数的模板类型.","tags":[{"name":"笔记","slug":"笔记","permalink":"http://yoursite.com/tags/笔记/"},{"name":"代码","slug":"代码","permalink":"http://yoursite.com/tags/代码/"},{"name":"CPP","slug":"CPP","permalink":"http://yoursite.com/tags/CPP/"}]},{"title":"C++笔记-模板的具体化","date":"2017-01-06T11:32:50.000Z","path":"2017/01/06/C++笔记-模板的具体化/","text":"隐式实例化:编译器在需要对象之前,不会生成类的隐式实例化12ArrayTp<int,100> * pt; //不生成实例pt = new ArrayTp<int,100>;//现在生成实例 显式实例化:使用关键字template并指出所需类型来声明类的时候,编译器将生成类声明的显式实例化:1template class ArrayTp<string,100>; 在这种情况下,虽然没有创建类对象,编译器也将生成类,包括方法定义. 显式具体化:(显式具体化==特型)在需要特殊要求的时候对模板进行修改,使其行为不同,这时可以创建显式具体化. 书上举的栗子:假设现在定义一个表示排序后数组的类:1template <typename T> class SortedArray{...} 假设模板使用>运算符来对值进行比较,对于数字来说没毛病,对于类型T,只要定义了T::operator>(),也没毛病,但是T如果是个const char **的字符串就不行了,因为这需要使用strcmp(),而不是>*.这种情况下可以提供一个现实模板具体化.格式如下:1template <> class Classname <specialized-type-name>{...} 当具体化模板和通用模板都与实例化请求匹配时,编译器将使用具体化模板. 所以说要使用const char 类型的SortedArray*模板可以这么写:1template <> class SortedArray<const char *> {...} 这样在使用const char **类型的SortedArray*模板时将使用上述专用定义而不是用通用模板定义. 部分具体化:部分具体化可以给类型参数制定具体的类型:12template <class T1, class T2> class Pair{...};template <class T1> class Pari<class T1,int>{...}; template后面的是没有被具体化的类型参数.上述T2被具体化为int,但T1不变. 部分具体化特型使得能够设置各种限制,例如:12345678//一般模板template<typename T1,typename T2,typename T3>class Trio{...}//对T3具体化的模板template<typename T1,T2> class Trio<T1,T2,T3>{...}//对T2,T3具体化的模板template<typename T1> class Trio<T1,T2*,T3*>{...} template 后面的是没有被具体化的类型参数 给出上述定义编译器将作出如下选择:123Trio<int,double,string*> T1; //使用一般模板Trio<int,short> T2; //使用对T3具体化的模板Trio<string,string*,char*>;//使用对T2,T3具体化的模板","tags":[{"name":"笔记","slug":"笔记","permalink":"http://yoursite.com/tags/笔记/"},{"name":"代码","slug":"代码","permalink":"http://yoursite.com/tags/代码/"},{"name":"CPP","slug":"CPP","permalink":"http://yoursite.com/tags/CPP/"}]},{"title":"C++笔记-多重继承","date":"2016-12-30T11:31:05.000Z","path":"2016/12/30/C++笔记-多重继承/","text":"必须使用关键字来限定每一个基类,不然编译器会默认成私有派生:1class mylove : public string, valarray<double> //valarray为私有继承 其实你不用看这仨奇葩类的123456789101112131415161718192021222324252627282930313233343536373839404142class Worker{public: Worker() : name("NULL"),id(0L){} Worker(const string & s,long n) : name(s),id(n){} virtual ~Worker() = 0; virtual void Set(); virtual void Show() const;private: string name; long id;};class Waiter : public Worker{public: Waiter() : Worker(),panache(0){} Waiter(const string & s, long n, int p = 0) : Worker(s,n),panache(p){} Waiter(const Worker & w, int p) : Worker(w),panache(p){} void Set(); void Show() const;private: int panache;};class Singer : public Worker{public: Singer() : Worker(),voice(other){} Singer(const string & s, long n, int v = other) : Worker(s,n),voice(v){} Singer(const Worker & w, int v =other) : Worker(w),voice(v){} void Set(); void Show() const;protected: enum{other,alto,contralto,soprano,bass,baritone,tenor}; enum{Vtypes = 7};private: static char* pv[Vtypes]; int voice;}; Worker?从Singer和Waiter共有派生出SingingWaiter:1class SingingWaiter : public Singing, public Waiter{...} 但这将出现二义性,因为Singing和Waiter都继承了一个Worker:12SingingWaiter sw;Worker* pw = sw; //二义性,鬼知道这时候用哪个worker so..应该使用类型转换来指定对象:123Worker* pw = (Singing*) &sw;Worker* pw2 = (Waiter*) &sw;//不过下面还有更简(ma)单(fan)的虚基类可以解决该问题 虚基类虚基类使得从多个类(他们基类相同)派生出的对象只继承一个基类对象.例如:在类声明中使用关键字virtual,可以使Worker被作用Singer和Waiter的虚基类:12class Singer : virtual public Worker{...}class Waiter : public virtual Worker{...} //这么写也行 然后 SingingWaiter可以定义为:1class SingingWaiter : public Singer,public Waiter{...} 现在SingingWaiter类只有一个Worker对象副本了,Singer和worker共享一个Worker对象,所以现在可以使用多态了. 新的构造函数规则使用虚基类时,构造函数需要使用一种新的方法,这是因为C++在基类是虚的时,禁止信息通过中间自动传递给基类,编译器在这时会使用基类的默认构造函数. 12345678910111213//通过中间类自动传递:class A{ int a; A(int n = 0) : a(n);}class B :public A{ int b; B(int a = 0, int bm = 0) : A(a),b(bm);}class C : public B{ int c; C(int a = 0, int b = 0, int cm = 0) : B(a,b),c(cm);} 使用虚基类时我们必须显示调用构造函数:1234567SingingWaiter(const Worker & wk,int p = 0,int v = Singer::other) :Worker(wk),Waiter(wk,p),Singer(wk,v){} //显示使用worker SingingWaiter(const Worker & wk, int p = 0, int v = Singer::other) :Waiter(wk,p),Singer(wk,v){} //错误的示范,会调用Worker的默认构造函数 对于非虚基类,显示调用Worker(const Worker&)是非法的 哪个方法?那么问题来了,我们打算在SingingWaiter中重定义Show方法,并用SingingWaiter对象调用继承的Show方法:12345//所以我傻不愣登的写下了如下代码:SingingWaiter aha("喵喵",2017,1,soprano);aha.Show(); //二义性,Worker和Singer都有Show() 鬼知道这个是哪个?aha.Singer::Show(); //然而可以用作用域解析运算符来确定 最好是使用模块化:12345678910111213141516171819Woeker::Data() const { cout << "Name: " << name; cout << "ID: " << id;}Waiter::Data() const{ cout << "panache: " << panache << endl;}Singer::Data() const{ cout << "rating: " << pv[voice] << endl;}SingingWaiter::Data() const{ Worker::Data(); Singer::Data();}SingingWaiter::Show() const{ Worker::Data(); Data();}//就是.........有点麻烦...","tags":[{"name":"笔记","slug":"笔记","permalink":"http://yoursite.com/tags/笔记/"},{"name":"代码","slug":"代码","permalink":"http://yoursite.com/tags/代码/"},{"name":"CPP","slug":"CPP","permalink":"http://yoursite.com/tags/CPP/"}]},{"title":"Hello World!!","date":"2016-12-27T13:49:34.066Z","path":"2016/12/27/hello-world/","text":"喵喵喵 本人的渣渣博客 以后就在这里撒欢辣 写的比较傻逼~ 大神轻喷呐 如有错误还劳驾指出哦~ Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub. Quick StartCreate a new post1$ hexo new \"My New Post\" More info: Writing Run server1$ hexo server More info: Server Generate static files1$ hexo generate More info: Generating Deploy to remote sites1$ hexo deploy More info: Deployment","tags":[]},{"title":"C++笔记-私有继承","date":"2016-12-23T16:17:20.000Z","path":"2016/12/24/C++笔记-私有继承/","text":"使用私有继承,基类的共有成员以及保护成员都将成为派生类的私有成员派生类不继承基类的接口,但能在派生类的成员函数中使用它们,私有继承特征与包含相同:不继承接口,继承实现.所以它可以用来实现has-a关系. 示例:123456class player : private string,private valarray<double>//使用多个基类:多重继承{ public: ...} 在这里新版本的构造函数将使用初始化成员列表,使用类名而不是使用成员名来标识构造函数: 12player::player(const char* str,const double* p,int i) : string(str),valarray<double>(p,i){ } 保护继承1class player : protected string,protected valarray<double>{...} 保护继承时,基类的公有成员和保护成员都将成为派生类的保护成员.使用私有继承时,第三代类将不能使用基类的接口,这是因为基类的公有方法在派生类中将变成私有方法.使用保护继承时,基类的公有方法将在二代类中变成受保护的,所以第三代类可以使用它们. 使用using重定义访问权限使用保护派生或者私有派生时,基类的公有继承将成为保护或者私有成员,如果想让基类方法在类外使用可以定义一个使用该基类方法的派生类方法.1234//派生类player希望使用基类valarray类的sum方法double player::sum() const { return valarry<double>::sum();} 或者可以使用using声明:123456class player : private valarray<double>{ public: using valarray<double>::min; using valarray<double>::max;} 他们就像player类的共有方法一样:1cout << "high score:" << a[i].max << endl; 注意 using声明只使用成员名,没有特征标,圆括号,返回类型,例如要在player]类中使用valarray类的operator[]方法,只需包含:1using::valarray::operator[]; 通常使用包含来建立has-a关系;如果新类需要访问原有类的保护成员,或需要重新定义虚函数应使用私有继承","tags":[{"name":"笔记","slug":"笔记","permalink":"http://yoursite.com/tags/笔记/"},{"name":"代码","slug":"代码","permalink":"http://yoursite.com/tags/代码/"},{"name":"CPP","slug":"CPP","permalink":"http://yoursite.com/tags/CPP/"}]},{"title":"C++笔记-包含类对象的成员","date":"2016-12-23T14:19:08.000Z","path":"2016/12/23/C++笔记-包含类对象的成员/","text":"接口和实现 使用公有继承时,类可以继承接口,可能还有实现(基类的纯虚函数提供接口,但不提供实现),获得接口是is-a的关系组成部分.而是用组合,类可以获得实现不继承接口是has-a的关系组成部分. 错误的示范:1234567891011121314class player{private: string name; valarray<double> source; ostream & arr_out(ostream & os) const;public: player():name("NULL"),source(){} explicit player(const string & s) :name(s),source(){} explicit player(int n) :name("NULL"),source(n){} player(const string& na, int n) :name(na),source(n){}}; 12player mylove("YSY",10);mylove = 5; //喵喵喵??重置ArrayD为五个空值的元素? mylove = 5;这里应该是:mylove[5] = 5才对.如果没有写explicit,编译器将调用转换构造函数player(5),name 的默认值将是NULL,并且编译器将会生成一个临时对象,并用临时对象替换mylove原有的值,这并不是我们想要的.如果加了explicit,编译器会报错这对我们debug很有利.毕竟在编译期出错优于在运行期出错.","tags":[{"name":"笔记","slug":"笔记","permalink":"http://yoursite.com/tags/笔记/"},{"name":"代码","slug":"代码","permalink":"http://yoursite.com/tags/代码/"},{"name":"CPP","slug":"CPP","permalink":"http://yoursite.com/tags/CPP/"}]},{"title":"C++笔记-类设计总结","date":"2016-12-21T14:57:57.000Z","path":"2016/12/21/C++笔记-类设计总结/","text":"默认构造函数:默认构造函数要么没有参数,要么所有参数都有默认值.在派生类构造函数初始化列表中记得显示调用基类构造函数,否则编译器将会使用基类的默认构造函数,这可能会出现错误. 复制构造函数:1classname(const classname &) 在按值传递,安置返回对象,编译器生产临时对象,将新对象初始化为一个同类对象的时候,将调用复制构造函数. 赋值运算符: 1classname & operator=(const classname &) 用于处理同类对象间的赋值,如果希望处理string类与classname类的赋值可以写成:1classname & operator=(const string &) 析构函数:当对象过期时,析构函数负责清理工作(如释放内存),对于基类应该提供一个虚析构函数,即使他不需要. 转换构造函数:1classname(const schar *) 使用一个参数的构造函数他定义了从参数类型到类类型的转换(话说这个中文名跟转换函数就差了两个字,但不一样容易弄混). 123Star(const char*);Star a = Star("233");a = "233"; 第二/三句话将会调用Star::operator(const Star &)并使用Star(const char)生成一个对象,该对象将作用于赋值运算符函数的参数. 使用转换构造函数时候时建议使用explicit禁止隐式转换* 按值传递对象与传递引用通常在编写以对象作为参数的函数时,应该按引用,不应该使用按值传递参数,一是为了效率,二是因为在继承使用虚函数时,基类使用引用参数的函数可以接受派生类. 返回对象与返回引用如果函数返回的是通过引用或指针传递给他的对象,则应该按引用返回对象.返回引用可以节约内存和时间,与按引用传递相似,调用与被调用函数使用同一个对象进行操作.但不总是可以返回引用,比如函数不能返回一个在函数中创建的临时变量的引用,因为当函数结束时临时变量将会消失,这时候应该返回对象. const123456789classname::classname(const char * s) //确保方法不修改参数classname::show() const; //确保方法不修改调用他的对象,这里的const表示const classname * this,this指向调用的对象const classname& classname::fun(const classname & cn) const{ if(s.total > total) return s; else return *this;};//该方法返回cn/this的引用,但因为cn/this是const,所以方法不能对cn/this进行修改,这意味着返回类型也必须为const 共有继承要考虑的因素:is-a关系is-a为”是一个”的意思,如果派生类不是某种特殊的基类则不要使用,比如从大脑类派生出程序员类.is-a关系的标志之一是:无需进行显示转换即可将积累指针或引用指向或引用派生类对象(向上强制类型转换).反之是可能出现错误的. 赋值运算符将派生类赋值给基类对象:123456class ZheXue {...}//基类-哲♂学class GaoBiLi : public ZheXue{...}//派生类-搞♂比♂利ZheXue bili;GaoBiLi fuc;bili = fuc; 1bili = fuc; 这将转化为: bili.operator=(fuc)他将调用 ZheXue::operator=(const ZheXue &); 那如果将基类赋值给派生类对象呢:1fuc = bili; 这将转化为fuc.operator=(bili) ; 他将调用 GaoBiLi::operator=(const GaoBiLi &);然而,派生类引用不能自动引用基类对象.除非我们定义转换构造函数:GaoBiLi(const ZheXue &) ;转换构造函数可以有一个类型为基类的参数和其他参数,但其他参数必须有默认值: 1GaoBiLi(const ZheXue & zx,string & na = "比♂利♂王",string & ty = "森之♂妖精"); 这样转换构造函数会根据bili来创建一个临时对象,然后把它作为赋值运算符的参数.然而还可以直接写个参数为ZheXue的赋值运算符函数….: 1GaoBiLi::operator=(const ZheXue &){......}; 私有成员与保护成员对于外界来说,只能用共有成员来访问二者,对于派生类来说,可以直接访问基类的保护成员,而私有成员仍要通过基类的成员函数来访问. 虚方法如果要在派生类中重定义基类的方法则应该使用virtual. 析构函数基类的析构函数应当是虚的.这样在使用指针或者引用删除派生对象时,程序会先调用派生类的析构函数然后调用基类的,而不会只调用基类的析构函数 友元函数友元函数并非类成员因此不能继承,如果希望派生类函数能使用基类的友元函数,可以使派生类指针或引用强制转换为基类的指针或引用,然后使用转换后的指针或引用来调用友元函数. 123456ostream & GaoBiLi::operator<<(ostream & os, GaoBiLi & gbl){ os << (const ZheXue &)gbl; os << "name:" << gbl.name << endl; return os;}","tags":[{"name":"笔记","slug":"笔记","permalink":"http://yoursite.com/tags/笔记/"},{"name":"代码","slug":"代码","permalink":"http://yoursite.com/tags/代码/"},{"name":"CPP","slug":"CPP","permalink":"http://yoursite.com/tags/CPP/"}]},{"title":"C++笔记-继承和动态内存分配","date":"2016-12-15T14:01:19.000Z","path":"2016/12/15/C++笔记-继承和动态内存分配/","text":"如果基类使用动态内存分配,派生类不使用1234567891011121314151617class player{ privatec: string * name; bool sex; public: player(const string & na = "NULL",bool a = 0); player(const player & p); virtual ~player(); player & operator=(const player & p);}class ship : public player{ private: int age; public: ...} 那么是否要为ship类定义显示析构函数,复制构造函数,重载赋值运算符呢? 不需要. 析构函数: 对于ship类,我们没有对他进行任何特殊的操作,所以默认的析构函数是合适的 复制构造函数:首先我们知道的是,默认复制构造函数是执行成员复制,因为player使用了动态内存分配,所以,默认复制构造函数不适用于player类,但对于ship类是适合的.当复制继承的组件获或者成员时,则使用他的复制构造函数.所以当ship的默认复制构造函数会使用player的默认复制构造函数来复制ship里的player对象.所以默认复制构造函数对于他们来说是合适的. 赋值运算符: ship默认的赋值运算符也会使用player的赋值运算符来对player成员进行赋值,所以是合适的. 如果基类和派生类都使用动态内存分配123456class ship : public player{ private: string * type; public: ...} 在这种情况下必须显示定义派生类的显示析构函数,复制构造函数,重载赋值运算符. 析构函数: 派生类的析构函数先释放type所管理的内存,然后基类析构函数释放name所管理的内存. 复制构造函数:派生类的复制构造函数只能访问派生类的数据,所以他必须调用父类的复制构造函数.ship::ship(const ship & p) : player(p) , 因为player类因为复制构造函数有一个player&参数,而基类可以指向派生类型,因此player的复制构造函数将使用ship参数的player部分来构造新对象的player部分. 赋值运算符:因为派生类采用动态内存分配,所以他需要一个显示赋值运算符.ship的赋值运算符只能直接访问ship类的数据,但他却要负责所继承的基类对象的赋值,这个时候可以显示调用基类的赋值运算符方法.然后在处理派生类的赋值.1234567ship & ship::operator=(const player & p){ if(this == p) return *this; player::operator=(p);//显示调用基类赋值运算符 delete type; type = new string; type = p.type;}","tags":[{"name":"笔记","slug":"笔记","permalink":"http://yoursite.com/tags/笔记/"},{"name":"代码","slug":"代码","permalink":"http://yoursite.com/tags/代码/"},{"name":"CPP","slug":"CPP","permalink":"http://yoursite.com/tags/CPP/"}]},{"title":"C++笔记-抽象基类(Abstract-Base-Class,ABC)","date":"2016-12-14T15:46:31.000Z","path":"2016/12/14/C++笔记-抽象基类(Abstract-Base-Class,ABC)/","text":"啥时候使用抽象基类?在下理解的是:你有一个基类和一个该基类的派生类,但是基类里有一些你派生类根本用不上的方法,使用了派生类就会导致一些信息冗余.然而不继承基类单独写个类你发现效率也不高,而且你发现你的基类和你的派生类之前还是有共同点的..这个时候就要上抽象基类了….把其共同点放到抽象基类里,然后分别从抽象基类派生刚才的”基类”与”派生类”. 啥是抽象基类 就是类里定义了纯虚函数的类………然而定义了纯虚函数就只能作为基类了.23333 纯虚函数纯虚函数:virtual 返回类型 函数名(形参) =0;在虚函数声明后面加个 =0 就是纯虚函数了,当类声明中包含纯虚函数的时候,则不能创建该类对象.所以包含纯虚函数的类只能作为基类,在原型中使用 =0 指出类是一个抽象基类,在类中可以不定义该函数.12345678class player{ private: int age; string name; pbulic: ... virtual void show() = 0;} 总之: ABC描述的是至少使用一个纯虚函数的接口,从ABC派生出的类将根据派生类的具体特征使用常规虚函数来实现这种接口.","tags":[{"name":"笔记","slug":"笔记","permalink":"http://yoursite.com/tags/笔记/"},{"name":"代码","slug":"代码","permalink":"http://yoursite.com/tags/代码/"},{"name":"CPP","slug":"CPP","permalink":"http://yoursite.com/tags/CPP/"}]},{"title":"C++笔记-访问控制protected","date":"2016-12-13T15:22:36.000Z","path":"2016/12/13/C++笔记-访问控制protected/","text":"protected关键字protected的意思是保护,和private有点相似,在类外只能通过共有类成员来访问protected中的成员.但他与private的区别体现在:在派生类中,派生类的成员可以直接访问基类的protected成员,但不能直接访问基类的private成员.举个栗子:12345678910111213141516class player{ ... public: void R18(int a){ if(a<18)cout << "禁止入内"; else {cout << "欢♂迎"; } protected: int age;}class ship : public player{ ... public: void showshipage(int m){ age = m; cout << age << "欢♂迎";}//通过派生类公有成员直接访问基类protected成员 ...} 但是这样做是有点小问题的age成员被设置为只能通过player::R18()来访问,但是有了ship::showshipage()将会忽略player::R18()的禁止入内措施,这使得age变成了一个公有变量…….然而对于成员函数来,保护控制很有用,他可以让派生类访问一些公众不能访问的内部函数.so..你问我滋补滋磁,我是滋次的.","tags":[{"name":"笔记","slug":"笔记","permalink":"http://yoursite.com/tags/笔记/"},{"name":"代码","slug":"代码","permalink":"http://yoursite.com/tags/代码/"},{"name":"CPP","slug":"CPP","permalink":"http://yoursite.com/tags/CPP/"}]},{"title":"C++笔记-静态联编和动态联编","date":"2016-12-11T15:38:53.000Z","path":"2016/12/11/C++笔记-静态联编和动态联编/","text":"一些理解性概念 啥是联编将源代码中的程序调用解释为执行特定的函数代码块被称为函数名联编在编译过程中的联编被称为静态联编在程序运行时的联编被称为静态连编 ##指针和引用类型的兼容性将派生类指针或引用转为基类指针或者引用被称为向上强制转换,如果是共有继承则不需要进行显式类型转换,任何对基类对象做的操作都适合派生类对象.相反,将基类指针或者引用转换为派生类指针或者引用被称为向下强制转换,需要显示类型转换,但是派生类可以新增成员函数,由于基类没有这些函数,这使得使用新增成员函数的类成员函数不能作用于基类.123456789101112131415161718192021222324class player{ pubilc: player(string & na){name = na;} void showname()const {cout << name;} private: string name;}class ship : pubilc player{ public: ... void shouage(int age); private: int age;}player lex("lexington"); ship * t =(ship*)&lex; //将基类指针转化为派生类指针,必须显示类型转换,向下强制转换ship sar("saratoga");player* v = &sar; //将派生类指针转化为基类指针,向上强制转换t->showage(20); //不安全的操作 showage不是player的成员v->showname(); //安全的 现在我们有个虚方法1234567891011121314``` void fr(player & r) //r.sizhai(); void fp(player * p) //p->sizhai(); void fv(player v) //v.sizhai(); fun(){ player p("LEX"); ship s("sar"); fr(p); // player::sizhai(); fr(s); // ship::sizhai(); fp(p); // player::sizhai(); fp(s); // ship::sizhai(); fv(p); // player::sizhai(); fv(s); // player::sizhai(); } 由于按值传递ship对象的player部分被传递给函数fv().但是引用和指针发生的向上强制转换分别为player对象和ship对象使用了不同的函数(virtual).隐式向上强制类型转换使得基类对象可以指向基类对象或派生类对象,因此需要动态联编. 虚函数和动态联编概念理解:虚函数工作原理通常,编译器处理虚函数的原理是:给每个对象添加一个隐藏成员,这个隐藏成员保存了一个指向函数地址数组的指针.它被称为虚函数表,虚函数表保存了类对象的虚函数地址.列如,基类对象包含一个指针,它指向基类中所有虚函数的地址表.派生类对象将指向一个独立地址表的指针,如果派生类里提供了虚函数的定义,那么这个独立的地址表将会保存新函数的地址,如果没提供,该表将使用原始版本的地址.如果派生类定义了新的虚函数,那么该函数的地址也会被添加到表中. 注意:无论类中有多少个虚函数,都只在对象中添加一个地址成员,只是大小有所差别 虚函数总结: 构造函数不能是虚函数; 析构函数应该是虚函数(除非不作为基类); 友元不能是虚函数,因为友元函数不是类成员; 如果基类声明被重载,则应在派生类中重定义所有的基类重载版本; 重定义不是重载,如果重定义继承的方法,应该确保与原型完全一致;注意:如果原返回类型是指向基类的指针或者引用,可以改成指向派生类的指针和引用,这被成为返回类型协变","tags":[{"name":"笔记","slug":"笔记","permalink":"http://yoursite.com/tags/笔记/"},{"name":"代码","slug":"代码","permalink":"http://yoursite.com/tags/代码/"},{"name":"CPP","slug":"CPP","permalink":"http://yoursite.com/tags/CPP/"}]},{"title":"C++笔记-一个简单的基类","date":"2016-12-08T13:58:54.000Z","path":"2016/12/08/C++笔记-一个简单的基类/","text":"从一个类派生出另一个类,原始类被称为基类,继承类被称为派生类例:1234567891011121314151617181920class player{ public: player(const string & na,bool se = 1); void Name() const{cout << name << endl;} bool sex {return sex;} privat: string name; bool sex;} class Ship : public player //继承了player类 公有派生{ public://派生类需要自己的构造函数并且必须使用基类的构造函数 Ship(bool mw = 0,int ag, const string & na, bool se = 1); Ship(bool mw = 0,int ag,player & p); int ShowAge() {cout << age; return age;} private: bool myWife; int age;} 冒号说明了Ship的基类是player,public表示了这个基类是公有基类,这被成为公有派生.使用公有基类,基类的公有成员将成为派生类的公有成员,基类的私有成员也将成为派生类的一部分但是不能直接访问,需要通过继承的基类的公有方法来间接访问.创建派生类对象的时候,首先创造基类对象,C艹使用成员列表初始化完成该工作:12345678910//在此声明成员列表初始化只能用于构造函数 Ship::Ship(bool mw, int ag,const string & na,bool se) : player(na,se){ mw = 1; ag = 18; } Ship::Ship(bool mw,int ag,player & p) : player(p){ mw = 0; ag = 14; } 在第二个构造函数中 由于基类类型为 player & ,因此将会调用基类的复制构造函数,由于基类没有该函数,则编译器将会自动生成一个~基类对象首先被创建.派生类构造函数应通过成员初始化列表将基类信息传递给基类构造函数 派生类和基类之间的特殊关系基类指针或引用可以在不进行显式类型转换的情况下,指向或引用派生类对象或引用.然而基类指针和引用只能用于调用基类方法.所以不能用它们来调用派生类方法,如果将基类对象和地址赋值给派生类引用和指针.因为派生类引用可以为基类对象调用派生类方法,但是基类没有派生类的成员所以这么做是没意义的.可以这么写:123456789Ship sp1(1,17,"Lexington",0);player *a = &sp1; //OKplayer &b = sp1; //OKa.Name();b->Name();player sp2("Saratoga",0);Ship *c = &sp2; //不能这么写Ship &d = sp2; //也不能这么写 但是如果基类引用和指针可以指向派生类对象呢?12345678void Show(const player & p){ //OK p.Name();}player temp1("LEX",0);Ship temp2(1,17"SAG",0);Show(temp1); //OKShow(temp2); //OK 函数Show的形参为一个基类引用,他可以指向基类对象或者派生类对象,并且该函数使用了基类的一个方法,所以Show可以使用player参数或者Ship参数.","tags":[{"name":"笔记","slug":"笔记","permalink":"http://yoursite.com/tags/笔记/"},{"name":"代码","slug":"代码","permalink":"http://yoursite.com/tags/代码/"},{"name":"CPP","slug":"CPP","permalink":"http://yoursite.com/tags/CPP/"}]},{"title":"C++笔记-多态公有继承","date":"2016-12-07T15:35:20.000Z","path":"2016/12/07/C++笔记-多态公有继承/","text":"虚方法:virtual该声明之处方法在派生类版本的行为不同.12345678910111213141516171819class You{public: You(const string& Na){Name = na;} virtual string Show(){return Name;} ~You(){}private: string Name;}class YouName : public You{public:YouName(){}~YouName(){}virtual void Show(){cout << "your name is "; Name();}}You A("YSY"); YouName B("YES");A.Name(); //You::Name()B.Name(); //YouNAme::Name(); 基类版本限定名为You::Show(),派生类限定名为YouName::Show()程序会根据使用对象类型来确定使用哪个版本. 需要注意的是如果方法是通过指针或引用调用的呢?程序将使用哪种方法? 如果没有使用关键字virtual,程序将根据引用类型或者指针类型来选择方法,如果使用了关键字virtual,程序将根据引用或指针指向的对象的类型来确定方法~","tags":[{"name":"笔记","slug":"笔记","permalink":"http://yoursite.com/tags/笔记/"},{"name":"代码","slug":"代码","permalink":"http://yoursite.com/tags/代码/"},{"name":"CPP","slug":"CPP","permalink":"http://yoursite.com/tags/CPP/"}]},{"title":"C++笔记-类和动态内存分配","date":"2016-12-07T13:59:20.000Z","path":"2016/12/07/C++笔记-类和动态内存分配/","text":"对于类中的非静态const数据成员,必须在执行到构造函数体前,级创建对象时进行初始化.他叫做成员列表初始化:123456789101112131415161718class FUN{ public: struct Node{Item item;struct Node* next;} Node* fornt; Node* rear; int items; const int qsize;}FUN::FUN(int qs) : qsize(qs){ front = rear = nullptr; items = 0;}//然而这种方法不局限于常量 所以也可以这么写FUN::FUN(int qs):qsize(qs),rear(nullptr),front(nullptr),items(0){...} 注意只有构造函数才可以使用这种方法,另外对于被引用的类成员也必须这么写: 123456class A{...};class B{private: A & hello;}B::B(A & h) : hello(h){....} 关于C++11的内存初始化直接这么写就行(那我们为啥要用第一种方法啊魂淡(。•ˇ‸ˇ•。)):1234class Classy{ int meml = 10; const int mem2 = 20;}","tags":[{"name":"笔记","slug":"笔记","permalink":"http://yoursite.com/tags/笔记/"},{"name":"代码","slug":"代码","permalink":"http://yoursite.com/tags/代码/"},{"name":"CPP","slug":"CPP","permalink":"http://yoursite.com/tags/CPP/"}]},{"title":"QML与C++交互","date":"2016-12-05T14:38:29.000Z","path":"2016/12/05/QT-QML与C++交互/","text":"图片作者为:Bison仓鼠终于搞定了gayhub的博客,以后就在这里写一些自己想写的东西好了 ╰(´︶`)╯ 前几天用QT的QML与C++交互,基于QMediaPlayer类撸了一个baka音乐播放器 因为是第一次用QML撸 再加上我幼儿园水平的代码 写的一团糟..代码请用鼠标♂插 这里 这个坑爹的QML与C++交互折腾了我好久 (╬▔▽▔)凸 以我这辣鸡播放器为例(大神请绕道orz) 那么问题来了如何使用C艹来控制QML?假设我们的qml是酱紫写的 1234567891011121314151617181920212223Rectangle { id: head y: 0 width: parent.width height: 40 color: \"#222225\" opacity: 0.95 Layout.maximumHeight: 45 Layout.fillWidth: true Layout.fillHeight: false Text { id: musicteitle x:5 y:5 width: parent.width height: 30 color: \"#C8C8C8\" font.family: \"microsoft yahei\" font.pixelSize: 23 text:\"正在播放: \" + myTITLE } } 让我们用这段代码来显示正在播放的某音乐的名字,细心的你可能发现了 你写这辣鸡玩意前面的我都能看懂 那个myTITLE是什么鬼?这里让我们隆重介绍一下QQmlContext这个神奇的类 : “QQmlContext类定义了一个QML引擎上下文引擎上下文允许将数据暴露给由QML引擎实例化的QML组件每个QQmlContext包含一组属性,与其QObject属性不同,它允许数据通过名称显式绑定到上下文。上下文属性通过调用QQmlContext :: setContextProperty()来定义和更新” –power for 谷歌翻译 参照官网 于是我们可以酱紫来控制音乐的名字:123QQmlApplicationEngine* view = new QQmlApplicationEngine;QQmlContext* title = view->rootContext();title->setContextProperty(\"myTITLE\",QVariant(NowMusicName)); 通过修改NowMusicName的值就可修改myTITLE的值 这俩东西是绑定的于是我们就可以通过C++来修改NowMusicName 从而修改QML中的myTITLE 进而达到显示出当前音乐名字的目的 那么 view是何方神圣? 官方文档: QQmlApplicationEngine QQmlApplicationEngine提供了从单个QML文件加载应用程序的方便方法。这个类结合了QQmlEngine和QQmlComponent来提供一个方便的方式来加载单个QML文件。它还向QML提供了一些中央应用程序功能,C++/QML混合应用程序通常会从C++控制 –power for 谷歌翻译 果冻,能不能给力点啊?(눈_눈) 你说的这么水 没人会看的好吧ಥ_ಥ 既然能从用C艹来艹QML 那自然也能用QML来艹C艹了 如何用QML来艹C艹?假设我们现在有个音乐类Music 我们想通过点击某个按钮来调用Music类的暂停函数 那么如何实现呢?这个就相对的比较简单了123456//main.cpp如下Music A;QQmlApplicationEngine* view = new QQmlApplicationEngine;QQmlContext* context = view->rootContext();context->setContextProperty("myPlay",A); //先创建一个关于Music类的一个上下文 123456789101112131415161718192021222324//fun.qml如下MouseArea { //设置鼠标点击区域 id: sta_pau x: 120 y: 8 width: 45 height: 45 Layout.maximumHeight: 40 Layout.maximumWidth: 70 Layout.fillHeight: true Layout.fillWidth: true onClicked: { myPlay.pausePlay(); } } Image { //把图片放到鼠标点击区域上去 这就是个按钮了 不要在意用没用button那个细节233 id: sta_pau_bg x: 120 y: 8 width: 45 height: 45 source: \"///img/我是一个萌萌的按钮图片.png\" } 这样 一旦我们点击鼠标区域 就会触发与myPlay相之绑定的Music类对象A 从而调用A的暂停播放函数. 果冻,能不能再给力点啊?(눈_눈) 你说的这么水 等着挨喷吧 o(////▽////)q 既然你诚心诚意的发问了~那么,现在让我们结合起用C艹来艹QML与用QML来艹C艹,假设我们要撸一个音乐播放器的进度条.C艹负责处理歌曲总时长,当前播放进度,QML负责用花哨の特♂技显示出来这些. 机智的你一定想到了,这还不简单,QML中使用一个Text控件用于显示,C艹则用QQmlContext建立上下文把总时长与标记绑定,当总时长改变,标记的值也会随之改变,显示出来的总时长也会随之改变. 而当前播放进度则使用QML的进度条显示Slider 控件处理.Slider的value就是当前播放的进度,通过点击事件onPressedChanged来处理进度条的拖动~ 为了更精细的显示出当前播放进度,我们还需要一个用于显示当前播放秒数的Text~同理用C艹获得当前播放秒数,绑定上下文就成.这里只截出一部分 详情还劳烦各位去窝项目上的main.qml与music.cpp上瞅瞅 鬼畜级代码奉上(。・`ω´・)123456789101112131415161718192021222324252627282930313233343536373839404142434445464748/*QML*/ RowLayout { id: progress x: 0 y: 20 width: 580 height: 60 spacing: 5 Layout.maximumWidth: 580 Text { /*用于精确显示当前播放秒数*/ id: s_time y: 0 width: 40 height: 20 color: \"#C8C8C8\"; text: mySTIME /*秒数的上下文*/ font.pixelSize: 18 } Slider { id: bar x: 50 width: 450 height: 20 Layout.maximumWidth: 550 Layout.maximumHeight: 20 Layout.fillWidth: true maximumValue:myPlay.getEndtime();/*使用与Music类对象A绑定的上下文来获得当前音乐的总时长*/ value: setNOW /*用当前播放的值来设置进度条的当前进度*/ onPressedChanged: { /*点击事件:当进度条拖动时改变音乐进度*/ myPlay.setNowMusicPos(value); } } Text { id: e_time x: 515 y: 0 width: 40 height: 20 color: \"#C8C8C8\" text: myETIME /*总时长的上下文*/ font.pixelSize: 18 } } 12345678910111213141516/*C艹*/ QObject::connect(now, &QMediaPlayer::positionChanged, [this](qint64 position){ if(nowMusic->duration() != 0) /*QMediaPlayer* nowMusic */ this->setEndtime(this->now->duration()); //获取当前音乐的总时长 settime(position); /*Music类成员,用于获得当前播放的位置(就是当前播放到哪了 单位:毫秒)*/ QQmlContext* s_time =myView->rootContext(); //当前播放时长的上下文 s_time->setContextProperty(\"mySTIME\",QVariant(timeformat(position))); QQmlContext* now_progress = myView->rootContext();//进度条值的上下文 now_progress->setContextProperty(\"setNOW\",QVariant(position)); QQmlContext* e_time = myView->rootContext(); //总时长的上下文 e_time->setContextProperty(\"myETIME\",QVariant(timeformat(this->endtime))); }); 在这里我们使用了一个QMediaPlayer类的一个信号,每当音乐播放进度改变时都会发射该信号从而调用与之对应的槽(在这段代码里槽为一个lambda).需要注意的是这个信号发射粒度(周期)为1s,据说可以修改发射粒度,找了半天无果,如果你知道怎么改,请务必联系我~阿里嘎多~每当音乐播放1s,positionchanged信号被发射,C++代码刷新各项数据,通过上下文引擎把数据暴露给QML.当进度条拖动的时候,QML的点击事件将触发改变音乐进度的函数来改变播放进度. 大概就酱 谢谢捧场 谢谢~ 欢迎讨论~欢迎纠错(逃","tags":[{"name":"代码","slug":"代码","permalink":"http://yoursite.com/tags/代码/"},{"name":"CPP","slug":"CPP","permalink":"http://yoursite.com/tags/CPP/"},{"name":"Qt","slug":"Qt","permalink":"http://yoursite.com/tags/Qt/"},{"name":"QML","slug":"QML","permalink":"http://yoursite.com/tags/QML/"}]}] \ No newline at end of file diff --git a/index.html b/index.html index 2bd8fdb..91789c1 100644 --- a/index.html +++ b/index.html @@ -143,6 +143,62 @@

    冰果冻

    +
    +
    + +
    + + +

    + 如何稍微有点瑕疵的把3D定制女仆2的模型导入到Maya2017 +

    + + + +
    + +
    + +
    +

     Warning:萌新向

      众所周知,我是个死宅,我觉得我死的还可以,但算不上是硬核死宅,主要是因为囊中羞涩以及时间大部分被我用来扯淡了,不过像3D妹抖2这种游戏我还是十分喜欢的.233333
      
      然后在一个月黑风高的晚上,本死宅正摩拳擦掌跃跃欲试准备重温我大I社的经典作品的时候,某群里的一个dalao突然问我3D定制女仆2能不能捏男的..
      
      是时候展现我..等等..Eexcuse me?我是不是撸多了看错了什么?
      男..男的?!什么鬼!…算了是时候展现我高超の捏脸技术辣…那么开工..于是我抱着大佬的大腿开始讨论电影大业(大雾
      
      "一时语塞"
      
      那么.呃..首先.怎么把3D妹抖2里的模型导出来呢..于是这个大佬给我发了个链接(你..为什么这么熟练啊?):
      https://tieba.baidu.com/p/4347623203?qq-pf-to=pcqq.group
      是贴吧某大神自制的十分牛逼的3D妹抖插件,可以把3D妹抖2的人物模型导出为PMX格式(似乎是mmd的工程文件?)或OBJ格式,具体使用方法在帖子里,我就不重复叙述了。。
      于是我展现了我神(手)乎(残)其(至)神(极)の捏脸技术..果断捏了个帅气的小♀哥♀哥:
      小哥哥
      (再次感叹下..3D妹抖2这渲染真牛逼)
      哎.,等等..说好的小哥哥! 这胸是怎么一回事啊,不过即使这样我也真觉得这个男装少女挺帅的..
      "真的"
      真没得治了..或许某些MOD可以解决此问题..不过这不是重点..
      重点是接下来到了激动人心的打开Maya环节.讲道理..这是我第二次打开Maya…真·萌新;
      [文件->导入].导入完我看了一眼吓得本萌新喵躯一震..口列娃纳尼?
      "诡异事件"
      为啥身体被分成了三段…这是何等的恶人才能干出的事儿啊(大雾).讲道理我一开始以为是Maya对obj格式的兼容问题.但有人告诉我,Maya对obj资呲的不错.然后我又想到是不是导出的问题?..我甚至考虑了3D妹抖2是不是只建了这三段的模型?….不过后来再次证明了我是个sb而且图样图森破…
      在我不经意之间,鼠标轻划过屏幕勾勒出了一片…一片网格?!
      "网格"
      凭借这本萌新敏锐的自觉.和十多分钟的摸索.我觉得.导出没问题.也不是3D妹抖建模的锅..只是..它变!透!明!了!..结果果然是透明度的问题..调回来就好辣..(不过他为啥会变透明啊? 求大佬详解~)
      我们可以在[大纲视图]中查看透明的区域.并在右边的[属性编辑器]中把透明度调回来;
      "调透明度"
      以此类推..我们还可以把贴图打上..按下[数字6]进入Maya的着色显示模式..(我才不会告诉你们我一开始没开着色显示弄了半个点的贴图…..被自己傻到);然后发现有的贴图已经被打上去了..而有的地方还是单调的模型灰..先把皮肤打上吧…看到右边的[属性编辑器]里有个[颜色]选项了吗? 看到颜色后面那个长的跟一个门似得的黑色框型小按钮了吗?
      "颜色"
      就是他,点一下,找到你存放模型的目录..找到长的跟皮肤似得的贴图..打开
      
      让我们把所有的贴图都打上..不知道为啥有的地方贴图只有渲染才能看见.比如眉毛睫毛….而且那个神一般的阿诺德渲染器本baka完全玩不转啊..
      
      没有渲染: 五官不知道为啥都没了
      
      
      阿诺德渲染器: 讲道理,这渲染效果真吊,就是那眉毛和眼睛是什么鬼啊! (求哪位大佬告诉我~)
      
     
      Maya软件渲染:终于有一个正常点的了.小伙…还挺帅的嘛..不过因为是CPU渲染..速度嘛..你们感受一下,我I7-6700HQ渲染540960分辨率的3D运动模糊产品级别,一帧用了6S..
      
      *硬件2.0渲染
    : 速度快了一个数量级….但是效果..我真不会调
      

    +
    + + + + +
    + +
    +
    + + + + + + + + +
    @@ -649,61 +705,6 @@

    - - - - - - - - - -