前言:
现在我们对“mfc office”大致比较珍视,姐妹们都需要知道一些“mfc office”的相关资讯。那么小编在网络上收集了一些关于“mfc office””的相关内容,希望同学们能喜欢,姐妹们一起来学习一下吧!C++
个人笔记总结
---------------------------------
王
小
龙
---------------
Good luck
Believe yourself
Just go
概述:
一、C++语言语法基础(6)
1.从C到C++的过渡
2.类和对象
3.操作符重载
4.继承与多态
5.异常和I/O流
二、数据结构和算法
1.基本数据结构,堆栈、队列、链表、二叉树,实现和应用(2)
2.排序和查找算法
三、模板和STL
1.模板语法
2.STL
四、阶段项目
简化的企业管理信息系统(MIS)
第一课 从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
二、C++语言的使用领域:
1.游戏开发:强建模能力,性能高。
2.科学计算:FORTRAN,C++算法库。
3.网络和分布式:ACE框架。
4.桌面应用:VC/MFC,Office,QQ,多媒体
5.操作系统和设备驱动:优化编译器的发明使C++在底层开发方面可
以和C向媲美。
6.移动终端
既需要性能,同时又要有面向对象的建模。
三、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) {
错误处理;
}
}
操作符重载
四、第一个C++程序
1.编译器:g++,如果用gcc需要带上-lstdc++,
指定其使用标准c++的运行库。
2.源文件扩展名:.cpp/.cc/.C/.cxx/.c++,最好用.cpp
3.头文件:#include <iostream>
大多数标准库头文件都没有.h后缀。
4.输出:cout - 标准输出对象
输入:cin - 标准输入对象
插入运算符:<<
提取运算符:>>
5.std:所有标准库的函数、对象、类型都位于std名字空间中。
五、名字空间
1.对程序中的标识符(类型、函数、变量),
按照某种逻辑规则划分成若干组。
2.定义名字空间
namespace 名字空间名 {
名字空间成员;
}
3.使用名字空间
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名字空间指令:
using namespace 名字空间名;
在该条指令之后的代码对指令所指名字空间中的所有成员都可见,
可直接访问这些成员,无需加"::"。
例:using namespace std;
3名字空间声明:
using 名字空间名::名字空间成员;
将指定名字空间中的某个成员引入当前作用域,
可直接访问这些成员,无需加"::"。
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;
}
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;
}
六、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
例子:
#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 !
例子:
#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++的布尔类型,跟在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
{ - <%
} - %>
[ - <:
] - :>
九、C++中的函数
1.重载:条件
在同一个作用域中,
函数名相同,
参数表不同的函数,
构成重载关系。
C++编译器会对程序中的函数做换名,
将参数表中的类型信息汇合到函数名中,以保证函数名的唯一。
通过extern "C",可以要求编译器不做C++换名,以方便在C语言的模块中使用C++编译生成的代码。
方式一:
extern "C" {
int add (int a, int b) {
return a + b;
}
int sub (int a, int 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:重载与缺省值
#include <iostream>
using namespace std;
void foo (int a = 10, double b = 0.01,
const char* c = "tarena"); //函数1
void foo (void) {} //函数2
//函数1与函数2构成重载关系
void bar (int) { //函数3
cout << "bar(int)" << endl;
}
void bar (int, double) { //函数4
cout << "bar(int,double)" << endl;
}
//函数3与函数4构成重载关系
int main (void) {
foo (1, 3.14, "hello");//调用函数1
foo (1, 3.14); //调用函数1
foo (1); //调用函数1
//foo (); // 歧义 ,可以调用函数2,但也可以调用函数1,因为函数1在不提供实参的情况下,可以取缺省值。
bar (100); //调用函数3
bar (100, 12.34); //调用函数4
return 0;
}
--------------------------------------
例子2:重载与作用域
#include <iostream>
using namespace std;
namespace ns1 {
int foo (int a) { 函数1
cout << "ns1::foo(int)" << endl;
return a;
}
};
namespace ns2 {
double foo (double a) { 函数2
cout << "ns2::foo(double)" << endl;
return a;
}
};
int main (void) {
using namespace ns1; // 名字空间指令
using namespace ns2; // 名字空间指令
cout << foo (10) << endl; //10 调用函数1,作用域可见ns2与ns1,所以与函数2构成重载
cout << foo (1.23) << endl; //1.23 调用函数2,作用域可见ns2与ns1,所以与函数1构成重载
using ns1::foo; //名字空间声明
(当同时出现名字指令与名字空间声明,则名字空间声明会隐藏名字空间指令)
cout << foo (10) << endl; //10,调用函数1,只可见名字空间ns1的foo(),所以也并不构成重载。
cout << foo (1.23) << endl; //10,调用函数1,只可见名字空间ns1的foo(),所以也并不构成重载。
using ns2::foo; //名字空间声明
cout << foo (10) << endl; //10,调用函数1,可见名字空间ns1与名字空间ns2的foo(),所以构成重载。
cout << foo (1.23) << endl; //1.23,调用函数2,可见名字空间ns1与名字空间ns2的foo(),所以构成重载。
return 0;
}
--------------------------------------------------------
3.内联
1)编译器用函数的二进制代码替换函数调用语句,减少函数调用的时间开销。这种优化策略成为内联。
2)频繁调用的简单函数适合内联,而稀少调用的复杂函数不适合内联。
3)递归函数无法内联。
4)通过inline关键字,可以建议编译对指定函数进行内联,但是仅仅是建议而已。
inline void foo (int x, int y){...}
十、C++的动态内存分配
malloc/calloc/realloc/free
1.new/delete:对单个变量进行内存分配/释放
2.new[]/delete[]:对数组进行内存分配/释放
例子:new与delete
#include <iostream>
using namespace std;
int main (void) {
//int* pi = (int*)malloc (sizeof (int));
//free (pi); //c中的方法
int* pi = new int;
*pi = 1000;
cout << *pi << endl;
delete pi; //一定要释放内存,否则会造成内存泄露,很严重
pi = NULL; //不要忘记这个,虽然不会报错,但是要有好习惯
/*
*pi = 2000;
cout << *pi << endl; //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); //用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;
}
十一、引用
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++层面上引用和指针存在以下不同:
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;
}
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;
}
十二、显示类型转换运算符
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; //p1为int型的指针
void* p2 = p1; //p2为void型的指针
p1 = static_cast<int*> (p2); //将void型的p2指针转换为int型指针并复制给int型的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);
例子一:常类型转换
#include <iostream>
using namespace std;
int main (void) {
const volatile int a = 100;
(关键字volatile在描述变量时使用,阻止编译器优化那些以valatile修饰的变量,volatile被用在一些变量能被意外方式改变的地方,例如:抛出中断,这些变量若无volatile可能会和编译器执行的优化 相冲突.)
//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;
}
十三、C++之父的建议
1.少用宏,多用const、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风格的强制类型转换,多用类型转换运算符。
5.少用C风格的字符串,多用string。
6.树立面向对象的编程思想。
附加: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;
}
第二课 类和对象
一、什么是对象
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 类名 {
返回类型 成员函数名 (形参表) {
函数体;
}
};
如
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
student.setAge (-100); //无效年龄
student.setName ("张飞"); //将其赋值给成员变量m_name
student.setAge (20); //将其赋值给成员变量m_age
student.eat ("包子"); //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;
}
例子1:类的声明与定义以及使用可以不在一个文件
这是s.h文件
#ifndef _S_H
#define _S_H
#include <string>
using namespace std;
// 声明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
这是s.cpp文件
#include <iostream>
using namespace std;
#include "s.h"
// 实现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;
}
这是main.cpp文件:
#include "s.h"
// 使用Student类
int main (void) {
Student s ("张飞", 25);
s.eat ("包子");
return 0;
}
---------------------------------------------------
例子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;
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}; //用s1与s2给数组初始化,但第三个元素没有赋值
sa1[0].eat ("KFC"); //25岁的张飞同学正在吃KFC
sa1[1].eat ("KFC"); //22岁的赵云同学正在吃KFC
sa1[2].eat ("KFC"); //0岁的无名同学正在吃KFC
Student* sa2 = new Student[3] {s1, s2}; // c++2011支持
sa2[0].eat ("KFC");
sa2[1].eat ("KFC");
sa2[2].eat ("KFC");
delete[] sa2;
Student s6 ("刘备");//调用单参构造
s6.eat ("米饭");//
return 0;
}
-----------------------------------------------
练习: 实现一个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) {
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;
}
-------------------------------------
五、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:
#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); //创建对象调用了构造函数,并输出this的地址,输出"构造:0xbf9b24d8"
cout << "main: " << &a << endl;//输出该对象的地址,输出"main:0xbf9b24d8"
a.foo (); //该对象调用foo函数,输出this的值,以及输出this->data的值,输出"foo:0xbf9b24d8 1000"
A* pa = new A (1000); //创建对象调用构造函数,输出this的地址,输出"构造:0x95cc008"
cout << "main: " << pa << endl; //输出该对象的地址,输出"main:0x95cc008"
pa->foo (); //该对象调用foo函数,输出this以及this->data的值,输出"foo:0x95cc008 1000"
delete pa;
}
---------------------------------------------------
例子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,如果前面的函数不加&,返回的只是拷贝,输出为1。
return 0;
}
例子3:学生与老师
#include <iostream>
using namespace std;
class Student; //因为在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);//将问题question和Teacher类变量的地址作为参数传递给Student类中的ask成员函数,并在ask函数中得到一个值作为参数传递给Teacher类中的replay函数,将值赋给m_answer,最后完成输出。
cout << "答案:" << m_answer << endl;
}
int main (void) {
Teacher t;
Student s;
t.educate (&s);
return 0;
}
---------------------------------------------
六、常函数与常对象
1.如果在一个类的成员函数的参数表后面加上const关键字,那么这个成员函数就被成为常函数,常函数的this指针是一个常指针。在常函数内部无法修改成员变量,除非该变量具有mutable属性。而且在常函数内部也无法调用非常函数。
2.常对象:拥有const属性的对象,兑现引用或指针。
常对象只能调用常函数。
同型的常函数和非常函数可以构成重载关系。常对象调用常版本,非常对象调用非常版本。如果没有非常版本,非常对象也可以调用常版本。
const XXX 函数名 (const YYY yyy)
const {
...
}
例子:
#include <iostream>
using namespace std;
class A {
public:
//void bar (void) { //函数1
//cout << "非常bar" << endl;
//}
//函数1与函数2构成函数重载
void bar (void) const { //函数2
cout << "常bar" << endl;
}
// void XXXbarYYY (A* this) {}
void foo (void) const { //函数3
//m_i = 100; //在常函数内部无法修改成员变量,除非那个成员变量有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
a.print ();//"100"
const A& r = a;//r为常对象,a为非常对象
r.bar ();//"常bar",r为常对象,常对象只能调用常函数
//XXXbarYYY (&r); // const A*
a.bar ();//"常bar",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;
}
private:
double* m_data;
string m_lable;
};
int main (void) {
//{
Double d1 (3.14);
d1.print ();//"构造,3.14"(d1调用的构造)
//}
Double* d2 = new Double (1.23);//"构造"(d2调用的构造)
delete d2; //"析构"(d2调用的析构)
cout << "再见!" << endl;//"再见"
return 0; //"析构",(程序结束,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;
}
//拷贝构造(自己定义的):
Integer (const Integer& that) :
m_data (that.m_data) {
cout << "拷贝构造" << endl;
}
private:
int m_data;
};
void foo (Integer i) { //用Inerger类变量时实参给函数中Interger类的形参赋值,同样会调用拷贝构造函数
i.print ();
}
Integer bar (void) {
Integer i;
return i;
}
int main (void) {
Integer i1 (10);
i1.print ();//正常创建对象,输出"10"
Integer i2 (i1); // 调用拷贝构造,输出"拷贝构造"
i2.print (); //调用print函数,输出"10"
Integer i3 = i1; // 调用拷贝构造,输出"拷贝构造"
i3.print (); //调用print函数,输出"10"
//Integer i4 (10, 20);
cout << "调用foo()函数" << endl;
foo (i1); //调用拷贝构造函数,且调用print函数输出,所以输出为"拷贝构造 10"
cout << "调用bar()函数" << endl;
Integer i4 (bar ());
return 0;
}
---------------------------------------------------
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;
}
//拷贝赋值运算符函数(运算符重载)
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) {
Integer i1 (10);
i1.print ();
Integer i2 (i1);
i2.print ();
i2.set (20);
i2.print ();
i1.print ();
Integer i3 (30);
i3.print (); // 30
i3 = i1; // 拷贝赋值
// i3.operator= (i1);
i3.print (); // 10
i3.set (40);
i3.print (); // 40
i1.print (); // 10
/*
int a = 10, b = 20, c = 30;
(a = b) = c;
cout << a << endl;
*/
(i3 = i1) = i2;
//i3.operator=(i1).operator=(i2);
i3.print ();
i3 = i3;
i3.print ();
return 0;
}
练习:实现一个简化版的字符串类String支持:
1.用字符指针形式的字符串构造
2.拷贝构造和拷贝赋值
3.获取字符指针形式字符串的成员函数,类似string::c_str
说明:不得使用std::string!
练习答案:
#include <iostream>
#include <cstring>
using namespace std;
class String {
public:
String (const char* str = NULL) { //构造函数
m_str = new char[strlen(str?str:"")+1];
strcpy (m_str, str ? str : "");
}
~String (void) { //析构函数
if (m_str) {
delete[] m_str;
m_str = NULL;
}
}
String (const String& that) : //拷贝构造函数
m_str (strcpy (
new char[strlen(that.m_str)+1],
that.m_str)) {}
/* 菜鸟
void operator= (const String& that) {
m_str = new char[strlen(that.m_str)+1];
strcpy (m_str, that.m_str);
}*/
String& operator= (const String& that) {
if (&that != this) {
/* 小鸟
delete[] m_str;
m_str = new char[strlen(that.m_str)+1];
strcpy (m_str, that.m_str);
*/
/* 大鸟
char* str =
new char[strlen(that.m_str)+1];
delete[] m_str;
m_str = strcpy (str, that.m_str);
*/
// 老鸟
String temp (that);
swap (m_str, temp.m_str);
}
return *this;
}
const char* c_str (void) const {
return m_str;
}
private:
char* m_str;
};
int main (void) {
String s1 ("Hello, World !");
cout << s1.c_str () << endl;
String s2 = s1;
cout << s2.c_str () << endl;
String s3 ("Hello, Linux !");
try {
s1 = s3;
}
catch (exception& ex) {
cout << ex.what () << endl;
}
cout << s1.c_str () << endl;
return 0; }
九、静态成员
1.静态成员变量和静态成员函数是属于类的而非属于对象。
2.静态成员变量,为多个对象所共享,只有一份实例,可以通过对象访问也可以通过类访问,必须在类的外部定义并初始化。静态成员变量本质上与全局变量并没有区别,只是多了类作用域的约束,和访控属性的限制。
class Account {
private:
string m_name;
double m_balance;
static double m_rate;
};
3.静态成员函数,没有this指针,无法访问非静态成员。
4.单例模式
例子0:
#include <iostream>
using namespace std;
class A {
public:
static int m_i;
static void foo (void) {
cout << "foo:" << m_i << endl;
//m_d = 3.14;//报错,静态成员函数不能访问非静态成员成员
//bar (); //报错,理由同上
}
double m_d;
void bar (void) {
m_i = 1000;//OK,非静态成员函数可以访问非静态成员,也可以访问静态成员
foo (); //OK,理由同上
}
};
int A::m_i = 1;//在外部定义
int main (void) {
A::m_i = 10;//通过类访问静态成员变量
A a1, a2;
cout << ++a1.m_i << endl;//通过对象访问静态成员变量,输出为"11"
cout << a2.m_i << endl; //因为静态成员变量,为多个对象共享,只有一个实例,所以上面a1将m_i修改为11,则通过a2访问的m_i也是11,输出为"11"
A::foo ();//输出为"foo:11",通过类访问静态成员函数
a1.foo ();//输出为"foo:11",通过对象访问静态成员函数
a1.bar ();//先调用bar,将m_i修改为1000,再调用foo,输出为"foo:1000"
return 0;
}
---------------------------------------------------
标签: #mfc office