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) { ... } template<typename T> 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 <iostream>
大多数标准库头文件都没有.h 后缀。
4.输出:cout - 标准输出对象
输入:cin
- 标准输入对象
插入运算符:<<
提取运算符:>> 5.std:所有标准库的函数、对象、类型都位于 std 名字空间中。
- 4 - 五、名字空间
1. 对程序中的标识符(类型、函数、变量), ,
按照某种逻辑规则划分成若干组。
2. 定义名字空间
namespace 名字空间名 {
名字空间成员; }
3. 使用名字空间
1 1 作用于限定符:名字空间名 :: 名字空间成员,
表示访问特定名字空间中的特定成员。
例子:
#include <iostream> int main (void) {
std ::cout << "Hello, World !" << std ::endl;
int i;
double d;
char s[256];
//
scanf ("%d%lf%s", &i, &d, s);
std ::cin >> i >> d >> s;
//
printf ("%d %lf %s\ \ n", i, d, s);
std ::cout << i << " " << d << " " << s << "\n";
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 <iostream> using namespace std; //namespace {
void print (int money) {
cout << money << 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 << "农行:";
::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 << "农行:" << balance << endl;
draw (3000);
cout << "农行:" << balance << endl;
ccb::save (8000);
cout << "建行:" << ccb::balance << endl;
ccb::draw (5000);
cout << "建行:" << ccb::balance << endl;
using ccb::salary; //
名字空间声明 // using abc::salary;
salary (6000);
cout << "建行:" << ccb::balance << endl;
abc::print (abc::balance);
return 0; }
- 7 - 六、 C++ 中的结构、联合和枚举
1. 结构
和 C 语言的不同:
1)定义结构型变量时,可以省略 struct 关键字。
2)结构内部可以定义函数——成员函数。
3)sizeof (空结构) -> 1
例子:
#include <iostream> using namespace std; struct Student {
char name[128];
int age;
void who (void) {
// 成员函数
cout << "我叫" << name << ",今年" << age
<< "岁了。" << endl;
} }; int main (void) {
Student student = {"张飞", 25}, *ps = &student;
student.who ();
ps->who ();
struct A {};
cout << sizeof (A) << endl;
return 0; } ----------------------------------------------------------- 2. 联合
增加了匿名联合的概念。借用联合语法形式, 描述一些变量在内存中的布局方式。
int main() {
UNION
{
int a;
char ch[4];
};
a=0x12345678; } 定义联合变量时,可以不加 union
- 8 - 例子:
#include <iostream> using namespace std; int main (void) {
// 匿名联合
union {
int x;
char c[4] /*= {"A", "B", "C", "D"}*/;
};
cout << (void*)&x << " " << (void*)c << endl;
x = 0x12345678;
for (int i = 0; i < 4; ++i)
cout << hex << (int)c[i] << " ";
cout << 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 <iostream> 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 << sizeof (b) << endl; // 1 b = 100; b = 1.234; b = "hello"; b = "A"; 八、 C++ 中的运算符别名
&& - and || - or &
- bitand
^
- xor {
- <%
}
- %> [
- <: ]
- :>
- 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 <iostream> 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 << "bar(int)" << endl; } void bar (int, double) {
// 函数 4 4
cout << "bar(int,double)" << 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 <iostream> using namespace std; namespace ns1 {
int foo (int a) {
函数 1 1
cout << "ns1::foo(int)" << endl;
return a;
} }; namespace ns2 {
double foo (double a) {
函数 2 2
cout << "ns2::foo(double)" << endl;
return a;
} }; int main (void) {
using namespace ns1;
// 名字空间指令
using namespace ns2;
// 名字空间指令
cout << foo (10) << endl;
//10 调用函数 1 1 ,作用域可见 2 ns2 与 与 ns1 ,所以与函数 2 2构成重载
cout << foo (1.23) << endl;
//1.23 调用函数 2 2 ,作用域可见 2 ns2 与 与 ns1 ,所以与函数1 1 构成重载
- 12 -
using ns1::foo;
// 名字空间声明 (当同时出现名字指令与名字空间声明,则名字空间声明会隐藏名字空间指令)
cout << foo (10) << endl;
//10 ,调用函数 1 1 ,只可见名字空间 1 ns1 的 的 foo(), 所以也并不构成重载。
cout << foo (1.23) << endl;
//10 ,调用函数 1 1 ,只可见名字空间 1 ns1 的 的 foo(), 所以也并不构成重载。
using ns2::foo;
// 名字空间声明
cout << foo (10) << endl;
//10 ,调用函数 1 1 ,可见名字空间 1 ns1 与名字空间 2 ns2 的foo(), 所以构成重载。
cout << foo (1.23) << 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 <iostream> using namespace std; int main (void) {
// int* pi = (int*)malloc (sizeof (int));
// free (pi);
c //c 中的方法
int* pi = new int;
*pi = 1000;
cout << *pi << endl;
delete pi;
// 一定要释放内存,否则会造成内存泄露,很严重
pi = NULL;
// 不要忘记这个,虽然不会 报错,但是要有好习惯
/*
*pi = 2000;
cout << *pi << endl;
i //pi 指向的内存地址已经被释放,被初始化为指向 NULL
*/
pi = new int[10];
for (size_t i = 0; i < 10; ++i)
pi[i] = i;
for (size_t i = 0; i < 10; ++i)
cout << pi[i] << " ";
cout << endl;
delete[] pi;
// 千 万记住 new[] 要用 delete[] 来释放内存
pi = NULL;
pi = new int (1234);
//用 用 w new 分配内存的同时初始化赋一个值。
cout << *pi << endl;
//1234
delete pi;
pi = NULL;
char buf[4] = {0x12,0x34,0x56,0x78};
pi = new (buf) int;
cout << hex << *pi << endl; //
delete pi;
cout << (void*)pi << " " << (void*)buf << 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 << a << 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 => 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 <iostream> 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 << s.name << "," << s.age << endl; // s.age = -1; } void foo (const int& x) {
cout << x << endl; } int main (void) {
int a = 100, b = 200; // swap1 (a, b); // swap2 (&a, &b);
swap3 (a, b);
cout << a << " " << b << endl; // 200 100
const char* x = "hello", *y = "world"; // swap4 (x, y); // swap5 (&x, &y);
swap6 (x, y);
cout << x << " " << y << endl;
Student s = {"张飞", 22};
print (s);
print (s);
foo (100);
return 0; }
- 17 - 十二、显示类型转换运算符
C:目标类型变量 = (目标类型)源类型变量; 1. 静态类型转换
static_cast<目标类型> (源类型变量) 如果在目标类型和源类型之间某一个方向上可以做隐式类型转换,那么在两个方向上都可以做静态类型转换。反之如果在两个方向上都不能做隐式类型转换,那么在任意一个方向上也不能做静态类型转换。
int* p1 = ...; void* p2 = p1; p1 = static_cast<int*> (p2); char c; int i = c; 如果存在从源类型到目标类型的自定义转换规则,那么也可以使用静态类型转换。
例子:静态类型转换
#include <iostream> using namespace std; int main (void) {
int* p1 = NULL;
1 //p1 为 为 t int 型的指针
void* p2 = p1;
2 //p2 为 为 d void 型的指针
p1 = static_cast<int*> (p2);
//将 将 d void 型的 2 p2 指针转换为 t int 型指针并复制给 int型的 1 p1 指针。
return 0; } --------------------------------------------------- 2. 动态类型转换
dynamic_cast<目标类型> (源类型变量) 用在具有多态性的父子类指针或引用之间。
3. 常类型转换
const_cast<目标类型> (源类型变量) 给一个拥有 const 属性的指针或引用去常 const int a = 100; const int* p1 = &a; *p1 = 200; // ERROR int* p2 = const_cast<int*> (p1); *p2 = 200; // OK 4. 从解释类型转换
reinterpret_cast<目标类型> (源类型变量); 在不同类型的指针或引用之间做类型转换,以及在指针和整型之间做类型转换。
5. 目标类型变量
= 目标类型( ( 源类型变量) ) ;
int a = int (3.14);
- 18 - 例子一:常类型转换
#include <iostream> 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_cast<int*> (p1); / // / 去常,去掉常属性
*p2 = 200;
cout << *p2 << endl;
// 200
cout << a << endl;
// 200
// cout << 100 << endl;
return 0;
} -------------------- -----------------------------------
例子二:解释型类型转换
#include <iostream> using namespace std; int main (void) {
int i = 0x12345678;
char* p = reinterpret_cast<char*> (&i);
for (size_t i = 0; i < 4; ++i)
cout << hex << (int)p[i] << " ";
//78 56 34 12
cout << endl;
float* q = reinterpret_cast<float*> (&i);
cout << *q << endl;
void* v = reinterpret_cast<void*> (i);
cout << v << 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)>(b)?(a):(b)) inline int double max (double a, double b) {
return a > b ? a : b; }
2. 变量随用随声明同时初始化。
3. 少用 malloc/free ,多用 new/delete 。
4. 少用 C C 风格的强制类型转换,多用类型转换运算符。
5. 少用 C C 风格的字符串,多用 string 。
6. 树立面向对象的编程思想。
- 20 - 附加:
g string 类:
例子:
#include <iostream> #include <cstring> #include <cstdio> using namespace std; int main (void) {
string s1 ("hello");
string s2 = "world";
(s1 += " ") += s2;
cout << s1 << endl;
string s3 = s1;
cout << s3 << endl;
cout << (s1 == s3) << endl;
s3[0] = "H";
cout << s3 << endl;
cout << (s1 > s3) << endl;
cout << s1.length () << endl;
cout << (s1 == s3) << endl;
cout << (strcasecmp (s1.c_str (),
s3.c_str ()) == 0) << endl;
cout << sizeof (s1) << 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)
现实世界
虚拟世界 对象
-> 抽象 -> 类 -> 对象 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 <iostream> using namespace std; class Student { private:
// 声明为私有部分
string m_name;
int m_age; public:
// 声明为私有部分
void eat (const string& food) {
cout << m_age << "岁的" << m_name
<< "同学正在吃" << food << "。" << endl;
}
void setName (const string& name) {
// 为接口
if (name == "2")
cout << "你才" << name << "!" << endl;
else
m_name = name;
}
void setAge (int age) {
// 为接口
if (age < 0)
cout << "无效的年龄!" << 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. 对象创建过程
分配内存->调用构造函数->调用类类型成员的构造函数->构造函数的代码
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 <string> 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 <iostream> 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 << m_name << "," << m_age << "," << food
<< endl; }
这是 p main.cpp 文件:
#include "s.h" // 使用 t Student 类
int main (void) {
Student s ("张飞", 25);
s.eat ("包子");
return 0; } ---------------------------------------------------
- 25 - 例子 2 2 :不同的构造函数
#include <iostream> 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 << m_age << "岁的" << m_name
<< "同学正在吃" << food << "。" << endl;
}
// void _ZN7Student3eatERKSs (Student* this, //
const string& food) { //
cout << this->m_age << "岁的"<<this->m_name //
<< "同学正在吃" << food << "。" << endl; //
}
// 这是计算机中编译的 样子
void setName (const string& name) {
if (name == "2")
cout << "你才" << name << "!" << endl;
else
m_name = name;
}
void setAge (int age) {
if (age < 0)
cout << "无效的年龄!" << 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->eat ("烤鸭"); // 当访问地址(指针或迭代器)的成员或数据时,用- -> >
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 <iomanip> cout << setw(4) << setfill("0") << 12; 0012 Clock
时、分、秒
走 - 显示、滴答
练习答案:
#include <iostream>
#include <iomanip>
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- - >tm_hour;
m_min = local- - >tm_min;
m_sec = local- - >tm_sec;
} }
} }
void run (void) {
for (;;) {
show ();
tick ();
} }
} }
private:
void show (void) {
- 28 -
cout << "\ \ r" << setfill ("0")
<< setw (2) << m_hour << ":"
<< setw (2) << m_min << ":"
<< setw (2) << m_sec << 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)在成员函数内部通过参数向外界传递调用对象自身,以实现对象间交互。
老 -问题-> 学 师 <-答案- 生 class A {
B m_b; }; class B {
A m_a; }; sizeof(A)
//error class C {
C m_c; }; 例子 1 1 :
#include <iostream> using namespace std; class A { public:
A (int data) : data (data) {
cout << "构造: " << this << endl; //
this->data = data;
}
void foo (void) {
cout << "foo: " << this << endl;
cout << this->data << endl;
}
int data; }; int main (void) {
A a (1000);
// 创建对象调用了构造函数,并输出 s this 的地址,输出“构造:0xbf9b24d8 ”
cout << "main: " << &a << endl; // 输出该对象的地址,输出“ main:0xbf9b24d8 ”
a.foo () ;
// 该对象调用 o foo 函数,输出 s this 的值,以及输出 this- -> > data的值,输出“ foo :
0xbf9b24d8
1000 ”
A* pa = new A (1000);
// 创建对象调用构造函数,输出 s this 的地址,输出“构造:
- 30 - 0x95cc008 ”
cout << "main: " << pa << endl; // 输出该对象的地址,输出“ main :
0x95cc008 ”
pa->foo ();
// 该对象调用 o foo 函数,输出 s this 以及 this- -a >data 的值,输出“ foo :
0x95cc008
1000 ”
delete pa; } --------------------------------------------------- 例子 2 2 :
#include <iostream> using namespace std; class Counter { public:
Counter (void) : m_data (0) {}
Counter& inc (void) {
// 返回的是一个别名,不加& & 的话,返回的就是一个拷贝
++m_data;
return *this;
}
void print (void) {
cout << m_data << 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 <iostream> 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 << "问题:" << question << endl;
t->reply ("不知道。");
} }; void Teacher::educate (Student* s) {
s->ask ("什么是 this 指针?", this); // 将问题 n question 和 和 r Teacher 类变量的地址作为参数传递给 t Student 类中的 ask k 成员函数,并在 k ask 函数中得到一个值作为参数传递给 Teacher类中的 y replay 函数,将值赋给 m_answer ,最后完成输出。
cout << "答案:" << m_answer << 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 <iostream> using namespace std; class A { public: // void bar (void) {
// 函数 1 1 //
cout << "非常 bar" << endl; // } // 函数 1 1 与函数 2 2 构成函数重载
void bar (void) const {
// 函数 2 2
cout << "常 bar" << endl;
} //
void XXXbarYYY (A* this) {}
void foo (void) const {
// 函数 3 3
//
m_i = 100;
// 在常函数内部无法修改成员变量,除非那个成员变量有 e mutable 属性,例:
mutable int m_i;
const_cast<A*>(this)->m_i = 100; // 也可以通过去常来解决
}
void print (void) const {
cout << m_i << endl;
} // _ZNK1A3fooEv (const A* this) { //
const_cast<A*>(this)->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 时被销毁。
如果一个类没有定义任何析构函数,那么系统会提供一个缺省析构函数。缺省析构函数对基本类型的成员变量什么也不干,对类类型的成员变量,调用相应类型的析构函数。
一般情况下,在析构函数中释放各种动态分配的资源。
构造: 基类->成员->子类 析构: 子类->成员->基类 例子:
#include <iostream> using namespace std; class Double { public:
Double (double data) :
m_data (new double (data)) {
cout << "构造" << endl;
} // 构造函数早开始时执行,既创建对象时执行
~Double (void) {
cout << "析构" << endl;
delete m_data;
} // 析构函数在对象结束时执行
void print (void) const {
cout << *m_data << 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 << "再见!" << endl; // “再见”
return 0;
// “析构”,(程序结束,1 d1 调用的析构)
} -----------------------------------------------------------------------------------八、拷贝构造函数和拷贝赋值运算符
1. 拷贝构造:用一个已有的对象,构造和它同类型的副本对象——克隆。
2. 拷贝构造函数
形如 class X {
X (const X& that) { ... } }; 的构造函数成为 拷贝构造函数 。如果一个类没有定义拷贝构造函数,系统会提供一个缺省拷贝构造函数。缺省拷贝构造函数对于基本类型的成员变量,按字节复制,对于类类型的成员变量,调用相应类型的拷贝构造函数。
3. 在某些情况就下, 缺省拷贝构造函数只能实现浅拷贝 ,如果需要获得深拷贝的复制效果,就需要 自己定义拷贝构造函数 。
例子:拷贝函数
#include <iostream> using namespace std; class Integer { public:
Integer (int data = 0) : m_data (data) {} // 构造函数
void print (void) const {
cout << m_data << endl;
}
- 35 - // 拷贝构造(自己定义的):
Integer (const Integer& that) :
m_data (that.m_data) {
cout << "拷贝构造" << 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 << "调用 foo()函数" << endl;
foo (i1);
// 调用拷贝构造函数,且调用 t print 函数输出,所以输出为“拷贝构造
10” ”
cout << "调用 bar()函数" << endl;
Integer i4 (bar ());
return 0; } ---------------------------------------------------
- 36 - 4. 拷贝赋值运算符函数
形如 class X {
X& operator= (const X& that) {
...
} }; 的成员函数称为 拷贝赋值运算符函数。如果一个类没有定义拷贝赋值运算符函数,系统会提供一个缺省拷贝赋值运算符函数。缺省拷贝赋值运算符函数对于基本类型的成员变量,按字节复制,对于类类型的成员变量,调用相应类型的拷贝赋值运算符函数。
5.在某些情况就下,缺省拷贝赋值运算符函数只能实现浅拷贝,如果需要获得深拷贝的复制效果,就需要自己定义拷贝赋值运算符函数。
例子:拷贝赋值运算符函数
#include <iostream> 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 << *m_data << 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) ...
推荐访问:笔记