- 2021-04-14 发布 |
- 37.5 KB |
- 42页
申明敬告: 本站不保证该用户上传的文档完整性,不预览、不比对内容而直接下载产生的反悔问题本站不予受理。
文档介绍
精编个人笔记总结(一)
个人笔记总结 C++ 个人笔记总结 --------------------------------- --------------- Good luck Believe yourself Just go - 1 - 概述: 一、 C++ 语言语法基础 (6) 1.从 C 到 C++的过渡 2.类和对象 3.操作符重载 4.继承与多态 5.异常和 I/O 流 二、数据结构和算法 1.基本数据结构,堆栈、队列、链表、二叉树,实现和应用(2) 2.排序和查找算法 三、模板和 STL 1.模板语法 2.STL 四、阶段项目 简化的企业管理信息系统(MIS) - 2 - 第一课 从 从 C C 到 到 C++ 的过渡 一、背景介绍 算盘 - 面向硬件的编程 电子计算机 - 机器语言的编程 1010 - 汇编语言的编程 ADD - 高级语言的编程 Fortran printf (%d, 12); - 结构化程序设计 C/PASCL 顺序、分支、循环、函数 - 面向对象的程序设计 C++/Java/C# - 面向问题的程序设计 1960 - Algol 60,算法语言,远离硬件,不适合进行系统开发 1963 - 剑桥大学,CPL,在 Algol 60 的基础上增加对系统开发的支 持,复杂,不易掌握,不易使用 1970 - MIT,BCPL,CPL 的精华版,易学易用,太慢,不实用 1972 - Ken Thomposon,B 语言,通过运行时支持优化 BCPL 的性能,缺少类型 1973 - Dennis Ritchie,C 语言,用 C 重新实现 UNIX 内核 1978 - 《The C Programming Language》,第一个 C 语言的事实标准 1989 - ANSI C,C89 1990 - ISO C, C90 1999 - ISO C 修订,C99 197X - Bajarne Stroustrup,simula 早期的面向对象语言,性能低 下,B 语言。 1979 - 贝尔实验室,多核 UNIX 系统仿真,Cpre, 通过扩展宏为 C 语言增加类似 simula 的面向对象机制。C with Class: simula - 类 Alogo 68 - 操作符重载 Ada - 模板、名字空间 Smalltalk - 引用、异常 C 是 C++的子集,C++是对 C 的扩展。 1983 - C++命名 1985 - CFront 1.0,第一款商用 C++编译器 1987 - GNU C++ 1990 - Borland C++ 1992 - Microsoft C++,IBM C++ 1998 - ISO C++98 2003 - ISO C++03 2011 - ISO C++2011/C++11/C++0x - 3 - 二、 C++ 语言的使用领域: 1.游戏开发:强建模能力,性能高。 2.科学计算:FORTRAN,C++算法库。 3.网络和分布式:ACE 框架。 4.桌面应用:VC/MFC,Office,QQ,多媒体 5.操作系统和设备驱动:优化编译器的发明使 C++在底层开发方面可 以和 C 向媲美。 6.移动终端 既需要性能,同时又要有面向对象的建模。 三、 C++比 比 C C 更丰富 1.支持面向对象,将问题域和方法域统一化。宏观面向对象,微观面 向过程。 2.支持泛型编程。 int add (int a, int b) { ... } templatelt;typename Tgt; T add (T a, T b) { ... } 3.支持异常机制。 int func (void) { ... } int main (void) { if (func () == -1) { 错误处理; } } 4.操作符重载 四、第一个 C++ 程序 1.编译器:g++,如果用 gcc 需要带上-lstdc++, 指定其使用标准 c++的运行库。 2.源文件扩展名:.cpp/.cc/.C/.cxx/.c++,最好用.cpp 3.头文件:#include lt;iostreamgt; 大多数标准库头文件都没有.h 后缀。 4.输出:cout - 标准输出对象 输入:cin - 标准输入对象 插入运算符:lt;lt; 提取运算符:gt;gt; 5.std:所有标准库的函数、对象、类型都位于 std 名字空间中。 - 4 - 五、名字空间 1. 对程序中的标识符(类型、函数、变量), , 按照某种逻辑规则划分成若干组。 2. 定义名字空间 namespace 名字空间名 { 名字空间成员; } 3. 使用名字空间 1 1 作用于限定符:名字空间名 :: 名字空间成员, 表示访问特定名字空间中的特定成员。 例子: #include lt;iostreamgt; int main (void) { std ::cout lt;lt; Hello, World ! lt;lt; std ::endl; int i; double d; char s[256]; // scanf (%d%lf%s, i, d, s); std ::cin gt;gt; i gt;gt; d gt;gt; s; // printf (%d %lf %s n, i, d, s); std ::cout lt;lt; i lt;lt; " " lt;lt; d lt;lt; " " lt;lt; s lt;lt; " "; return 0; } --------------------------------------------------------- 2 2 名字空间指令: using namespace 名字空间名; 在该条指令之后的代码对指令所指名字空间中的所有成员都可见, 可直接访问这些成员,无需加::。 例: using namespace std; 3 3 名字空间声明: using 名字空间名::名字空间成员; 将指定名字空间中的某个成员引入当前作用域, 可直接访问这些成员,无需加::。 - 5 - 4. 匿名名字空间 如果一个标识符没有被显示地定义在任何名字空间中, 编译器会将其缺省地置于匿名名字空间中。 对匿名名字空间中的成员通过::名字空间成员的形式访问。 5. 名字空间合并 6. 名字空间嵌套 namespace ns1 { namespace ns2 { namespace ns3 { void foo (void) { ... } } } } ns1::ns2::ns3::foo (); using namespace ns1::ns2::ns3; foo (); --------------- 例子:名字空间 #include lt;iostreamgt; using namespace std; //namespace { void print (int money) { cout lt;lt; money lt;lt; endl; } //} // 农行名字空间 namespace abc { int balance = 0; void save (int money) { balance += money; } void draw (int money) { balance -= money; } } namespace abc { void salary (int money) { balance += money; } - 6 - void print (int money) { cout lt;lt; 农行:; ::print (money); } } // 建行名字空间 namespace ccb { int balance = 0; void save (int money) { balance += money; } void draw (int money) { balance -= money; } void salary (int money) { balance += money; } } int main (void) { using namespace abc; // 名字空间指令 save (5000); cout lt;lt; 农行: lt;lt; balance lt;lt; endl; draw (3000); cout lt;lt; 农行: lt;lt; balance lt;lt; endl; ccb::save (8000); cout lt;lt; 建行: lt;lt; ccb::balance lt;lt; endl; ccb::draw (5000); cout lt;lt; 建行: lt;lt; ccb::balance lt;lt; endl; using ccb::salary; // 名字空间声明 // using abc::salary; salary (6000); cout lt;lt; 建行: lt;lt; ccb::balance lt;lt; endl; abc::print (abc::balance); return 0; } - 7 - 六、 C++ 中的结构、联合和枚举 1. 结构 和 C 语言的不同: 1)定义结构型变量时,可以省略 struct 关键字。 2)结构内部可以定义函数成员函数。 3)sizeof (空结构) -gt; 1 例子: #include lt;iostreamgt; using namespace std; struct Student { char name[128]; int age; void who (void) { // 成员函数 cout lt;lt; 我叫 lt;lt; name lt;lt; ,今年 lt;lt; age lt;lt; 岁了。 lt;lt; endl; } }; int main (void) { Student student = {张飞, 25}, *ps = student; student.who (); ps-gt;who (); struct A {}; cout lt;lt; sizeof (A) lt;lt; endl; return 0; } ----------------------------------------------------------- 2. 联合 增加了匿名联合的概念。借用联合语法形式, 描述一些变量在内存中的布局方式。 int main() { UNION { int a; char ch[4]; }; a=0x12345678; } 定义联合变量时,可以不加 union - 8 - 例子: #include lt;iostreamgt; using namespace std; int main (void) { // 匿名联合 union { int x; char c[4] /*= {"A", "B", "C", "D"}*/; }; cout lt;lt; (void*)x lt;lt; " " lt;lt; (void*)c lt;lt; endl; x = 0x12345678; for (int i = 0; i lt; 4; ++i) cout lt;lt; hex lt;lt; (int)c[i] lt;lt; " "; cout lt;lt; endl; return 0; } --------------------------------------------------- 3. 枚举 枚举是一个独立的数据类型。 C: enum E {a, b, c}; enum E e; e = a; e = 1000; C++: enum E {a, b, c}; E e; e = a; e = b; e = c; b=1; // ERROR ! e = 1000; // ERROR ! e = 1; // ERROR ! - 9 - 例子: #include lt;iostreamgt; using namespace std; int main (void) { enum E {a, b, c}; E e; e = a; e = b; e = c; / // / e = 1000; // 报错 // e = 1; // 报错 return 0;} 七、 C++ 的布尔类型,跟在 t cout 后面可以 boolalpha bool b = true; b = false; cout lt;lt; sizeof (b) lt;lt; endl; // 1 b = 100; b = 1.234; b = hello; b = "A"; 八、 C++ 中的运算符别名 - and || - or - bitand ^ - xor { - lt;% } - %gt; [ - lt;: ] - :gt; - 10 - 九、 C++ 中的函数 1. 重载:条件 在同一个作用域中, 函数名相同, 参数表不同的函数, 构成重载关系。 C++编译器会对程序中的函数做换名, 将参数表中的类型信息汇合到函数名中,以保证函数名的唯一。 通过 extern C,可以要求编译器不做 C++换名,以方便在 C 语言的模块中使用C++编译生成的代码。 方式一: extern C { int add (int a, int b) { return a + b; } } int sub (int a, in t b) { return a - - b; } } } } 方式二: extern C int add (int a, int b, int c) { return a + b + c; } } 2. 缺省参数和哑元参数 1)如果调用一个函数时,没有提供实参,那么对应形参就取缺省值。 2)如果一个参数带有缺省值,那么 它后边的所有参数必须都带有缺省值。 3)如果一个函数声明和定义 分开,那么缺省参数 只能放在声明中。 4)避免和重载发生歧义。 5)只有类型而没有名字的形参,谓之哑元。 i++ - operator++ ++i V1: void decode (int arg) { ... } V2: void decode (int) { ... } 例子 1 1 :重载与缺省值 #include lt;iostreamgt; using namespace std; void foo (int a = 10, double b = 0.01, const char* c = tarena); // 函数 1 1 void foo (void) {} // 函数 2 2 // 函数 1 1 与函数 2 2 构成重载关系 void bar (int) { // 函数 3 3 - 11 - cout lt;lt; bar(int) lt;lt; endl; } void bar (int, double) { // 函数 4 4 cout lt;lt; bar(int,double) lt;lt; endl; } // 函数 3 3 与函数 4 4 构成重载关系 int main (void) { foo (1, 3.14, hello); // 调用函数 1 1 foo (1, 3.14); // 调用函数 1 1 foo (1); // 调用函数 1 // foo (); // 歧义 ,可以调用函数 2 2 ,但也可以调用函数 1 1 ,因为函数 1 1 在不提供实参的情况下, 可以取缺省值。 bar (100); // 调用函数 3 3 bar (100, 12.34); // 调用函数 4 4 return 0; } -------------------------------------- 例子 2 2 :重载与作用域 #include lt;iostreamgt; using namespace std; namespace ns1 { int foo (int a) { 函数 1 1 cout lt;lt; ns1::foo(int) lt;lt; endl; return a; } }; namespace ns2 { double foo (double a) { 函数 2 2 cout lt;lt; ns2::foo(double) lt;lt; endl; return a; } }; int main (void) { using namespace ns1; // 名字空间指令 using namespace ns2; // 名字空间指令 cout lt;lt; foo (10) lt;lt; endl; //10 调用函数 1 1 ,作用域可见 2 ns2 与 与 ns1 ,所以与函数 2 2构成重载 cout lt;lt; foo (1.23) lt;lt; endl; //1.23 调用函数 2 2 ,作用域可见 2 ns2 与 与 ns1 ,所以与函数1 1 构成重载 - 12 - using ns1::foo; // 名字空间声明 (当同时出现名字指令与名字空间声明,则名字空间声明会隐藏名字空间指令) cout lt;lt; foo (10) lt;lt; endl; //10 ,调用函数 1 1 ,只可见名字空间 1 ns1 的 的 foo(), 所以也并不构成重载。 cout lt;lt; foo (1.23) lt;lt; endl; //10 ,调用函数 1 1 ,只可见名字空间 1 ns1 的 的 foo(), 所以也并不构成重载。 using ns2::foo; // 名字空间声明 cout lt;lt; foo (10) lt;lt; endl; //10 ,调用函数 1 1 ,可见名字空间 1 ns1 与名字空间 2 ns2 的foo(), 所以构成重载。 cout lt;lt; foo (1.23) lt;lt; endl; //1.23 ,调用函数 2 2 ,可见名字空间 1 ns1 与名字空间 2 ns2 的foo(), 所以构成重载。 return 0; } -------------------------------------------------------- 3. 内联 1)编译器用函数的二进制代码替换函数调用语句,减少函数调用的时间开销。这种优化策略成为内联。 2)频繁调用的简单函数适合内联,而稀少调用的复杂函数不适合内联。 3) 递归函数无法内联。 4)通过 inline 关键字,可以建议编译对指定函数进行内联,但是仅仅是建议而已。 inline void foo (int x, int y){...} - 13 - 十、 C++ 的动态内存分配 malloc/calloc/realloc/free 1.new/delete:对单个变量进行内存分配/释放 2.new[]/delete[]:对数组进行内存分配/释放 例子:w new 与 与 delete #include lt;iostreamgt; using namespace std; int main (void) { // int* pi = (int*)malloc (sizeof (int)); // free (pi); c //c 中的方法 int* pi = new int; *pi = 1000; cout lt;lt; *pi lt;lt; endl; delete pi; // 一定要释放内存,否则会造成内存泄露,很严重 pi = NULL; // 不要忘记这个,虽然不会 报错,但是要有好习惯 /* *pi = 2000; cout lt;lt; *pi lt;lt; endl; i //pi 指向的内存地址已经被释放,被初始化为指向 NULL */ pi = new int[10]; for (size_t i = 0; i lt; 10; ++i) pi[i] = i; for (size_t i = 0; i lt; 10; ++i) cout lt;lt; pi[i] lt;lt; " "; cout lt;lt; endl; delete[] pi; // 千 万记住 new[] 要用 delete[] 来释放内存 pi = NULL; pi = new int (1234); //用 用 w new 分配内存的同时初始化赋一个值。 cout lt;lt; *pi lt;lt; endl; //1234 delete pi; pi = NULL; char buf[4] = {0x12,0x34,0x56,0x78}; pi = new (buf) int; cout lt;lt; hex lt;lt; *pi lt;lt; endl; // delete pi; cout lt;lt; (void*)pi lt;lt; " " lt;lt; (void*)buf lt;lt; endl; int (*p)[4] = new int[3][4]; delete[] p; int (*q)[4][5] = new int[3][4][5]; delete[] q; return 0; } - 14 - 十一、引用 1. 引用即别名。 int a = 20; int b = a; // int* b = a; b = 10; // *b = 10; cout lt;lt; a lt;lt; endl; // 10 2. 引用必须初始化。 int a; int* p; a = 20; p = a; int b; // ERROR ! int b = a; // OK 3. 引用一旦初始化就不能再引用其它变量。 int a = 20, c = 30; int b = a; b = c; // c =gt; b/a 4. 引用的应用场景 1) 引用型参数 a.修改实参 b.避免拷贝,通过加 const 可以防止在函数中意外地修改实参的值,同时还可以接受拥有常属性的实参。 2) 引用型返回值 int b = 10; int a = func (b); func (b) = a; 从一个函数中返回引用往往是为了将该函数的返回值作为左值使用。但是,一定要保证函数所返回的引用的目标在该函数返回以后依然有定义,否则将导致不确定的后果。 不要返回局部变量的引用,可以返回全局、静态、成员变量的引用,也可以返回引用型形参变量本身。 5. 引用和指针 1)引用的本质就是指针,很多场合下引用和指针可以互换。 2)在 C++层面上引用和指针存在以下不同: - 15 - A. 指针式实体变量,但是引用不是实体变量。 int a = b; sizeof (a); // 4 double d = f; sizeof (d); // 8 B. 指针可以不初始化,但是引用必须初始化。 C. 指针的目标可以修改,但是引用的目标的不能修改。 D. 可以定义指针的指针,但是不能定义引用的指针。 int a; int* p = a; int** pp = p; // OK int r = a; int* pr = r; // ERROR E. 可以定义指针的引用,但是不能定义引用的引用。 int a; int* p = a; int* q = p; // OK int r = a; int s = r; // ERROR F. 可以定义指针的数组,但是不能定义引用的数组。 int a, b, c; int* parr[] = {a, b, c}; // OK int rarr[] = {a, b, c}; // ERROR 可以定义数组的引用。 int arr[] = {1, 2, 3}; int (arr_ref)[3] = arr; // OK 例子:指针与引用 #include lt;iostreamgt; using namespace std; void swap1 (int a, int b) { int c = a; a = b; b = c; } void swap2 (int* a, int* b) { int c = *a; *a = *b; *b = c; } void swap3 (int a, int b) { int c = a; a = b; b = c; - 16 - } void swap4 (const char* x, const char* y) { const char* z = x; x = y; y = z; } void swap5 (const char** x, const char** y) { const char* z = *x; *x = *y; *y = z; } void swap6 (const char* x, const char* y) { const char* z = x; x = y; y = z; } struct Student { char name[1024]; int age; }; void print (const struct Student s) { cout lt;lt; s.name lt;lt; , lt;lt; s.age lt;lt; endl; // s.age = -1; } void foo (const int x) { cout lt;lt; x lt;lt; endl; } int main (void) { int a = 100, b = 200; // swap1 (a, b); // swap2 (a, b); swap3 (a, b); cout lt;lt; a lt;lt; " " lt;lt; b lt;lt; endl; // 200 100 const char* x = hello, *y = world; // swap4 (x, y); // swap5 (x, y); swap6 (x, y); cout lt;lt; x lt;lt; " " lt;lt; y lt;lt; endl; Student s = {张飞, 22}; print (s); print (s); foo (100); return 0; } - 17 - 十二、显示类型转换运算符 C:目标类型变量 = (目标类型)源类型变量; 1. 静态类型转换 static_castlt;目标类型gt; (源类型变量) 如果在目标类型和源类型之间某一个方向上可以做隐式类型转换,那么在两个方向上都可以做静态类型转换。反之如果在两个方向上都不能做隐式类型转换,那么在任意一个方向上也不能做静态类型转换。 int* p1 = ...; void* p2 = p1; p1 = static_castlt;int*gt; (p2); char c; int i = c; 如果存在从源类型到目标类型的自定义转换规则,那么也可以使用静态类型转换。 例子:静态类型转换 #include lt;iostreamgt; using namespace std; int main (void) { int* p1 = NULL; 1 //p1 为 为 t int 型的指针 void* p2 = p1; 2 //p2 为 为 d void 型的指针 p1 = static_castlt;int*gt; (p2); //将 将 d void 型的 2 p2 指针转换为 t int 型指针并复制给 int型的 1 p1 指针。 return 0; } --------------------------------------------------- 2. 动态类型转换 dynamic_castlt;目标类型gt; (源类型变量) 用在具有多态性的父子类指针或引用之间。 3. 常类型转换 const_castlt;目标类型gt; (源类型变量) 给一个拥有 const 属性的指针或引用去常 const int a = 100; const int* p1 = a; *p1 = 200; // ERROR int* p2 = const_castlt;int*gt; (p1); *p2 = 200; // OK 4. 从解释类型转换 reinterpret_castlt;目标类型gt; (源类型变量); 在不同类型的指针或引用之间做类型转换,以及在指针和整型之间做类型转换。 5. 目标类型变量 = 目标类型( ( 源类型变量) ) ; int a = int (3.14); - 18 - 例子一:常类型转换 #include lt;iostreamgt; using namespace std; int main (void) { const volatile int a = 100; ( (字 关键字 e volatile 在描述变量时使用, ,以 阻止编译器优化那些以 e valatile 修饰的变量 ,volatile被用在一些变量能被意外方式改变的地方, , 例如: : 抛出中断, , 这些变量若无 volate ile 可能会和编译器执行的优化 相冲突. .) ) // a = 200; const volatile int* p1 = a; // *p1 = 200; int* p2 = const_castlt;int*gt; (p1); / // / 去常,去掉常属性 *p2 = 200; cout lt;lt; *p2 lt;lt; endl; // 200 cout lt;lt; a lt;lt; endl; // 200 // cout lt;lt; 100 lt;lt; endl; return 0; } -------------------- ----------------------------------- 例子二:解释型类型转换 #include lt;iostreamgt; using namespace std; int main (void) { int i = 0x12345678; char* p = reinterpret_castlt;char*gt; (i); for (size_t i = 0; i lt; 4; ++i) cout lt;lt; hex lt;lt; (int)p[i] lt;lt; " "; //78 56 34 12 cout lt;lt; endl; float* q = reinterpret_castlt;float*gt; (i); cout lt;lt; *q lt;lt; endl; void* v = reinterpret_castlt;void*gt; (i); cout lt;lt; v lt;lt; endl; return 0; } - 19 - 十三、 C++ 之父的建议 1. 少用宏,多用 const 、m enum 和 和 inline #define PAI 3.141519 const double PAI = 3.14159; #define ERROR_FILE -1 #define ERROR_MEM -2 enum { ERROR_FILE = -1, ERROR_MEM = -2 }; #define max(a,b) ((a)gt;(b)?(a):(b)) inline int double max (double a, double b) { return a gt; b a : b; } 2. 变量随用随声明同时初始化。 3. 少用 malloc/free ,多用 new/delete 。 4. 少用 C C 风格的强制类型转换,多用类型转换运算符。 5. 少用 C C 风格的字符串,多用 string 。 6. 树立面向对象的编程思想。 - 20 - 附加: g string 类: 例子: #include lt;iostreamgt; #include lt;cstringgt; #include lt;cstdiogt; using namespace std; int main (void) { string s1 (hello); string s2 = world; (s1 += ) += s2; cout lt;lt; s1 lt;lt; endl; string s3 = s1; cout lt;lt; s3 lt;lt; endl; cout lt;lt; (s1 == s3) lt;lt; endl; s3[0] = "H"; cout lt;lt; s3 lt;lt; endl; cout lt;lt; (s1 gt; s3) lt;lt; endl; cout lt;lt; s1.length () lt;lt; endl; cout lt;lt; (s1 == s3) lt;lt; endl; cout lt;lt; (strcasecmp (s1.c_str (), s3.c_str ()) == 0) lt;lt; endl; cout lt;lt; sizeof (s1) lt;lt; endl; FILE* fp = fopen (string.txt, w); // fwrite (s3, sizeof (s3), 1, fp); fwrite (s3.c_str (), sizeof (char), s3.length (), fp); fclose (fp); return 0; } - 21 - 第二课 类和对象 一、什么是对象 1.万物皆对象 2.程序就是一组对象,对象之间通过消息交换信息 3.类就是对对象的描述和抽象,对象就是类的具体化和实例化 二 、通过类描述对象 属性:姓名、年龄、学号 行为:吃饭、睡觉、学习 类就是从属性和行为两个方面对对象进行抽象。 三、面向对象程序设计 (OOP) 现实世界 虚拟世界 对象 -gt; 抽象 -gt; 类 -gt; 对象 1.至少掌握一种 OOP 编程语言 2.精通一种面向对象的元语言UML 3.研究设计模式,GOF 四、类的基本语法 1. 类的定义 class 类名 { }; 如 class Student { }; 2. 成员变量 属性 class 类名 { 类型 成员变量名; }; 如 class Student { string m_name; int m_age; }; 3. 成员函数 行为 class 类名 { 返回类型 成员函数名 (形参表) { 函数体; } }; - 22 - 如 class Student { string m_name; int m_age; void eat (const string food) { ... } }; 4. 访问控制属性 1)公有成员:public,谁都可以访问。 2)私有成员:private,只有自己可以访问。 3)保护成员:protected,只有自己和自己的子类可以访问。 4)类的成员缺省访控属性为私有,而结构的成员缺省访控属性为公有。 例子: #include lt;iostreamgt; using namespace std; class Student { private: // 声明为私有部分 string m_name; int m_age; public: // 声明为私有部分 void eat (const string food) { cout lt;lt; m_age lt;lt; 岁的 lt;lt; m_name lt;lt; 同学正在吃 lt;lt; food lt;lt; 。 lt;lt; endl; } void setName (const string name) { // 为接口 if (name == 2) cout lt;lt; 你才 lt;lt; name lt;lt; ! lt;lt; endl; else m_name = name; } void setAge (int age) { // 为接口 if (age lt; 0) cout lt;lt; 无效的年龄! lt;lt; endl; else m_age = age; } }; int main (void) { Student student; student.setName (2); // 你才 2 2 student.setAge (-100); // 无效年龄 student.setName (张飞); // 将其赋值给成员变量 m_name - 23 - student.setAge (20); // 将其赋值给 成员变量 m_age student.eat (包子); 0 //20 岁的张飞同学正在吃包子 return 0; } 5. 构造函数 class { ... 类名 (行参表) { 构造函数体; } }; 当一个对象被创建时,构造函数会自动被执行,其参数来自构造实参。 6. 构造函数可以通过构造参数实现重载。 7. 如果一个类没有定义任何构造函数,那么系统就会缺省地为其提供一个无参构造函数,该构造函数对于基本类型的成员变量不做初始化,对于类类型的成员变量,调用其相应类型的无参构造函 数初始化。 8. 对象创建过程 分配内存-gt;调用构造函数-gt;调用类类型成员的构造函数-gt;构造函数的代码 9. 初始化表 class 类名 { 类名(...) :初始化表 { 构造函数体 } }; const int x = 100; x = 100; int a = b; a = b; 1) 如果类中含有常量或引用型的成员变量,必须通过初始化表对其初始化。 2)成员变量的初始化顺序仅于其被声明的顺序有关,而与初始化表的顺序无关。 class A { public: A (char* psz) : m_str (psz), m_len (m_str.length()) {} private: size_t m_len; string m_str; } - 24 - 例子 1 1 :类的声明与定义以及使用可以不在一个文件 这是 h s.h 文件 #ifndef _S_H #define _S_H #include lt;stringgt; using namespace std; // 声明 t Student 类 class Student { public: Student (const string name = , int age = 0); void eat (const string food); private: string m_name; int m_age; }; #endif // _S_H 这是 p s.cpp 文件 #include lt;iostreamgt; using namespace std; #include s.h // 实现 t Student 类 Student::Student (const string name /* = */, int age /* = 0 */) : m_name (name), m_age (age) {} void Student::eat (const string food) { cout lt;lt; m_name lt;lt; , lt;lt; m_age lt;lt; , lt;lt; food lt;lt; endl; } 这是 p main.cpp 文件: #include s.h // 使用 t Student 类 int main (void) { Student s (张飞, 25); s.eat (包子); return 0; } --------------------------------------------------- - 25 - 例子 2 2 :不同的构造函数 #include lt;iostreamgt; using namespace std; class A { public: A (int a) {} }; class Student { private: string m_name; int m_age; A m_a; // 类类型的成员变量 public: void eat (const string food) { cout lt;lt; m_age lt;lt; 岁的 lt;lt; m_name lt;lt; 同学正在吃 lt;lt; food lt;lt; 。 lt;lt; endl; } // void _ZN7Student3eatERKSs (Student* this, // const string food) { // cout lt;lt; this-gt;m_age lt;lt; 岁的lt;lt;this-gt;m_name // lt;lt; 同学正在吃 lt;lt; food lt;lt; 。 lt;lt; endl; // } // 这是计算机中编译的 样子 void setName (const string name) { if (name == 2) cout lt;lt; 你才 lt;lt; name lt;lt; ! lt;lt; endl; else m_name = name; } void setAge (int age) { if (age lt; 0) cout lt;lt; 无效的年龄! lt;lt; endl; else m_age = age; } // 如果同时有多种构造函数存在,则根据构造参数来确定调用哪 个构造函数,既构造函数可以通过构造参数实现重载 // 构造函数 Student (const string name, int age) : m_a (100) { m_name = name; - 26 - m_age = age; } // 无参构造 Student (void) : m_a (100) { // 没有参数 m_name = 没名; m_age = 0; } // 单参构造 Student (const string name) : m_a (100), m_name (name), m_age (0) { m_name = 哈哈; } }; int main (void) { Student s1 (张飞, 25); s1.eat (包子); // _ZN7Student3eatERKSs (s1, 包子); // 编译器中的样子 Student s2 = Student (赵云, 22); s2.eat (烧饼); // _ZN7Student3eatERKSs (s2, 烧饼); // 编译器中的样子 Student s3; // 调用的无参构造 s3.eat (煎饼果子); Student* s4 = new Student (关羽, 30); // 调用有参构造,分配内存,并初始化 s4-gt;eat (烤鸭); // 当访问地址(指针或迭代器)的成员或数据时,用- -gt; gt; delete s4; Student s5 = *new Student (); // 调用无参构造初始化 s5.eat (面条); // 当访问直接对象的成员或数据时,用. . delete s5; Student sa1[3] = {s1, s2}; //用 用 1 s1 与 与 2 s2 给数组初始化,但第三个元素没有赋值 sa1[0].eat (KFC); 5 //25 岁的张飞同学正在吃 KFC sa1[1].eat (KFC); 2 //22 岁的赵云同学正在吃 KFC sa1[2].eat (KFC); 0 //0 岁的无名同学 正在吃 KFC Student* sa2 = new Student[3] {s1, s2}; 1 // c++2011 支持 sa2[0].eat (KFC); sa2[1].eat (KFC); - 27 - sa2[2].eat (KFC); delete[] sa2; Student s6 (刘备); // 调用单参构造 s6.eat (米饭);// return 0; } ----------------------------------------------- 练习 : 实 现一个 k Clock 类支持两种工作模式,计时器和电子钟。 00:01:00 14:05:37 #include lt;iomanipgt; cout lt;lt; setw(4) lt;lt; setfill("0") lt;lt; 12; 0012 Clock 时、分、秒 走 - 显示、滴答 练习答案: #include lt;iostreamgt; #include lt;iomanipgt; using namespace std; class Clock { public: Clock (bool timer = true) : m_ hour (0), m_min (0), m_sec (0) { if (! timer) { time_t t = time (NULL); tm* local = localtime (t); m_hour = local- - gt;tm_hour; m_min = local- - gt;tm_min; m_sec = local- - gt;tm_sec; } } } } void run (void) { for (;;) { show (); tick (); } } } } private: void show (void) { - 28 - cout lt;lt; " r" lt;lt; setfill ("0") lt;lt; setw (2) lt;lt; m_hour lt;lt; ":" lt;lt; setw (2) lt;lt; m_min lt;lt; ":" lt;lt; setw (2) lt;lt; m_sec lt;lt; flush; // printf ( r%02d:%02d:%02d, m_hour, // m_min, m_sec); } } void tick (void) { sleep (1); if (++m_sec == 60) { m_sec = 0; if (++m_min == 60) { m_min = 0; if (++m_hour == 24) m_hour = 0; } } } } } } int m_hour; int m_min; int m_sec; }; int main (void) { Clock clock (false); clock.run (); return 0; } } ------------------- ------------------ - 29 - 五、s this 指针 1.一般而言,在类的构造函数或成员函数中,关键字 this 表示一个指针,对于构造函数而言,this 指向正在被构造的对象,对于成员函数而言,this 指向调用该函数的对象。 2.this 指针的用途 1)在类的内部区分成员变量。 2)在成员函数中返回调用对象自身。 3)在成员函数内部通过参数向外界传递调用对象自身,以实现对象间交互。 老 -问题-gt; 学 师 lt;-答案- 生 class A { B m_b; }; class B { A m_a; }; sizeof(A) //error class C { C m_c; }; 例子 1 1 : #include lt;iostreamgt; using namespace std; class A { public: A (int data) : data (data) { cout lt;lt; 构造: lt;lt; this lt;lt; endl; // this-gt;data = data; } void foo (void) { cout lt;lt; foo: lt;lt; this lt;lt; endl; cout lt;lt; this-gt;data lt;lt; endl; } int data; }; int main (void) { A a (1000); // 创建对象调用了构造函数,并输出 s this 的地址,输出构造:0xbf9b24d8 cout lt;lt; main: lt;lt; a lt;lt; endl; // 输出该对象的地址,输出 main:0xbf9b24d8 a.foo () ; // 该对象调用 o foo 函数,输出 s this 的值,以及输出 this- -gt; gt; data的值,输出 foo : 0xbf9b24d8 1000 A* pa = new A (1000); // 创建对象调用构造函数,输出 s this 的地址,输出构造: - 30 - 0x95cc008 cout lt;lt; main: lt;lt; pa lt;lt; endl; // 输出该对象的地址,输出 main : 0x95cc008 pa-gt;foo (); // 该对象调用 o foo 函数,输出 s this 以及 this- -a gt;data 的值,输出 foo : 0x95cc008 1000 delete pa; } --------------------------------------------------- 例子 2 2 : #include lt;iostreamgt; using namespace std; class Counter { public: Counter (void) : m_data (0) {} Counter inc (void) { // 返回的是一个别名,不加 的话,返回的就是一个拷贝 ++m_data; return *this; } void print (void) { cout lt;lt; m_data lt;lt; endl; } private: int m_data; }; int main (void) { Counter c; // c.inc (); // c.inc (); // c.inc (); c.inc ().inc ().inc (); // 函数返回的是一个别名,是一个左值,可以用来调用函数 c.print (); // 输出为 3 3 ,如果前面的函数不加 ,返回的只是拷贝,输出为1 1 。 return 0; } - 31 - 例子 3 3 :学生与老师 #include lt;iostreamgt; using namespace std; class Student; // 因为在 r Teacher 中会用到 Student ,所以提前声明一下 class Teacher { public: void educate (Student* s); // 可以声明在类的内部,定义在类的外部 void reply (const string answer) { m_answer = answer; } private: string m_answer; }; class Student { public: void ask (const string question, Teacher* t) { cout lt;lt; 问题: lt;lt; question lt;lt; endl; t-gt;reply (不知道。); } }; void Teacher::educate (Student* s) { s-gt;ask (什么是 this 指针?, this); // 将问题 n question 和 和 r Teacher 类变量的地址作为参数传递给 t Student 类中的 ask k 成员函数,并在 k ask 函数中得到一个值作为参数传递给 Teacher类中的 y replay 函数,将值赋给 m_answer ,最后完成输出。 cout lt;lt; 答案: lt;lt; m_answer lt;lt; endl; } int main (void) { Teacher t; Student s; t.educate (s); return 0; } --------------------------------------------- - 32 - 六、常函数与常对象 1.如果在一个类的成员函数的参数表后面加上 const 关键字,那么这个成员函数就被成为常函数,常函数的 this 指针是一个常指针。在常函数内部无法修改成员变量,除非该变量具有 mutable 属性。而且在常函数内部也无法调用非常函数。 2.常对象:拥有 const 属性的对象,兑现引用或指针。 常对象只能调用常函数。 同型的常函数和非常函数可以构成重载关系。常对象调用常版本,非常对象调用非常版本。如果没有非常版本,非常对象也可以调用常版本。 const XXX 函数名 (const YYY yyy) const { ... } 例子: #include lt;iostreamgt; using namespace std; class A { public: // void bar (void) { // 函数 1 1 // cout lt;lt; 非常 bar lt;lt; endl; // } // 函数 1 1 与函数 2 2 构成函数重载 void bar (void) const { // 函数 2 2 cout lt;lt; 常 bar lt;lt; endl; } // void XXXbarYYY (A* this) {} void foo (void) const { // 函数 3 3 // m_i = 100; // 在常函数内部无法修改成员变量,除非那个成员变量有 e mutable 属性,例: mutable int m_i; const_castlt;A*gt;(this)-gt;m_i = 100; // 也可以通过去常来解决 } void print (void) const { cout lt;lt; m_i lt;lt; endl; } // _ZNK1A3fooEv (const A* this) { // const_castlt;A*gt;(this)-gt;m_i = 100; // } int m_i; }; void func (void) /*const*/ {} int main (void) { A a; a.foo (); // 调用的是函数 3 3 - 33 - a.print (); // 100 const A r = a;r //r 为常对象,a a 为非常对象 r.bar (); // 常 bar ,r r 为常对象,常对象只能调用常函数 // XXXbarYYY (r); // const A* a.bar (); // 常 bar ,a a 为非常对象,可以调用常函数,也 可调用非常函数 // XXXbarYYY (a); // A* return 0; } --------------------------------------------- 七、析构函数 class 类名 { ~类名 (void) { 析构函数体; } }; 当一个对象被销毁时自动执行析构函数。 局部对象离开作用域时被销毁,堆对象被 delete 时被销毁。 如果一个类没有定义任何析构函数,那么系统会提供一个缺省析构函数。缺省析构函数对基本类型的成员变量什么也不干,对类类型的成员变量,调用相应类型的析构函数。 一般情况下,在析构函数中释放各种动态分配的资源。 构造: 基类-gt;成员-gt;子类 析构: 子类-gt;成员-gt;基类 例子: #include lt;iostreamgt; using namespace std; class Double { public: Double (double data) : m_data (new double (data)) { cout lt;lt; 构造 lt;lt; endl; } // 构造函数早开始时执行,既创建对象时执行 ~Double (void) { cout lt;lt; 析构 lt;lt; endl; delete m_data; } // 析构函数在对象结束时执行 void print (void) const { cout lt;lt; *m_data lt;lt; endl; } - 34 - private: double* m_data; string m_lable; }; int main (void) { // { Double d1 (3.14); d1.print (); // 构造 ,3.14 (1 d1 调用的构造) // } Double* d2 = new Double (1.23); // 构造(2 d2 调用的构造) delete d2; // 析构(2 d2 调用的析构) cout lt;lt; 再见! lt;lt; endl; // 再见 return 0; // 析构,(程序结束,1 d1 调用的析构) } -----------------------------------------------------------------------------------八、拷贝构造函数和拷贝赋值运算符 1. 拷贝构造:用一个已有的对象,构造和它同类型的副本对象克隆。 2. 拷贝构造函数 形如 class X { X (const X that) { ... } }; 的构造函数成为 拷贝构造函数 。如果一个类没有定义拷贝构造函数,系统会提供一个缺省拷贝构造函数。缺省拷贝构造函数对于基本类型的成员变量,按字节复制,对于类类型的成员变量,调用相应类型的拷贝构造函数。 3. 在某些情况就下, 缺省拷贝构造函数只能实现浅拷贝 ,如果需要获得深拷贝的复制效果,就需要 自己定义拷贝构造函数 。 例子:拷贝函数 #include lt;iostreamgt; using namespace std; class Integer { public: Integer (int data = 0) : m_data (data) {} // 构造函数 void print (void) const { cout lt;lt; m_data lt;lt; endl; } - 35 - // 拷贝构造(自己定义的): Integer (const Integer that) : m_data (that.m_data) { cout lt;lt; 拷贝构造 lt;lt; endl; } private: int m_data; }; void foo (Integer i) { //用 用 r Inerger 类变量时实参给函数中 r Interger 类的形参赋值,同样会调用拷贝构造函数 i.print (); } Integer bar (void) { Integer i; return i; } int main (void) { Integer i1 (10); i1.print (); // 正常创建对象,输出 10 Integer i2 (i1); // 调用拷贝构造,输出拷贝构 造 i2.print (); // 调用 t print 函数,输出 10 Integer i3 = i1; // 调用拷贝构造,输出拷贝构造 i3.print (); // 调用 t print 函数,输出 10 // Integer i4 (10, 20); cout lt;lt; 调用 foo()函数 lt;lt; endl; foo (i1); // 调用拷贝构造函数,且调用 t print 函数输出,所以输出为拷贝构造 10 cout lt;lt; 调用 bar()函数 lt;lt; endl; Integer i4 (bar ()); return 0; } --------------------------------------------------- - 36 - 4. 拷贝赋值运算符函数 形如 class X { X operator= (const X that) { ... } }; 的成员函数称为 拷贝赋值运算符函数。如果一个类没有定义拷贝赋值运算符函数,系统会提供一个缺省拷贝赋值运算符函数。缺省拷贝赋值运算符函数对于基本类型的成员变量,按字节复制,对于类类型的成员变量,调用相应类型的拷贝赋值运算符函数。 5.在某些情况就下,缺省拷贝赋值运算符函数只能实现浅拷贝,如果需要获得深拷贝的复制效果,就需要自己定义拷贝赋值运算符函数。 例子:拷贝赋值运算符函数 #include lt;iostreamgt; using namespace std; class Integer { public: Integer (int data) : m_data (new int (data)) {} // 构造函数 ~Integer (void) { // 析构函数 if (m_data) { delete m_data; m_data = NULL; } } void print (void) const { cout lt;lt; *m_data lt;lt; endl; } Integer (const Integer that) : // 拷贝构造函数 m_data (new int (*that.m_data)) {} void set (int data) { *m_data = data; } - 37 - // 拷贝赋值运算符函数(运算符重载) Integer operator= (const Integer that) { // 防止自赋值 if (that != this) { // 释放旧资源 delete m_data; // 分配新资源 m_data = new int (*that.m_data); // 拷贝新数据 } // 返回自引用 return *this; } private: int* m_data; }; int main (void) ...查看更多