C++入门应该注意的问题(默认成员函数)

C++类和对象中的默认成员函数问题

默认成员函数一共有6个,本文就讲重点的四个,取地址重载就不说了,没用过,也不实用。

构造函数

首先,构造函数:作用就是用来初始化对象(要通过初始化列表的方式)

1. 函数名与类名相同。

2. 无返回值。

3. 对象实例化时编译器自动调用对应的构造函数。

4. 构造函数可以重载。也就是说可以有多种初始化的方式。

这里有两个问题特别容易混淆

问:什么是默认构造函数

答:我们自己不写,编译器自动生成的;我们自己写的无参的构造函数;我们自己写的全缺省的构造函数。一共三个,总结一下,就是不用参数就可以调用的构造函数。

一般情况,建议一个类最好给一个全缺省的构造函数。

再问:我们不写,编译器自动生成的构造函数有什么价值?
答:默认生成构造函数对基本类型的成员变量是不处理的,对于自定义类型,默认生成构造函数只能调用它的默认构造函数

后来,C++11对于上面这个基本类型不处理的小缺陷,给出了补充语法:声明缺省值,注意这里不是初始化!

class B
{
public:
	B()
		:_b(0)
		{}
private:
	int _b;
};

class A
{

private:
	// 默认生成构造函数对基本类型成员变量是不处理的。
	// 基本类型不处理,是C++语法设计的一个小缺陷
	// 所以C++11,给出了补充语法:声明缺省值,来补足这里的缺陷
	int _a1 = 1; // 这里不是初始化,这里是给缺省值
	int _a2 = 2;

	// 默认生成构造函数对自动类型的成员回去调用它的默认构造函数初始化
	B _b ;
};	

int main()
{
   A aa;
   return 0;
}

关于初始化列表这个问题也还是比较容易混淆,下面代码的第三句注释非常重要

class B
{
public:
	/*B()
		:_b(0)
		{}
		*/
	B(int b = 0)
		:_b(b)
	{}
private:
	int _b;
};

class D
{
public:
	// D的构造函数中,在哪里调用的B的默认构造函数呢?
	// 对象定义的时候自动调用构造函数,
    // 调用构造函数的时候必须走一遍初始化列表
	// 构造函数的初始化列表可以认为是成员变量定义初始化的地方
	// 初始化列表,你显式的写或者不写,都会走一遍
	D()
	{}
    //这里就去调用了B的默认构造函数

private:
	int _d = 0;    // 成员声明
	B	_b;
};

int main()
{
    A aa;
	D d;   // 对象定义的时候,要调用它的构造函数来初始化
	return 0;
}

同时还有几种类型必须在初始化列表初始化:const、引用、没有默认构造函数的成员

析构函数

这个比较简单

析构函数:与构造函数功能相反,析构函数不是完成对象的销毁,局部对象销毁工作是由编译器完成的。而 对象在销毁时会自动调用析构函数,完成类的一些资源清理工作。

1. 析构函数名是在类名前加上字符 ~。

2. 无参数无返回值。

3. 一个类有且只有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。

4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。

看下面代码的注释来理解吧,还是比较简单的

class Date
{
public:
	//~Date()
	//{
	//	 // ... 这个类没有什么资源需要清理,
	//	 // 所以其实它不写析构函数,编译器自动生成就可以
	//}
private:
	int _year = 0;
	int _month = 1;
	int _day = 1;

	B _b = 10;
};

namespace yzy
{
	class string
	{
	public:
		string(const char* str)	
		{
            _str=new char[strlen(str) + 1];
			strcpy(_str, str);
		}

		~string()
		{
			cout << "~string()" << endl;
			delete[] _str;
			_str = nullptr;
		}
	private:
		char* _str;
	};
}

int main()
{
	Date d;  // 析构函数不是完成对象的销毁
			 // 这个对象是存在函数栈帧里面的,函数结束,栈帧销毁,对象就销毁了

	// yzy::string s1("hello  world"); 
	// 针对s1有两块空间要销毁
	// 第一:s1对象本身,他是函数结束,栈帧销毁,他就销毁
	// 第二:s1里面的_str指向的堆上的空间,他是析构函数清理的

}

 注意:成员变量有指针就要写析构函数这句话是错的!

默认生成的析构函数并不是什么事情都不做,内置类型不处理,自定义类型会去调用它的析构函数

拷贝构造函数

1. 拷贝构造函数是构造函数的一个重载形式。

2. 拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用。

先看下面这段代码,我们没有写拷贝构造函数,

class Date
{
public:

private:
	int _year = 0;
	int _month = 1;
	int _day = 1;
};
int main()
{
	// 浅拷贝(值拷贝)的类
	Date d1;
	Date d2(d1);
	Date d3 = d1;

	return 0;
}

所以可以得出的结论是:我们不写,编译器默认生成拷贝构造函数,会对基本类型完成值拷贝(浅拷贝),对于自定义类型成员,会去调它的拷贝构造

对于string这种类,内含的自定义类型_str必须由我们自己来写默认拷贝构造函数,否则就会犯一块空间被析构两次的经典错误,因为不写的话就是浅拷贝,比如string s2(s1)这种语句,s2会和s1指向同一块空间。

下面的代码就写了自定义类型的拷贝构造函数,也就是自己实现了string的深拷贝

namespace yzy
{
	class string
	{
	public:
		string(const char* str="")	
           : _str(new char[strlen(str) + 1]);
		{
            
			strcpy(_str, str);
		}

		string(const char& s)	
           : _str(new char[strlen(s._str) + 1]);
		{
            
			strcpy(_str, s._str);
		}


		~string()
		{
			cout << "~string()" << endl;
			delete[] _str;
			_str = nullptr;
		}
	private:
		char* _str;
	};
}

赋值运算符的重载

赋值运算符的重载是已经定义出来的对象的赋值拷贝

默认生成的赋值运算符,特性和拷贝构造是一致的,也就是内置类型默认生成的operator=,会完成浅拷贝,自定义类型会调用它的operator赋值,比如string类中的_str,date类中的_b

下面就是string类的赋值运算符重载函数,比如执行s1=s3,思路就是要先释放掉s1对应的空间,再重新开一块比s3大1的空间,然后再strcpy

		string& operator=(const string& s)
		{
			if (this != &s)
			{
				delete[] _str;
				_str = new char[strlen(s._str) + 1];
				strcpy(_str, s._str);
			}

			return *this;
		}

最后总结,其实上文并没有提这些函数的写法,比如引用啊,各种参数啊,包括this指针,下次可以专门更一期this指针加写法的博客

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
这是DS小龙哥编写整理的C++入门指南PDF文档,适合C++初学者,C语言转C++工程师当做入门工具书学习。PDF里有完整示例、知识讲解,平时开发都可以复制粘贴,非常便捷。 目前一共写了7章,后续会持续更新资源包,更新后重新下载即可。 这是目前书籍的目录: C++入门指南 1 一、 C++语言基本介绍与开发环境搭建 1 1.1 C++简介 1 1.2 面向对象编程 1 1.3 Windows系统下搭建C++学习环境 2 二、C++基础入门 16 2.1 C++类和对象 17 2.2 C++命名空间 18 2.3 std标准命名空间 20 2.4 C++新增的标准输入输出方法(cin和cout) 22 2.5 C++规定的变量定义位置 24 2.6 C++新增的布尔类型(bool) 24 2.7 C++ 新增的new和delete运算符 25 2.8 C++函数默认参数(缺省参数) 26 2.9 C++函数重载详解 28 2.10 C++新增的引用语法 30 三、 C++面向对象:类和对象 34 3.1 类的定义和对象的创建 34 3.2 类的成员变量和成员函数 36 3.3 类成员的访问权限以及类的封装 38 3.4 C++类的构造函数与析构函数 39 3.5 对象数组 47 3.6 this指针 50 3.7 static静态成员变量 52 3.8 static静态成员函数 53 3.9 const成员变量和成员函数 55 3.10 const对象(常对象) 56 3.11 友元函数和友元类 58 3.11.3 友元类 61 3.12 C++字符串 62 四、C++面向对象:继承与派生 75 4.1 继承与派生概念介绍 75 4.2 继承的语法介绍 75 4.3 继承方式介绍(继承的权限) 76 4.4 继承时变量与函数名字遮蔽问题 79 4.5 基类和派生类的构造函数 82 4.6 基类和派生类的析构函数 83 4.7 多继承 85 4.8 虚继承和虚基类 88 五、C++多态与抽象类 91 5.1 多态概念介绍 91 5.2 虚函数 92 5.3 纯虚函数和抽象类 95 六、C++运算符重载 97 6.1 运算符重载语法介绍 97 6.2 可重载运算符与不可重载运算符 98 6.3 一元运算符重载 99 6.4 二元运算符重载 102 6.5 关系运算符重载 104 6.6 输入/输出运算符重载(>>、<<) 105 6.7 函数调用运算符 () 重载 106 6.8 重载[ ](下标运算符) 107 七、C++模板和泛型程序设计 108 7.1 函数模板 108 7.2 类模板 110
Visual C++ 2005入门经典.pdf(整理并添加所有书签) ,看书的时候更方便. 封面 目录 第1章 使用Visual C++ 2005 编程 1.1 .NET Framework 1.2 CLR 1.3 编写C++应用程序 1.4 学习windows编程 1.4.1 学习c++ 1.4.2 C++标准 1.4.3 控制台应用程序 1.4.4 Windows编程概念 1.5 集成开发环境简介 1.6 使用IDE 1.6.1 工具栏选项 1.6.2 可停靠的工具栏 1.6.3 文档 1.6.4 项目和解决方案 1.6.5 设置Visual C++2005的选项 1.6.6 创建和执行windows应用程序 1.6.7 创建windows Forms应用程序 1.7 小结 第2章 数据、变量和计算 2.1 C++程序结构 2.1.1 程序注释 2.1.2 #include指令——头文件 2.1.3 命名空间和using声明 2.1.4 main()函数 2.1.5 程序语句 2.1.6 空白 2.1.7 语句块 2.1.8 自动生成的控制台程序 2.2 定义变量 2.2.1 命名变量 2.2.2 C++中的关键字 2.2.3 声明变量 2.2.4 变量的初值 2.3 基本数据类型 2.3.1 整型变量 2.3.2 字符数据类型 2.3.3 整型修饰符 2.3.4 布尔类型 2.3.5 浮点类型 2.3.6 ISO/ANSI C++中的基本类型 2.3.7 字面值 2.3.8 定义数据类型的同义词 2.3.9 具有特定值集的变量 2.3.10 指定枚举常量的类型 2.4 基本的输入输出操作 2.4.1 从键盘输入 2.4.2 到命令行的输出 2.4.3 格式化输出 2.4.4 转义序列 2.5 C++中的计算 2.5.1 赋值语句 2.5.2 算术运算 2.5.3 计算余数 2.5.4 修改变量 2.5.5 增量和减量运算符 2.5.6 计算的顺序 2.6 变量类型和类型强制转换 2.6.1 对操作数进行类型强制转换的规则 2.6.2 赋值语句中的类型强制转换 2.6.3 显式类型强制转换 2.6.4 老式的类型强制转换 2.6.5 按位运算符 2.7 了解存储时间和作用域 2.7.1 自动变量 2.7.2 决定变量声明的位置 2.7.3 全局娈量 2.7.4 静态变量 2.8 命名空间 2.8.1 声明命名空间 2.8.2 多个命名空间 2.9 C++/CLI编程 2.9.1 C++/CLI特有的基本数据类型 2.9.2 命令行上的C++/CLI输出 2.9.3 C++/CLI特有的功能——格式化输出 2.9.4 C++/CLI的键盘输入 2.9.5 使用safe cast 2.9.6 C++/CLI枚举 2.10 小结 2.11 练习题 第3章 判断和循环 3.1 比较数据值 3.1.1 if语句 3.1.2 嵌套if语句 3.1.3 扩展的if语句 3.1.4 嵌套的if-else语句 3.1.5 逻辑运算符和表达式 3.1.6 条件运算符 3.1.7 switch语句 3.1.8 无条件转移 3.2 重复执行语句块 3.2.1 循环的概念 3.2.2 for循环的变体 3.2.3 while循环 3.2.4 do-while循环 3.2.5 嵌套的循环 3.3 C++/CLI编程 3.4 小结 3.5 练习 第4章 数组、字符串和指针 4.1 处理多个相同类型的数据值 4.1.1 数组 4.1.2 声明数组 4.1.3 初始化数组 4.1.4 字符数组和字符串处理 4.1.5 多维数组 4.2 间接数据存取 4.2.1 指针的概念 4.2.2 声明指针 4.2.3 使用指针 4.2.4 初始化指针 4.2.5 sizeof运算符 4.2.6 常量指针和指向常量的指针 4.2.7指针和数组 4.3 动态内存分配 4.3.1 堆的别名——自由存储器 4.3.2 new和delete运算符 4.3.3 为数组动态分配内存 4.3.4 多维数组的动态分配 4.4 使用引用 4.4.1 引用的概念 4.4.2 声明并初始化引用 4.5 C++/CLI编程 4.5.1 跟踪句柄 4.5.2 CLR数组 4.5.3 字符串 4.5.4 跟踪引用 4.5.5 内部指针 4.6 小结 4.7 练习 第5章 程序结构(1) 5.1 理解函数 5.1.1 需要函数的原因 5.1.2 函数的结构 5.1.3 使用函数 5.2 给函数传递实参 5.2.1 按值传递机制 5.2.2 给函数传递指针实参 5.2.3 给函数传递数组 5.2.4 给函数传递引用实参 5.2.5 使用const修饰符 5.2.6 main()函数的实参 5.2.7 接受数量不定的函数实参 5.3 从函数返回值 5.3.1 返回指针 5.3.2 返回引用 5.3.3 函数中的静态变量 5.4 递归函数调用 5.5 C++/CLI编程 5.5.1 接受数量可变实参的函数 5.5.2 main()的实参 5.6 小结 5.7 练习 第6章 程序结构(2) 6.1 函数指针 6.1.1 声明函数指针 6.1.2 函数指针作为实参 6.1.3 函数指针的数组 6.2 初始化函数形参 6.3 异常 6.3.1 抛出异常 6.3.2 捕获异常 6.3.3 MFC中的异常处理 6.4 处理内存分配错误 6.5 函数重载 6.5.1 函数重载的概念 6.5.2 何时重载函数 6.6 函数模板 6.7 使用函数的示例 6.7.1 实现计算器 6.7.2 从字符串中删除空格 6.7.3 计算表达式的值 6.7.4 获得项值 6.7.5 分析数 6.7.6 整合程序 6.7.7 扩展程序 6.7.8 提取子字符串 6.7.9 运行修改过的程序 6.8 C++/CLI编程 6.8.1 理解类函数 6.8.2 CLR版本的计算器程序 6.9 小结 6.10 练习 第7章 自定义数据类型 7.1 C++中的结构 7.1.1 结构的概念 7.1.2 定义结构 7.1.3 初始化结构 7.1.4 访问结构的成员 7.1.5 伴随结构的智能帮助 7.1.6 RECT结构 7.1.7 使用指针处理结构 7.2 数据类型、对象、类和实例 7.2.1 类的起源 7.2.2 类的操作 7.2.3 术语 7.3 理解类 7.3.1 定义类 7.3.2 声明类的对象 7.3.3 访问类的数据成员 7.3.4 类的成员函数 7.3.5 成员函数定义的位置 7.3.6 内联函数 7.4 类构造函数 7.4.1 构造函数的概念 7.4.2 默认的构造函数 7 4.3 在类定义中指定默认的形参值 7.4.4 在构造函数中使用初始化列表 7.5 类的私有成员 7.5.1 访问私有类成员 7.5.2 类的友元函数 7.5.3 默认复制构造函数 7.6 this指针 7.7 类的const对象 7.7.1 类的const成员函数 7.7.2 类外部的成员函数定义 7.8 类对象的数组 7.9 类的静态成员 7.9.1 类的静态数据成员 7.9.2 类的静态函数成员 7.10 类对象的指针和引用 7.10.1 类对象的指针 7.10.2 类对象的引用 7.11 C++/CLI编程 7.11.1 定义数值类类型 7.11.2 定义引用类类型 7.11.3 类属性 7.11.4 initonly字段 7.11.5 静态构造函数 7.12 小结 7.13 练习 第8章 深入理解类 8.1 类的析构函数 8.1.1 析构函数的概念 8.1.2 默认的析构函数 8.1.3 析构函数与动态内存分配 8.2 实现复制构造函数 8.3 在变量之间共享内存 8.3.1 定义联台 8.3.2 匿名联合 8.3.3 类和结构中的联合 8.4 运算符重载 8.4.1 实现重载的运算符 8.4.2 实现对运算符的完全支持 8.4.3 重载赋值运算符 8.4.4 重载加法运算符 8.4.5 重载递增和递减运算符 8.5 类模板 8.5.1 定义类模板 8.5.2 根据类模板创建对象 8.5.3 使用有多个形参的类模板 8.6 使用类 8.6.1 类接口的概念 8.6.2 定义问题 8.6.3 实现CBox类 8.6.4 定义CBox类 8.6.5 使用CBox类 8.7 组织程序代码 8.8 C++/CLI编程 8.8.1 在数值类中重载运算符 8.8.2 重载递增和递减运算符 8.8.3 在引用类中重载运算符 8.9 小结 8.1O 练习 第9章 类继承和虚函数 9.1 面向对象编程的基本思想 9.2 类的继承 9.2.1 基类的概念 9.2.2 基类的派生类 9.3 继承机制下的访问控制 9.3.1 派生类中构造函数的操作 9.3.2 声明类的保护成员 9.3.3 继承类成员的访问级别 9.4 派生类中的复制构造函数 9.5 友元类成员 9.5.1 友元类 9.5.2 对类友元关系的限制 9.6 虚函数 9.6.1 虚函数的概念 9.6.2 使用指向类对象的指针 9.6.3 使用引用处理虚函数 9.6.4 纯虚函数 9.6.5 抽象类 9.6.6 间接基类 9.6.7 虚析构函数 9.7 类类型之间的强制转换 9.8 嵌套类 9.9 C++/CLI编程 9.9.1 C++/CLI类的继承 9.9.2 接口类 9.9.3 定义接口类 9.9.4 类和程序集 9.9.5 被指定为new的函数 9.9.6 委托和事件 9.9.7 引用类的析构函数和结束函数 9.9.8 通用类 9.10 小结 9.11 练习 第10章 调试技术 10.1 理解调试 10.1.1 程序故障 10.1.2 常见故障 10.2 基本的调试操作 10.2.1 设置断点 10.2.2 设置跟踪点 10.2.3 启动调试模式 10.2.4 修改变量的值 10.3 添加调试代码 10.3.1 使用断言 10.3.2 添加自己的调试代码 10.4 调试程序 10.4.1 调用堆栈 10.4.2 单步执行到出错位置 10.5 测试扩展的类 10.6 调试动态内存 10.6.1 检查自由存储器的函数 10.6.2 控制自由存储器的调试操作 10.6.3 自由存储器的调试输出 10.7 调试C++/CLI程序 10.8 小结 第11章 Windows编程的概念 11.1 Windows编程基础 11.1.1窗口的元素 11.1.2 Windows程序与操作系统 11.1.3事件驱动型程序 11.1.4 Windows消息 11.1.5 WindowsAPI 11.1.6.Windows数据类型 11.1.7 Windows程序中的符号 11.2 Windows程序的结构 11.2.1 WinMain()函数 11.2.2消息处理函数 11.2.3简单的Windows程序 11.3 Windows程序的组织 11.4 MFC 11.4.1 MFC标记法 11.4.2 MFC程序的组织方式 11.5使用Windows Forms 11.6小结 第12章 使用MFC编写Windows程序 12.1 MFC的文档,视图概念 12.1.1 文档的概念 12.1.2 文档界面 12.1.3 视图的概念 12.1.4 连接文档和视图 12.1.5 应用程序和MFC 12.2 创建MFC应用程序 12.2.1 创建SDI应用程序 12.2.2 MFCApplicationwizard的输出 12.2.3 创建MDI应用程序 12.3 小结 12.4 练习 第13章 处理菜单和工具栏 13.1 与Windows进行通信 13.1.1 了解消息映射 13.1.2 消息类别 13.1.3 处理程序中的消息 13.2 扩充Sketcher程序 13.3 菜单的元素 13.4 为菜单消息添加处理程序 13.4.1 选择处理菜单消息的类 13.4.2 创建菜单消息函数 13.4.3 编写菜单消息函数的代码 13.4.4 添加更新用户界面的消息处理程序 13.5 添加工具栏按钮 13.5.1 编辑工具栏按钮的属性 13.5.2 练习使用工具栏按钮 13.5.3 添加工具提示 13.6 小结 13.7 练习题 第14章 在窗口中绘图 14.1 窗口绘图的基础知识 14.1.1 窗口客户区 14.1.2 Windows图形设备界面 14.2 Visual C++中的绘图机制 14.2.1 应用程序中的视图类 14.2.2 CDC类 14.3 实际绘制图形 14.4 对鼠标进行编程 14.4.1 鼠标发出的消息 14.4.2 鼠标消息处理程序 14.4.3 使用鼠标绘图 14.5 练习使用Sketcher程序 14.5.1 运行这个示例 14.5.2 捕获鼠标消息 14.6 小结 14.7 练习题 第15章 创建文档和改进视图 15.1 什么是集合类 15.1.1 集合的类型 15.1.2 类型安全的集合类 15.1.3 对象集合 15.1.4 类型化指针集合 15.2 使用CList模板类 15.2.1 绘制曲线 15.2.2 定义CCurve类 15.2.3 实现CCurve类 15.2.4 练习使用CCurve类 15.3 创建文档 15.4 改进视图 15.4.1 更新多个视图 15.4.2 滚动视图 15.4.3 使用MM_LOENGLISH映射模式 15.5 删除和移动形状 15.6 实现上下文菜单 15.6.1 关联菜单和类 15.6.2 选择上下文菜单 15.6.3 醒目显示元素 15.6.4 处理菜单消息 15.7 处理被屏蔽的元素 15.8 小结 15.9 练习 第16章 使用对话框和控件 16.1 理解对话框 16.2 理解控件 16.3 创建对话框资源 16.4 对话框的编程 16.4.1 添加对话框类 16.4.2 模态和非模态对话框 16.4.3 显示对话框 16.5 支持对话框控件 16.5.1 初始化控件 16.5.2 处理单选按钮消息 16.6 完成对话框的操作 16.6.1 给文档类添加存储线宽的成员 16.6.2 给元素添加线宽 16.6.3 在视图中创建元素 16.6.4 练习使用对话框 16.7 使用微调按钮控件 16.7.1 添加Scale菜单项和工具栏按钮 16.7.2 创建微调按钮 16.7.3 生成比例对话框类 16.7.4 显示微调按钮 16.8 使用比例系数 16.8.1 可缩放的映射模式 16.8.2 设置文档的大小 16.8.3 设置映射模式 16.8.4 同时实现滚动与缩放 16.9 使用状态栏 16.10 使用列表框 16.10.1 删除比例对话框 16.1O.2 创建列表框控件 16.11 使用编辑框控件 16.11.1 创建编辑框资源 16.11.2 创建对话框类 16.11.3 添加Text菜单项 16.11.4 定义文本元素 16.11.5 实现CText类 16.11.6 创建文本元素 16.12 小结 16.13 练习 第17章 存储和打印文档 17.1 了解串行化 17.2 串行化文档 17.2.1 文档类定义中的串行化 17.2.2 丈档类实现中的串行化 17.2.3 基于CObject的类的功能 17.2.4 串行化的工作方式 17.2.5 如何实现类的串行化 17.3 应用串行化 17.3.1 记录文档修改 17.3.2 串行化文档 17.3.3 串行化元素类 17.4 练习串行化 17.5 移动文本 17.6 打印文档 17.7 实现多页打印 17.7.1 获取文档的总尺寸 17.7.2 存储打印数据 17.7.3 准备打印 17.7.4 打印后的清除 17.7.5 准备设备上下文 17.7.6 打印文档 17.7.7 获得文档的打印输出 17.8 小结 17.9 练习题 第18章 编写自己的DLL 18.1 了解DLL 18.1.1 DLL的工作方式 18.1.2 DLL的内容 18.1.3 DLL变体 18.2 决定放入DLL的内容 18.3 编写DLL 18.3.1 编写和使用扩展DLL 18.3.2 从DLL中导出变量和函数 18.3.3 将符号导入程序 18.3.4 实现符号从DLL的导出 18.4 小结 18.5 练习题 第19章 连接到数据源 19.1 数据库基础知识 19.2 SQL 19.2.1 使用SQL检索数据 19.2.2 使用SQL连接表 19.2.3 对记录进行排序 19.3 MFC中的数据库支持 19.4 创建数据库应用程序 19.4.1 注册ODBC数据库 19.4.2 生成MFC ODBC程序 19.4.3 了解程序结构 19.4.4 示例练习 19.5 对记录集进行排序 19.6 使用另一个记录集对象 19.6.1 添加记录集类 19.6.2 添加记录集的视图类 19.6.3 定制记录集 19.6.4 访问多个表视图 19.6.5 查看产品的订单 19.7 查看客户的详细情况 19.7.1 添加客户记录集 19.7.2 创建客户对话框资源 19.7.3 创建客户视图类 19.7.4 添加过滤器 19.7.5 实现过滤器参数 19.7.6 链接订单对话框和客户对话框 19.7.7 练习使用数据库查看器 19.8 小结 19.9 练习题 第20章 更新数据源 20.1 更新操作 20.1.1 CRecordset更新操作 20.1.2 事务 20.2 简单的更新示例 20.3 管理更新过程 20.4 向表中添加行 20.4.1 订单录入过程 20.4.2 创建资源 20.4.3 创建记录集 20.4.4 创建记录集视图 20.4.5 给对话框资源添加控件 20.4.6 实现对话框切换 20.4.7 创建订单ID 20.4.8 存储订单数据 20.4.9 为订单选择产品 20.4.10 添加新订单 20.5 小结 20.6 练习 第21章 使用Windows Forms的应用程序 21.1 理解Wqndows Forms 21.2 理解Windows Forms应用程序 21.2.1 修改窗体的属性 21.2.2 如何启动应用程序 21.3 定制应用程序GUI 21.3.1 给窗体添加控件 21.3.2 添加选项卡控件 21.3.3 使用CroupBox控件 21.3.4 使用Button控件 21.3.5 使用WebBrowser控件 21.3.6 Winning应用程序的操作 21.3.7 添加上下文菜单 21.3.8 创建事件处理程序 21.3.9 处理Limits菜单的事件 21.3.10 创建对话框 21.3.11 使用对话框 21.3.12 添加第二个对话框 21.3.13 实现Help|About菜单项 21.3.14 处理按钮单击事件 21.3.15 响应上下文莱单 21.4 小结 21.5 练习 第22章 在Windows Forms应用程序中访问数据源 22.1 使用数据源 22.2 访问并显示数据 22.3 使用DataGridView控件 22.4 在无约束模式中使用DataGridView控件 22.5 定制DataGridView控件 22.5.1 定制题头单元格 22.5.2 定制非题头单元格 22.5.3 动态设置单元格样式 22.6 使用约束模式 22.7 BindingSource组件 22.8 使用BindingNavigator控件 22.9 绑定到单独的控件 22.10 使用多个表 22.11 小结 22.12 练习 附录A C++关键字 A1 ISO/ANSI C++关键宇 A2 C++/CLI关键字 附录B ASCII码
主体:(一) 一、C++概述 (一) 发展历史 1980年,Bjarne Stroustrup博士开始着手创建一种模拟语言,能够具有面向对象的程序设计特色。在当时,面向对象编程还是一个比较新的理念,Stroustrup博士并不是从头开始设计新语言,而是在C语言的基础上进行创建。这就是C++语言。 1985年,C++开始在外面慢慢流行。经过多年的发展,C++已经有了多个版本。为次,ANSI和ISO的联合委员会于1989年着手为C++制定标准。1994年2月,该委员会出版了第一份非正式草案,1998年正式推出了C++的国际标准。 (二) C和C++ C++是C的超集,也可以说C是C++的子集,因为C先出现。按常理说,C++编译器能够编译任何C程序,但是C和C++还是有一些小差别。 例如C++增加了C不具有的关键字。这些关键字能作为函数和变量的标识符在C程序中使用,尽管C++包含了所有的C,但显然没有任何C++编译器能编译这样的C程序。 C程序员可以省略函数原型,而C++不可以,一个不带参数的C函数原型必须把void写出来。而C++可以使用空参数列表。 C++中new和delete是对内存分配的运算符,取代了C中的malloc和free。 标准C++中的字符串类取代了C标准C函数库头文件中的字符数组处理函数。 C++中用来做控制态输入输出的iostream类库替代了标准C中的stdio函数库。 C++中的try/catch/throw异常处理机制取代了标准C中的setjmp()和longjmp()函数。 二、关键字和变量 C++相对与C增加了一些关键字,如下: typename bool dynamic_cast mutable namespace static_cast using catch explicit new virtual operator false private template volatile const protected this wchar_t const_cast public throw friend true reinterpret_cast try bitor xor_e and_eq compl or_eq not_eq bitand 在C++中还增加了bool型变量和wchar_t型变量: 布尔型变量是有两种逻辑状态的变量,它包含两个值:真和假。如果在表达式中使用了布尔型变量,那么将根据变量值的真假而赋予整型值1或0。要把一个整型变量转换成布尔型变量,如果整型值为0,则其布尔型值为假;反之如果整型值为非0,则其布尔型值为真。布儿型变量在运行时通常用做标志,比如进行逻辑测试以改变程序流程。 #include iostream.h int main() { bool flag; flag=true; if(flag) cout<<true<<endl; return 0; } C++中还包括wchar_t数据类型,wchar_t也是字符类型,但是是那些宽度超过8位的数据类型。许多外文字符集所含的数目超过256个,char字符类型无法完全囊括。wchar_t数据类型一般为16位。 标准C++的iostream类库中包括了可以支持宽字符的类和对象。用wout替代cout即可。 #include iostream.h int main() { wchar_t wc; wc='b'; wout<<wc; wc='y'; wout<<wc; wc='e'; wout<<wc<<endl; return 0; } 说明一下:某些编译器无法编译该程序(不支持该数据类型)。 三、强制类型转换 有时候,根据表达式的需要,某个数据需要被当成另外的数据类型来处理,这时,就需要强制编译器把变量或常数由声明时的类型转换成需要的类型。为此,就要使用强制类型转换说明,格式如下: int* iptr=(int*) &table; 表达式的前缀(int*)就是传统C风格的强制类型转换说明(typecast),又可称为强制转换说明(cast)。强制转换说明告诉编译器把表达式转换成指定的类型。有些情况下强制转换是禁用的,例如不能把一个结构类型转换成其他任何类型。数字类型和数字类型、指针和指针之间可以相互转换。当然,数字类型和指针类型也可以相互转换,但通常认为这样做是不安全而且也是没必要的。强制类型转换可以避免编译器的警告。 long int el=123; short i=(int) el; float m=34.56; int i=(int) m; 上面两个都是C风格的强制类型转换,C++还增加了一种转换方式,比较一下上面和下面这个书写方式的不同: long int el=123; short i=int (el); float m=34.56; int i=int (m); 使用强制类型转换的最大好处就是:禁止编译器对你故意去做的事发出警告。但是,利用强制类型转换说明使得编译器的类型检查机制失效,这不是明智的选择。通常,是不提倡进行强制类型转换的。除非不可避免,如要调用malloc()函数时要用的void型指针转换成指定类型指针。 四、标准输入输出流 在C语言中,输入输出是使用语句scanf()和printf()来实现的,而C++中是使用类来实现的。 #include iostream.h main() //C++中main()函数默认为int型,而C语言中默认为void型。 { int a; cout<>a; /*输入一个数值*/ cout<<a<<endl; //输出并回车换行 return 0; } cin,cout,endl对象,他们本身并不是C++语言的组成部分。虽然他们已经是ANSI标准C++中被定义,但是他们不是语言的内在组成部分。在C++中不提供内在的输入输出运算符,这与其他语言是不同的。输入和输出是通过C++类来实现的,cin和cout是这些类的实例,他们是在C++语言的外部实现。 在C++语言中,有了一种新的注释方法,就是‘//’,在该行//后的所有说明都被编译器认为是注释,这种注释不能换行。C++中仍然保留了传统C语言的注释风格/*……*/。 C++也可采用格式化输出的方法: #include iostream.h int main() { int a; cout<>a; cout<<dec<<a<<' ' //输出十进制数 <<oct<<a<<' ' //输出八进制数 <<hex<<a<<endl; //输出十六进制数 return 0; } 从上面也可以看出,dec,oct,hex也不可作为变量的标识符在程序中出现。 五、函数参数问题 (一) 无名的函数形参 声明函数时可以包含一个或多个用不到的形式参数。这种情况多出现在用一个通用的函数指针调用多个函数的场合,其中有些函数不需要函数指针声明中的所有参数。看下面的例子: int fun(int x,int y) { return x*2; } 尽管这样的用法是正确的,但大多数C和C++的编译器都会给出一个警告,说参数y在程序中没有被用到。为了避免这样的警告,C++允许声明一个无名形参,以告诉编译器存在该参数,且调用者需要为其传递一个实际参数,但是函数不会用到这个参数。下面给出使用了无名参数的C++函数代码: int fun(int x,int) //注意不同点 { return x*2; } (二) 函数默认参数 C++函数的原型中可以声明一个或多个带有默认值的参数。如果调用函数时,省略了相应的实际参数,那么编译器就会把默认值作为实际参数。可以这样来声明具有默认参数的C++函数原型: #include iostream.h void show(int=1,float=2.3,long=6); int main() { show(); show(2); show(4,5.6); show(8,12.34,50L); return 0; } void show(int first,float second,long third) { cout<<first=<<first <<second=<<second <<third=<<third<<endl; } 上面例子中,第一次调用show()函数时,让编译器自动提供函数原型中指定的所有默认参数,第二次调用提供了第一个参数,而让编译器提供剩下的两个,第三次调用则提供了前面两个参数,编译器只需提供最后一个,最后一个调用则给出了所有三个参数,没有用到默认参数。 六、函数重载 在C++中,允许有相同的函数名,不过它们的参数类型不能完全相同,这样这些函数就可以相互区别开来。而这在C语言中是不允许的。 1.参数个数不同 #include iostream.h void a(int,int); void a(int); int main() { a(5); a(6,7); return 0; } void a(int i) { cout<<i<<endl; //输出5 } void a(int i,int j) { cout<<i<<j<<endl; //输出67 } 2.参数格式不同 #include iostream.h void a(int,int); void a(int,float); int main() { a(5,6); a(6,7.0); return 0; } void a(int i,int j) { cout<<i<<j<<endl; //输出56 } void a(int i,float j) { cout<<i<<j<>a; for(int i=1;i<=10;i++) //C语言中,不允许在这里定义变量 { static int a=0; //C语言中,同一函数块,不允许有同名变量 a+=i; cout<<::a<< <<a<<endl; } return 0; } 八、new和delete运算符 在C++语言中,仍然支持malloc()和free()来分配和释放内存,同时增加了new和delete来管理内存。 1.为固定大小的数组分配内存 #include iostream.h int main() { int *birthday=new int[3]; birthday[0]=6; birthday[1]=24; birthday[2]=1940; cout<<I was born on <<birthday[0]<<'/'<<birthday[1]<<'/'<<birthday[2]<>size; int *array=new int[size]; for(int i=0;i<size;i++) array[i]=rand(); for(i=0;i<size;i++) cout<<'\n'<<array[i]; delete [] array; return 0; } 九、引用型变量 在C++中,引用是一个经常使用的概念。引用型变量是其他变量的一个别名,我们可以认为他们只是名字不相同,其他都是相同的。 1.引用是一个别名 C++中的引用是其他变量的别名。声明一个引用型变量,需要给他一个初始化值,在变量的生存周期内,该值不会改变。& 运算符定义了一个引用型变量: int a; int& b=a; 先声明一个名为a的变量,它还有一个别名b。我们可以认为是一个人,有一个真名,一个外号,以后不管是喊他a还是b,都是叫他这个人。同样,作为变量,以后对这两个标识符操作都会产生相同的效果。 #include iostream.h int main() { int a=123; int& b=a; cout<<a<<','<<b<<endl; //输出123,123 a++; cout<<a<<','<<b<<endl; //输出124,124 b++; cout<<a<<','<<b<等运算符 #include iostream.h void func1(s p); void func2(s& p); struct s { int n; char text[10]; }; int main() { static s str={123,China}; func1(str); func2(str); return 0; } void func1(s p) { cout<<p.n<<endl; cout<<p.text<<endl; } void func2(s& p) { cout<<p.n<<endl; cout<<p.text<<endl; } 从表面上看,这两个函数没有明显区别,不过他们所花的时间却有很大差异,func2()函数所用的时间开销会比func2()函数少很多。它们还有一个差别,如果程序递归func1(),随着递归的深入,会因为栈的耗尽而崩溃,但func2()没有这样的担忧。 4.以引用方式调用 当函数把引用作为参数传递给另一个函数时,被调用函数将直接对参数在调用者中的拷贝进行操作,而不是产生一个局部的拷贝(传递变量本身是这样的)。这就称为以引用方式调用。把参数的值传递到被调用函数内部的拷贝中则称为以传值方式调用。 #include iostream.h void display(const Date&,const char*); void swapper(Date&,Date&); struct Date { int month,day,year; }; int main() { static Date now={2,23,90}; static Date then={9,10,60}; display(now,Now: ); display(then,Then: ); swapper(now,then); display(now,Now: ); display(then,Then: ); return 0; } void swapper(Date& dt1,Date& dt2) { Date save; save=dt1; dt1=dt2; dt2=save; } void display(const Date& dt,const char *s) { cout<<s; cout<<dt.month<<'/'<<dt.day<<'/'<<dt.year<<endl; } 5.以引用作为返回值 #include iostream.h struct Date { int month,day,year; }; Date birthdays[]= { {12,12,60}; {10,25,85}; {5,20,73}; }; const Date& getdate(int n) { return birthdays[n-1]; } int main() { int dt=1; while(dt!=0) { cout<<Enter date # (1-3,0 to quit)<>dt; if(dt>0 && dt<4) { const Date& bd=getdate(dt); cout<<bd.month<<'/'<<bd.day<<'/'<<bd.year<<endl; } } return 0; } 程序都很简单,就不讲解了。 主体:(二)类的设计,构造函数和析构函数 类是编程人员表达自定义数据类型的C++机制。它和C语言中的结构类似,C++类支持数据抽象和面向对象的程序设计,从某种意义上说,也就是数据类型的设计和实现。 一、类的设计 1.类的声明 class 类名 { private: //私有 ... public: //公有 ... }; 2.类的成员 一般在C++类中,所有定义的变量和函数都是类的成员。如果是变量,我们就叫它数据成员如果是函数,我们就叫它成员函数。 3.类成员的可见性 private和public访问控制符决定了成员的可见性。由一个访问控制符设定的可访问状态将一直持续到下一个访问控制符出现,或者类声明的结束。私有成员仅能被同一个类中的成员函数访问,公有成员既可以被同一类中的成员函数访问,也可以被其他已经实例化的类中函数访问。当然,这也有例外的情况,这是以后要讨论的友元函数。 类中默认的数据类型是private,结构中的默认类型是public。一般情况下,变量都作为私有成员出现,函数都作为公有成员出现。 类中还有一种访问控制符protected,叫保护成员,以后再说明。 4.初始化 在声明一个类的对象时,可以用圆括号()包含一个初始化表。 看下面一个例子: #include iostream.h class Box { private: int height,width,depth; //3个私有数据成员 public: Box(int,int,int); ~Box(); int volume(); //成员函数 }; Box::Box(int ht,int wd,int dp) { height=ht; width=wd; depth=dp; } Box::~Box() { //nothing } int Box::volume() { return height*width*depth; } int main() { Box thisbox(3,4,5); //声明一个类对象并初始化 cout<<thisbox.volume()<<endl; return 0; } 当一个类中没有private成员和protected成员时,也没有虚函数,并且不是从其他类中派生出来的,可以用{}来初始化。(以后再讲解) 5.内联函数 内联函数和普通函数的区别是:内联函数是在编译过程中展开的。通常内联函数必须简短。定义类的内联函数有两种方法:一种和C语言一样,在定义函数时使用关键字inline。如: inline int Box::volume() { return height*width*depth; } 还有一种方法就是直接在类声明的内部定义函数体,而不是仅仅给出一个函数原型。我们把上面的函数简化一下: #include iostream.h class Box { private: int height,width,depth; public: Box(int ht,int wd,int dp) { height=ht; width=wd; depth=dp; } ~Box(); int volume() { return height*width*depth; } }; int main() { Box thisbox(3,4,5); //声明一个类对象并初始化 cout<<thisbox.volume()<<endl; return 0; } 这样,两个函数默认为内联函数了。 二、构造函数 什么是构造函数?通俗的讲,在类中,函数名和类名相同的函数称为构造函数。上面的Box()函数就是构造函数。C++允许同名函数,也就允许在一个类中有多个构造函数。如果一个都没有,编译器将为该类产生一个默认的构造函数,这个构造函数可能会完成一些工作,也可能什么都不做。 绝对不能指定构造函数的类型,即使是void型都不可以。实际上构造函数默认为void型。 当一个类的对象进入作用域时,系统会为其数据成员分配足够的内存,但是系统不一定将其初始化。和内部数据类型对象一样,外部对象的数据成员总是初始化为0。局部对象不会被初始化。构造函数就是被用来进行初始化工作的。当自动类型的类对象离开其作用域时,所站用的内存将释放回系统。 看上面的例子,构造函数Box()函数接受三个整型擦黑素,并把他们赋值给立方体对象的数据成员。 如果构造函数没有参数,那么声明对象时也不需要括号。 1.使用默认参数的构造函数 当在声明类对象时,如果没有指定参数,则使用默认参数来初始化对象。 #include iostream.h class Box { private: int height,width,depth; public: Box(int ht=2,int wd=3,int dp=4) { height=ht; width=wd; depth=dp; } ~Box(); int volume() { return height*width*depth; } }; int main() { Box thisbox(3,4,5); //初始化 Box defaulbox; //使用默认参数 cout<<thisbox.volume()<<endl; //输出60 cout<<defaulbox.volume()<<endl; //输出24 return 0; } 2.默认构造函数 没有参数或者参数都是默认值的构造函数称为默认构造函数。如果你不提供构造函数,编译器会自动产生一个公共的默认构造函数,这个构造函数什么都不做。如果至少提供一个构造函数,则编译器就不会产生默认构造函数。 3.重载构造函数 一个类中可以有多个构造函数。这些构造函数必须具有不同的参数表。在一个类中需要接受不同初始化值时,就需要编写多个构造函数,但有时候只需要一个不带初始值的空的Box对象。 #include iostream.h class Box { private: int height,width,depth; public: Box() { //nothing } Box(int ht=2,int wd=3,int dp=4) { height=ht; width=wd; depth=dp; } ~Box(); int volume() { return height*width*depth; } }; int main() { Box thisbox(3,4,5); //初始化 Box otherbox; otherbox=thisbox; cout<<otherbox.volume();<<endl; return 0; } 这两个构造函数一个没有初始化值,一个有。当没有初始化值时,程序使用默认值,即2,3,4。 但是这样的程序是不好的。它允许使用初始化过的和没有初始化过的Box对象,但它没有考虑当thisbox给otherbox赋值失败后,volume()该返回什么。较好的方法是,没有参数表的构造函数也把默认值赋值给对象。 class Box { int height,width,depth; public: Box() { height=0;width=0;depth=0; } Box(int ht,int wd,int dp) { height=ht;width=wd;depth=dp; } int volume() { return height*width*depth; } }; 这还不是最好的方法,更好的方法是使用默认参数,根本不需要不带参数的构造函数。 class Box { int height,width,depth; public: Box(int ht=0,int wd=0,int dp=0) { height=ht;width=wd;depth=dp; } int volume() { return height*width*depth; } }; 三、析构函数 当一个类的对象离开作用域时,析构函数将被调用(系统自动调用)。析构函数的名字和类名一样,不过要在前面加上 ~ 。对一个类来说,只能允许一个析构函数,析构函数不能有参数,并且也没有返回值。析构函数的作用是完成一个清理工作,如释放从堆中分配的内存。 我们也可以只给出析构函数的形式,而不给出起具体函数体,其效果是一样的,如上面的例子。但在有些情况下,析构函数又是必需的。如在类中从堆中分配了内存,则必须在析构函数中释放 主体:(三)类的转换 C++的内部数据类型遵循隐式类型转换规则。假设某个表达市中使用了一个短整型变量,而编译器根据上下文认为这儿需要是的长整型,则编译器就会根据类型转换规则自动把它转换成长整型,这种隐式转换出现在赋值、参数传递、返回值、初始化和表达式中。我们也可以为类提供相应的转换规则。 对一个类建立隐式转换规则需要构造一个转换函数,该函数作为类的成员,可以把该类的对象和其他数据类型的对象进行相互转换。声明了转换函数,就告诉了编译器,当根据句法判定需要类型转换时,就调用函数。 有两种转换函数。一种是转换构造函数;另一种是成员转换函数。需要采用哪种转换函数取决于转换的方向。 一、转换构造函数 当一个构造函数仅有一个参数,且该参数是不同于该类的一个数据类型,这样的构造函数就叫转换构造函数。转换构造函数把别的数据类型的对象转换为该类的一个对象。和其他构造函数一样,如果声明类的对象的初始化表同转换构造函数的参数表相匹配,该函数就会被调用。当在需要使用该类的地方使用了别的数据类型,便宜器就会调用转换构造函数进行转换。 #include iostream.h #include time.h #include stdio.h class Date { int mo, da, yr; public: Date(time_t); void display(); }; void Date::display() { char year[5]; if(yr<10) sprintf(year,0%d,yr); else sprintf(year,%d,yr); cout<<mo<<'/'<<da<<'/'<tm_mday; mo=tim->tm_mon+1; yr=tim->tm_year; if(yr>=100) yr-=100; } int main() { time_t now=time(0); Date dt(now); dt.display(); return 0; } 本程序先调用time()函数来获取当前时间,并把它赋给time_t对象;然后程序通过调用Date类的转换构造函数来创建一个Date对象,该对象由time_t对象转换而来。time_t对象先传递给localtime()函数,然后返回一个指向tm结构(time.h文件中声明)的指针,然后构造函数把结构中的日月年的数值拷贝给Date对象的数据成员,这就完成了从time_t对象到Date对象的转换。 二、成员转换函数 成员转换函数把该类的对象转换为其他数据类型的对象。在成员转换函数的声明中要用到关键字operator。这样声明一个成员转换函数: operator aaa(); 在这个例子中,aaa就是要转换成的数据类型的说明符。这里的类型说明符可以是任何合法的C++类型,包括其他的类。如下来定义成员转换函数; Classname::operator aaa() 类名标识符是声明了该函数的类的类型说明符。上面定义的Date类并不能把该类的对象转换回time_t型变量,但可以把它转换成一个长整型值,计算从2000年1月1日到现在的天数。 #include iostream.h class Date { int mo,da,yr; public: Date(int m,int d,int y) {mo=m; da=d; yr=y;} operator int(); //声明 }; Date::operator int() //定义 { static int dys[]={31,28,31,30,31,30,31,31,30,31,30,31}; int days=yr-2000; days*=365; days+=(yr-2000)/4; for(int i=0;i<mo-1;i++) days+=dys[i]; days+=da; return days; } int main() { Date now(12,24,2003); int since=now; cout<<since<<endl; return 0; } 三、类的转换 上面两个例子都是C++类对象和内部数据对象之间的相互转换。也可以定义转换函数来实现两个类对象之间的相互转换。 #include iostream.h class CustomDate { public: int da, yr; CustomDate(int d=0,int y=0) {da=d; yr=y;} void display() { cout<<yr<<'-'<<da<<endl; } }; class Date { int mo, da, yr; public: Date(int m=0,int d=0,int y=0) {mo=m; da=d; yr=y;} Date(const CustomDate&); //转换构造函数 operator CustomDate(); //成员转换函数 void display() { cout<<mo<<'/'<<da<<'/'<<yr<<endl; } }; static int dys[] = {31,28,31,30,31,30,31,31,30,31,30,31}; Date::Date(const CustomDate& jd) { yr=jd.yr; da=jd.da; for(mo=0;modys[mo]) da-=dys[mo]; else break; mo++; } Date::operator CustomDate() { CustomDate cd(0,yr); for(int i=0;i<mo-1;i++) cd.da+=dys[i]; cd.da+=da; return cd; } int main() { Date dt(12,24,3); CustomDate cd; cd = dt; //调用成员转换函数 cd.display(); dt = cd; //调用转换构造函数 dt.display(); return 0; } 这个例子中有两个类CustomDate和Date,CustomDate型日期包含年份和天数。 这个例子没有考虑闰年情况。但是在实际构造一个类时,应该考虑到所有问题的可能性。 在Date里中具有两种转换函数,这样,当需要从Date型变为CustomDate型十,可以调用成员转换函数;反之可以调用转换构造函数。 不能既在Date类中定义成员转换函数,又在CustomDate类里定义转换构造函数。那样编译器在进行转换时就不知道该调用哪一个函数,从而出错。 四、转换函数的调用 C++里调用转换函数有三种形式:第一种是隐式转换,例如编译器需要一个Date对象,而程序提供的是CustomDate对象,编译器会自动调用合适的转换函数。另外两种都是需要在程序代码中明确给出的显式转换。C++强制类型转换是一种,还有一种是显式调用转换构造函数成员转换函数。下面的程序给出了三中转换形式: #include iostream.h class CustomDate { public: int da, yr; CustomDate(int d=0,int y=0) {da=d; yr=y;} void display() { cout<<yr<<'-'<<da<<endl; } }; class Date { int mo, da, yr; public: Date(int m,int d,int y) { mo=m; da=d; yr=y; } operator CustomDate(); }; Date::operator CustomDate() { static int dys[]={31,28,31,30,31,30,31,31,30,31,30,31}; CustomDate cd(0,yr); for(int i=0;i<mo-1;i++) cd.da+=dys[i]; cd.da+=da; return cd; } int main() { Date dt(11,17,89); CustomDate cd; cd = dt; cd.display(); cd = (CustomDate) dt; cd.display(); cd = CustomDate(dt); cd.display(); return 0; } 五、转换发生的情形 上面的几个例子都是通过不能类型对象之间的相互赋值来调用转换函数,还有几种调用的可能: 参数传递 初始化 返回值 表达式语句 这些情况下,都有可能调用转换函数。 下面的程序不难理解,就不分析了。 #include iostream.h class CustomDate { public: int da, yr; CustomDate() {} CustomDate(int d,int y) { da=d; yr=y;} void display() { cout<<yr<<'-'<<da<<endl; } }; class Date { int mo, da, yr; public: Date(int m,int d,int y) { mo=m; da=d; yr=y; } operator CustomDate(); }; Date::operator CustomDate() { static int dys[]={31,28,31,30,31,30,31,31,30,31,30,31}; CustomDate cd(0,yr); for (int i=0;i<mo-1;i++) cd.da += dys[i]; cd.da+=da; return cd; } class Tester { CustomDate cd; public: explicit Tester(CustomDate c) { cd=c; } void display() { cd.display(); } }; void dispdate(CustomDate cd) { cd.display(); } CustomDate rtndate() { Date dt(9,11,1); return dt; } int main() { Date dt(12,24,3); CustomDate cd; cd = dt; cd.display(); dispdate(dt); Tester ts(dt); ts.display(); cd = rtndate(); cd.display(); return 0; } 六、显式构造函数 注意上面Tester类的构造函数前面有一个explicit修饰符。如果不加上这个关键字,那么在需要把CustomDate对象转换成Tester对象时,编译器会把该函数当作转换构造函数来调用。但是有时候,并不想把这种只有一个参数的构造函数用于转换目的,而仅仅希望用它来显式地初始化对象,此时,就需要在构造函数前加explicit。如果在声明了Tester对象以后使用了下面的语句将导致一个错误: ts=jd; //error 这个错误说明,虽然Tester类中有一个以Date型变量为参数的构造函数,编译器却不会把它看作是从Date到Tester的转换构造函数,因为它的声明中包含了explicit修饰符。 七、表达式内部的转换 在表达式内部,如果发现某个类型和需要的不一致,就会发生错误。数字类型的转换是很简单,这里就不举例了。下面的程序是把Date对象转换成长整型值。 #include iostream.h class Date { int mo, da, yr; public: Date(int m,int d,int y) { mo=m; da=d; yr=y; } operator long(); }; Date::operator long() { static int dys[]={31,28,31,30,31,30,31,31,30,31,30,31}; long days=yr; days*=365; days+=(yr-1900)/4; //从1900年1月1日开始计算 for(int i=0;i<mo-1;i++) days+=dys[i]; days+=da; return days; } int main() { Date today(12,24,2003); const long ott=123; long sum=ott+today; cout<<ott<< + <<(long) today<< = <<sum; return 0; } 在表达式中,当需要转换的对象可以转换成某个数字类型,或者表达式调用了作用于某个类的重载运算符时,就会发生隐式转换。运算符重载以后再学习。 主体:(四)私有数据成员和友元 一、私有数据成员的使用 1.取值和赋值成员函数 面向对象的约定就是保证所有数据成员的私有性。一般我们都是通过公有成员函数来作为公共接口来读取私有数据成员的。某些时候,我们称这样的函数为取值和赋值函数。 取值函数的返回值和传递给赋值函数的参数不必一一匹配所有数据成员的类型。 #include iostream.h class Date { int mo, da, yr; public: Date(int m,int d,int y) { mo=m; da=d; yr=y; } int getyear() const { return yr; } void setyear(int y) { yr = y; } }; int main() { Date dt(4,1,89); cout<<The year is: <<dt.getyear()<<endl; dt.setyear(97); cout<<The new year is: <<dt.getyear(); return 0; } 上面的例子很简单,不分析了。要养成这样的习惯,通过成员函数来访问和改变类中的数据。这样有利于软件的设计和维护。比如,改变Date类内部数据的形式,但仍然用修改过的getyear()和setyear()来提供访问接口,那么使用该类就不必修改他们的代码,仅需要重新编译程序即可。 2.常量成员函数 注意上面的程序中getyear()被声明为常量型,这样可以保证该成员函数不会修改调用他的对象。通过加上const修饰符,可以使访问对象数据的成员函数仅仅完成不会引起数据变动的那些操作。 如果程序声明某个Date对象为常量的话,那么该对象不得调用任何非常量型成员函数,不论这些函数是否真的试图修改对象的数据。只有把那些不会引起数据改变的函数都声明为常量型,才可以让常量对象来调用。 3.改进的成员转换函数 下面的程序改进了从Date对象到CustomDate对象的成员转换函数,用取值和赋值函数取代了使用公有数据成员的做法。(以前的程序代码在上一帖中) #include iostream.h class CustomDate { int da,yr; public: CustomDate() {} CustomDate(int d,int y) { da=d; yr=y; } void display() const {cout<<yr<<'-'<<da<<endl;} int getday() const { return da; } void setday(int d) { da=d; } }; class Date { int mo,da,yr; public: Date(int m,int d,int y) { mo=m; da=d; yr=y; } operator CustomDate() const; }; Date::operator CustomDate() const { static int dys[] = {31,28,31,30,31,30,31,31,30,31,30,31}; CustomDate cd(0,yr); int day=da; for(int i=0;i<mo-1;i++) day+=dys[i]; cd.setday(day); return cd; } int main() { Date dt(11,17,89); CustomDate cd; cd=dt; cd.display(); return 0; } 注意上面的程序中Date::operator CustomDate()声明为常量型,因为这个函数没有改变调用它对象的数据,尽管它修改了一个临时CustomDate对象并将其作为函数返回值。 二、友元 前面已经说过了,私有数据成员不能被类外的其他函数读取,但是有时候类会允许一些特殊的函数直接读写其私有数据成员。 关键字friend可以让特定的函数或者别的类的所有成员函数对私有数据成员进行读写。这既可以维护数据的私有性,有可以保证让特定的类或函数能够直接访问私有数据。 1.友元类 一个类可以声明另一个类为其友元,这个友元的所有成员函数都可以读写它的私有数据。 #include iostream.h class Date; class CustomDate { int da,yr; public: CustomDate(int d=0,int y=0) { da=d; yr=y; } void display() const {cout<<yr<<'-'<<da<<endl;} friend Date; //这儿 }; class Date { int mo,da,yr; public: Date(int m,int d,int y) { mo=m; da=d; yr=y; } operator CustomDate(); }; Date::operator CustomDate() { static int dys[] = {31,28,31,30,31,30,31,31,30,31,30,31}; CustomDate cd(0, yr); for (int i=0;i<mo-1;i++) cd.da+=dys[i]; cd.da+=da; return cd; } int main() { Date dt(11,17,89); CustomDate cd(dt); cd.display(); return 0; } 在上面的程序中,有这样一句 friend Date; 该语句告诉编译器,Date类的所有成员函数有权访问CustomDate类的私有成员。因为Date类的转换函数需要知道CustomDate类的每个数据成员,所以真个Date类都被声明为CustomDate类的友元。 2.隐式构造函数 上面程序对CustomDate的构造函数的调用私有显示该类需要如下的一个转换构造函数: CustomDate(Date& dt); 但是唯一的一个构造函数是:CustomDate(int d=0;int y=0); 这就出现了问题,编译器要从Date对象构造一个CustomDate对象,但是CustomDate类中并没有定义这样的转换构造函数。不过Date类中定义了一个成员转换函数,它可以把Date对象转换成CustomDate对象。于是编译器开始搜索CustomDate类,看其是否有一个构造函数,能从一个已存在的CustomDate的对象创建新的CustomDate对象。这种构造函数叫拷贝构造函数。拷贝构造函数也只有一个参数,该参数是它所属的类的一个对象,由于CustomDate类中没有拷贝构造函数,于是编译器就会产生一个默认的拷贝构造函数,该函数简单地把已存在的对象的每个成员拷贝给新对象。现在我们已经知道,编译器可以把Date对象转换成CustomDate对象,也可以从已存在的CustomDate对象生成一个新的CustomDate对象。那么上面提出的问题,编译器就是这样做的:它首先调用转换函数,从Date对象创建一个隐藏的、临时的、匿名的CustomDate对象,然后用该临时对象作为参数调用默认拷贝构造函数,这就生成了一个新的CustomDate对象。 3.预引用 上面的例子中还有这样一句 class Date; 这个语句叫做预引用。它告诉编译器,类Date将在后面定义。编译器必须知道这个信号,因为CustomDate类中引用了Date类,而Date里也引用了CustomDate类,必须首先声明其中之一。 使用了预引用后,就可以声明未定义的类的友元、指针和引用。但是不可以使用那些需要知道预引用的类的定义细节的语句,如声明该类的一个实例或者任何对该类成员的引用。 4.显式友元预引用 也可以不使用预引用,这只要在声明友元的时候加上关键自class就行了。 #include iostream.h class CustomDate { int da,yr; public: CustomDate(int d=0,int y=0) { da=d; yr=y; } void display() const {cout<<yr<<'-'<<da<<endl;} friend class Date; //这儿,去掉前面的预引用 }; class Date { ... ... }; Date::operator CustomDate() { ... ... } int main() { ... ... } 5.友元函数 通常,除非真的需要,否则并不需要把整个类都设为另一个类的友元,只需挑出需要访问当前类私有数据成员成员函数,将它们设置为该类的友元即可。这样的函数称为友元函数。 下面的程序限制了CustomDate类数据成员的访问,Date类中只有需要这些数据的成员函数才有权读写它们。 #include iostream.h class CustomDate; class Date { int mo,da,yr; public: Date(const CustomDate&); void display() const {cout<<mo<<'/'<<da<<'/'<<yr<<endl;} }; class CustomDate { int da,yr; public: CustomDate(int d=0,int y=0) { da=d; yr=y; } friend Date::Date(const CustomDate&); }; Date::Date(const CustomDate& cd) { static int dys[] = {31,28,31,30,31,30,31,31,30,31,30,31}; yr=cd.yr; da=cd.da; for(mo=0;modys[mo]) da-=dys[mo]; else break; mo++; } int main() { Date dt(CustomDate(123, 89)); dt.display(); return 0; } 6.匿名对象 上面main()函数中Date对象调用CustomDate类的构造函数创建了一个匿名CustomDate对象,然后用该对象创建了一个Date对象。这种用法在C++中是经常出现的。 7.非类成员的友元函数 有时候友元函数未必是某个类的成员。这样的函数拥有类对象私有数据成员的读写权,但它并不是任何类的成员函数。这个特性在重载运算符时特别有用。 非类成员的友元函数通常被用来做为类之间的纽带。一个函数如果被两个类同时声明为友元,它就可以访问这两个类的私有成员。下面的程序说明了一个可以访问两个类私有数据成员的友元函数是如何将在两个类之间架起桥梁的。 #include iostream.h class Time; class Date { int mo,da,yr; public: Date(int m,int d,int y) { mo=m; da=d; yr=y;} friend void display(const Date&, const Time&); }; class Time { int hr,min,sec; public: Time(int h,int m,int s) { hr=h; min=m; sec=s;} friend void display(const Date&, const Time&); }; void display(const Date& dt, const Time& tm) { cout << dt.mo << '/' << dt.da << '/' << dt.yr; cout << ' '; cout << tm.hr << ':' << tm.min << ':' << tm.sec; } int main() { Date dt(2,16,97); Time tm(10,55,0); display(dt, tm); return 0; } 主体:(五)析构函数和this指针 一、析构函数 前面的一些例子都没有说明析构函数,这是因为所用到的类在结束时不需要做特别的清理工作。下面的程序给出了一新的Date类,其中包括一个字符串指针,用来表示月份。 #include iostream.h #include string.h class Date { int mo,da,yr; char *month; public: Date(int m=0, int d=0, int y=0); ~Date(); void display() const; }; Date::Date(int m,int d,int y) { static char *mos[] = { January,February,March,April,May,June, July,August,September,October,November,December }; mo=m; da=d; yr=y; if(m!=0) { month=new char[strlen(mos[m-1])+1]; strcpy(month, mos[m-1]); } else month = 0; } Date::~Date() { delete [] month; } void Date::display() const { if(month!=0) cout<<month<<' '<<da<<','<<yr; } int main() { Date birthday(8,11,1979); birthday.display(); return 0; } 在Date对象的构造函数中,首先用new运算符为字符串month动态分配了内存,然后从内部数组中把月份的名字拷贝给字符串指针month。 析构函数在删除month指针时,可能会出现一些问题。当然从这个程序本身来看,没什么麻烦;但是从设计一个类的角度来看,当Date类用于赋值时,就会出现问题。假设上面的main()修改为“ int main() { Date birthday(8,11,1979); Date today; today=birthday; birthday.display(); return 0; } 这会生成一个名为today的空的Date型变量,并且把birthday值赋给它。如果不特别通知编译器,它会简单的认为类的赋值就是成员成员的拷贝。在上面的程序中,变量birthday有一个字符型指针month,并且在构造函数里用new运算符初始化过了。当birthday离开其作用域时,析构函数会调用delete运算符来释放内存。但同时,当today离开它的作用域时,析构函数同样会对它进行释放操作,而today里的month指针是birthday里的month指针的一个拷贝。析构函数对同一指针进行了两次删除操作,这会带来不可预知的后果。 如果假设today是一个外部变量,而birthday是一个自变量。当birthday离开其作用域时,就已经把对象today里的month指针删除了。显然这也是不正确的。 再假设有两个初始化的Date变量,把其中一个的值赋值给另一个: Date birthday(8,11,1979); Date today(12,29,2003); today=birthday; 问题就更复杂了,当这两个变量离开作用域时,birthday中的month的值已经通过赋值传递给了today。而today中构造函数用new运算符给month的值却因为赋值被覆盖了。这样,birthday中的month被删除了两次,而today中month却没有被删除掉。 二、重载赋值运算符 为了解决上面的问题,我们应该写一个特殊的赋值运算符函数来处理这类问题。当需要为同一个类的两个对象相互赋值时,就可以重载运算符函数。这个方法可以解决类的赋值和指针的释放。 下面的程序中,类中的赋值函数用new运算符从堆中分配了一个不同的指针,该指针获取赋值对象中相应的值,然后拷贝给接受赋值的对象。 在类中重载赋值运算符的格式如下: void operator = (const Date&) 后面我们回加以改进。目前,重载的运算符函数的返回类型为void。它是类总的成员函数,在本程序红,是Date类的成员函数。它的函数名始终是operator =,参数也始终是同一个类的对象的引用。参数表示的是源对象,即赋值数据的提供者。重载函数的运算符作为目标对象的成员函数来使用。 #include iostream.h #include string.h class Date { int mo,da,yr; char *month; public: Date(int m=0, int d=0, int y=0); ~Date(); void operator=(const Date&); void display() const; }; Date::Date(int m, int d, int y) { static char *mos[] = { January,February,March,April,May,June, July,August,September,October,November,December }; mo = m; da = d; yr = y; if (m != 0) { month = new char[strlen(mos[m-1])+1]; strcpy(month, mos[m-1]); } else month = 0; } Date::~Date() { delete [] month; } void Date::display() const { if (month!=0) cout<<month<<' '<<da<<,<<yr<<endl; } void Date::operator=(const Date& dt) { if (this != &dt) { mo = dt.mo; da = dt.da; yr = dt.yr; delete [] month; if (dt.month != 0) { month = new char [std::strlen(dt.month)+1]; std::strcpy(month, dt.month); } else month = 0; } } int main() { Date birthday(8,11,1979); birthday.display(); Date newday(12,29,2003); newday.display(); newday = birthday; newday.display(); return 0; } 除了为Date类加入了一个重载运算符函数,这个程序和上面的一个程序是相同的。赋值运算符函数首先取得所需的数据,然后用delete把原来的month指针所占用的内存返还给堆。接着,如果源对象的month指针已经初始化过,就用new运算符为对象重新分配内存,并把源对象的month字符串拷贝给接受方。 重载的Date类赋值运算符函数的第一个语句比较了源对象的地址和this指针。这个操作取保对象不会自己给自己赋值。 三、this指针 this指针是一个特殊的指针,当类的某个非静态的成员函数在执行时,就会存在this指针。它指向类的一个对象,且这个对象的某个成员函数正在被调用。 this指针的名字始终是this,而且总是作为隐含参数传递给每一个被声明的成员函数,例如: void Date::myFunc(Date* this); 实际编程时函数的声明不需要包含这个参数。 当程序中调用某个对象的成员函数时,编译器会把该对象的地址加入到参数列表中,感觉上就好象函数采用了上面所示的声明,并且是用如下方式来调用的: dt.myFunc(& dt); 静态成员函数不存在this指针。 当调用某个对象的成员函数时,编译器把对象的地址传递给this指针,然后再调用该函数。因此,成员函数你对任何成员的调用实际上都隐式地使用了this指针。 1.以this指针作为返回值 使用this指针可以允许成员函数返回调用对象给调用者。前面的程序中重载赋值运算符没有返回值,因此不能用如下的形式对字符串进行赋值: a=b=c; 为了使重载的类赋值机制也能这样方便,必须让赋值函数返回赋值的结果,在这里就是目标对象。当赋值函数执行时,其返回值也恰好是this指针所指的内容。 下面的程序对前面那个程序进行了修改,让重载赋值运算符返回了一个Date对象的引用。 #include iostream.h #include string.h class Date { int mo,da,yr; char *month; public: Date(int m=0, int d=0, int y=0); ~Date(); void operator=(const Date&); void display() const; }; Date::Date(int m, int d, int y) { static char *mos[] = { January,February,March,April,May,June, July,August,September,October,November,December }; mo = m; da = d; yr = y; if (m != 0) { month = new char[strlen(mos[m-1])+1]; strcpy(month, mos[m-1]); } else month = 0; } Date::~Date() { delete [] month; } void Date::display() const { if (month!=0) cout<<month<<' '<<da<<,<<yr<<endl; } void Date::operator=(const Date& dt) { if (this != &dt) { mo = dt.mo; da = dt.da; yr = dt.yr; delete [] month; if (dt.month != 0) { month = new char [std::strlen(dt.month)+1]; std::strcpy(month, dt.month); } else month = 0; } return *this; } int main() { Date birthday(8,11,1979); Date oldday,newday; oldday=newday=birthday; birthday.display(); oldday.display(); newday.display(); return 0; } 2.在链表中使用this指针 在应用程序中,如果数据结构里有指向自身类型的成员,那么使用this指针会提供更多的方便。下面的程序中建立了一个类ListEntry的链表。 #include iostream.h #include string.h class ListEntry { char* listvalue; ListEntry* preventry; public: ListEntry(char*); ~ListEntry() { delete [] listvalue; } ListEntry* PrevEntry() const { return preventry; }; void display() const { cout<<endl<<listvalue; } void AddEntry(ListEntry& le) { le.preventry = this; } }; ListEntry::ListEntry(char* s) { listvalue = new char[strlen(s)+1]; strcpy(listvalue, s); preventry = 0; } int main() { ListEntry* prev = 0; while (1) { cout <<endl<> name; if (strncmp(name, end, 3) == 0) break; ListEntry* list = new ListEntry(name); if (prev != 0) prev->AddEntry(*list); prev = list; } while (prev != 0) { prev->display(); ListEntry* hold = prev; prev = prev->PrevEntry(); delete hold; } return 0; } 程序运行时,会提示输入一串姓名,当输入完毕后,键入end,然后程序会逆序显示刚才输入的所有姓名。 程序中ListEntry类含有一个字符串和一个指向前一个表项的指针。构造函数从对中获取内存分配给字符串,并把字符串的内容拷贝到内存,然后置链接指针为NULL。析构函数将释放字符串所占用的内存。 成员函数PrevEntry()返回指向链表前一个表项的指针。另一个成员函数显示当前的表项内容。 成员函数AddEntry(),它把this指针拷贝给参数的preventry指针,即把当前表项的地址赋值给下一个表项的链接指针,从而构造了一个链表。它并没有改变调用它的listEntry对象的内容,只是把该对象的地址赋给函数的参数所引用的那个ListEntry对象的preventry指针,尽管该函数不会修改对象的数据,但它并不是常量型。这是因为,它拷贝对象的地址this指针的内容给一个非长常量对象,而编译器回认为这个非常量对象就有可能通过拷贝得到的地址去修改当前对象的数据,因此AddEntry()函数在声明时不需要用const。 主体:(六)类对象数组和静态成员 一、类对象数组 类的对象和C++其他数据类型一样,也可以为其建立数组,数组的表示方法和结构一样。 #include iostream.h class Date { int mo,da,yr; public: Date(int m=0,int d=0, int y=0) { mo=m; da=d; yr=y;} void display() const { cout<<mo<<'/'<<da<<'/'<<yr<<endl; } }; int main() { Date dates[2]; Date today(12,31,2003); dates[0]=today; dates[0].display(); dates[1].display(); return 0; } 1.类对象数组和默认构造函数 在前面已经说过,不带参数或者所有参数都有默认值的构造函数叫做默认构造函数。如果类中没有构造函数,编译器会自动提供一个什么都不做的公共默认构造函数 。如果类当中至少有一个构造函数,编译器就不会提供默认构造函数。 如果类当中不含默认构造函数,则无法实例化其对象数组。因为实例花类对象数组的格式不允许用初始化值来匹配某个构造函数的参数表。 上面的程序中,main()函数声明了一个长度为2的Date对象数组,还有一个包含初始化值的单个Date对象。接着把这个初始化的Date对象赋值给数组中第一个对象,然后显示两个数组元素中包含的日期。从输出中可以看到,第一个日期是有效日期,而第二个显示的都是0。 当声明了某个类的对象数组时,编译器会为每个元素都调用默认构造函数。 下面的程序去掉了构造函数默认参数值,并且增加了一个默认构造函数。 #include class Date { int mo, da, yr; public: Date(); Date(int m,int d,int y) { mo=m; da=d; yr=y;} void display() const { cout <<mo<<'/'<<da<<'/'<<yr<<endl; } }; Date::Date() { cout <<Date constructor running<<endl; mo=0; da=0; yr=0; } int main() { Date dates[2]; Date today(12,31,2003); dates[0]=today; dates[0].display(); dates[1].display(); return 0; } 运行程序,输出为: Date constructor running Date constructor running 12/31/2003 0/0/0 从输出中可以看出,Date()这个默认构造函数被调用了两次。 2.类对象数组和析构函数 当类对象离开作用域时,编译器会为每个对象数组元素调用析构函数。 #include iostream.h class Date { int mo,da,yr; public: Date(int m=0,int d=0,int y=0) { mo=m; da=d; yr=y;} ~Date() {cout<<Date destructor running<<endl;} void display() const {cout<<mo<<'/'<<da<<'/'<<yr<<endl; } }; int main() { Date dates[2]; Date today(12,31,2003); dates[0]=today; dates[0].display(); dates[1].display(); return 0; } 运行程序,输出为: 12/31/2003 0/0/0 Date destructor running Date destructor running Date destructor running 表明析构函数被调用了三次,也就是dates[0],dates[1],today这三个对象离开作用域时调用的。 二、静态成员 可以把类的成员声明为静态的。静态成员只能存在唯一的实例。所有的成员函数都可以访问这个静态成员。即使没有声明类的任何实例,静态成员也已经是存在的。不过类当中声明静态成员时并不能自动定义这个变量,必须在类定义之外来定义该成员。 1.静态数据成员 静态数据成员相当于一个全局变量,类的所有实例都可以使用它。成员函数能访问并且修改这个值。如果这个静态成员是公有的,那么类的作用域之内的所有代码(不论是在类的内部还是外部)都可以访问这个成员。下面的程序通过静态数据成员来记录链表首项和末项的地址。 #include iostream.h #include string.h class ListEntry { public: static ListEntry* firstentry; private: static ListEntry* lastentry; char* listvalue; ListEntry* nextentry; public: ListEntry(char*); ~ListEntry() { delete [] listvalue;} ListEntry* NextEntry() const { return nextentry; }; void display() const { cout<<listvalue<nextentry=this; lastentry=this; listvalue=new char[strlen(s)+1]; strcpy(listvalue,s); nextentry=0; } int main() { while (1) { cout<>name
Visual C++ 2005入门经典.pdf(整理并添加所有书签) ,看书的时候更方便. 封面 目录 第1章 使用Visual C++ 2005 编程 1.1 .NET Framework 1.2 CLR 1.3 编写C++应用程序 1.4 学习windows编程 1.4.1 学习c++ 1.4.2 C++标准 1.4.3 控制台应用程序 1.4.4 Windows编程概念 1.5 集成开发环境简介 1.6 使用IDE 1.6.1 工具栏选项 1.6.2 可停靠的工具栏 1.6.3 文档 1.6.4 项目和解决方案 1.6.5 设置Visual C++2005的选项 1.6.6 创建和执行windows应用程序 1.6.7 创建windows Forms应用程序 1.7 小结 第2章 数据、变量和计算 2.1 C++程序结构 2.1.1 程序注释 2.1.2 #include指令——头文件 2.1.3 命名空间和using声明 2.1.4 main()函数 2.1.5 程序语句 2.1.6 空白 2.1.7 语句块 2.1.8 自动生成的控制台程序 2.2 定义变量 2.2.1 命名变量 2.2.2 C++中的关键字 2.2.3 声明变量 2.2.4 变量的初值 2.3 基本数据类型 2.3.1 整型变量 2.3.2 字符数据类型 2.3.3 整型修饰符 2.3.4 布尔类型 2.3.5 浮点类型 2.3.6 ISO/ANSI C++中的基本类型 2.3.7 字面值 2.3.8 定义数据类型的同义词 2.3.9 具有特定值集的变量 2.3.10 指定枚举常量的类型 2.4 基本的输入输出操作 2.4.1 从键盘输入 2.4.2 到命令行的输出 2.4.3 格式化输出 2.4.4 转义序列 2.5 C++中的计算 2.5.1 赋值语句 2.5.2 算术运算 2.5.3 计算余数 2.5.4 修改变量 2.5.5 增量和减量运算符 2.5.6 计算的顺序 2.6 变量类型和类型强制转换 2.6.1 对操作数进行类型强制转换的规则 2.6.2 赋值语句中的类型强制转换 2.6.3 显式类型强制转换 2.6.4 老式的类型强制转换 2.6.5 按位运算符 2.7 了解存储时间和作用域 2.7.1 自动变量 2.7.2 决定变量声明的位置 2.7.3 全局娈量 2.7.4 静态变量 2.8 命名空间 2.8.1 声明命名空间 2.8.2 多个命名空间 2.9 C++/CLI编程 2.9.1 C++/CLI特有的基本数据类型 2.9.2 命令行上的C++/CLI输出 2.9.3 C++/CLI特有的功能——格式化输出 2.9.4 C++/CLI的键盘输入 2.9.5 使用safe cast 2.9.6 C++/CLI枚举 2.10 小结 2.11 练习题 第3章 判断和循环 3.1 比较数据值 3.1.1 if语句 3.1.2 嵌套if语句 3.1.3 扩展的if语句 3.1.4 嵌套的if-else语句 3.1.5 逻辑运算符和表达式 3.1.6 条件运算符 3.1.7 switch语句 3.1.8 无条件转移 3.2 重复执行语句块 3.2.1 循环的概念 3.2.2 for循环的变体 3.2.3 while循环 3.2.4 do-while循环 3.2.5 嵌套的循环 3.3 C++/CLI编程 3.4 小结 3.5 练习 第4章 数组、字符串和指针 4.1 处理多个相同类型的数据值 4.1.1 数组 4.1.2 声明数组 4.1.3 初始化数组 4.1.4 字符数组和字符串处理 4.1.5 多维数组 4.2 间接数据存取 4.2.1 指针的概念 4.2.2 声明指针 4.2.3 使用指针 4.2.4 初始化指针 4.2.5 sizeof运算符 4.2.6 常量指针和指向常量的指针 4.2.7指针和数组 4.3 动态内存分配 4.3.1 堆的别名——自由存储器 4.3.2 new和delete运算符 4.3.3 为数组动态分配内存 4.3.4 多维数组的动态分配 4.4 使用引用 4.4.1 引用的概念 4.4.2 声明并初始化引用 4.5 C++/CLI编程 4.5.1 跟踪句柄 4.5.2 CLR数组 4.5.3 字符串 4.5.4 跟踪引用 4.5.5 内部指针 4.6 小结 4.7 练习 第5章 程序结构(1) 5.1 理解函数 5.1.1 需要函数的原因 5.1.2 函数的结构 5.1.3 使用函数 5.2 给函数传递实参 5.2.1 按值传递机制 5.2.2 给函数传递指针实参 5.2.3 给函数传递数组 5.2.4 给函数传递引用实参 5.2.5 使用const修饰符 5.2.6 main()函数的实参 5.2.7 接受数量不定的函数实参 5.3 从函数返回值 5.3.1 返回指针 5.3.2 返回引用 5.3.3 函数中的静态变量 5.4 递归函数调用 5.5 C++/CLI编程 5.5.1 接受数量可变实参的函数 5.5.2 main()的实参 5.6 小结 5.7 练习 第6章 程序结构(2) 6.1 函数指针 6.1.1 声明函数指针 6.1.2 函数指针作为实参 6.1.3 函数指针的数组 6.2 初始化函数形参 6.3 异常 6.3.1 抛出异常 6.3.2 捕获异常 6.3.3 MFC中的异常处理 6.4 处理内存分配错误 6.5 函数重载 6.5.1 函数重载的概念 6.5.2 何时重载函数 6.6 函数模板 6.7 使用函数的示例 6.7.1 实现计算器 6.7.2 从字符串中删除空格 6.7.3 计算表达式的值 6.7.4 获得项值 6.7.5 分析数 6.7.6 整合程序 6.7.7 扩展程序 6.7.8 提取子字符串 6.7.9 运行修改过的程序 6.8 C++/CLI编程 6.8.1 理解类函数 6.8.2 CLR版本的计算器程序 6.9 小结 6.10 练习 第7章 自定义数据类型 7.1 C++中的结构 7.1.1 结构的概念 7.1.2 定义结构 7.1.3 初始化结构 7.1.4 访问结构的成员 7.1.5 伴随结构的智能帮助 7.1.6 RECT结构 7.1.7 使用指针处理结构 7.2 数据类型、对象、类和实例 7.2.1 类的起源 7.2.2 类的操作 7.2.3 术语 7.3 理解类 7.3.1 定义类 7.3.2 声明类的对象 7.3.3 访问类的数据成员 7.3.4 类的成员函数 7.3.5 成员函数定义的位置 7.3.6 内联函数 7.4 类构造函数 7.4.1 构造函数的概念 7.4.2 默认的构造函数 7 4.3 在类定义中指定默认的形参值 7.4.4 在构造函数中使用初始化列表 7.5 类的私有成员 7.5.1 访问私有类成员 7.5.2 类的友元函数 7.5.3 默认复制构造函数 7.6 this指针 7.7 类的const对象 7.7.1 类的const成员函数 7.7.2 类外部的成员函数定义 7.8 类对象的数组 7.9 类的静态成员 7.9.1 类的静态数据成员 7.9.2 类的静态函数成员 7.10 类对象的指针和引用 7.10.1 类对象的指针 7.10.2 类对象的引用 7.11 C++/CLI编程 7.11.1 定义数值类类型 7.11.2 定义引用类类型 7.11.3 类属性 7.11.4 initonly字段 7.11.5 静态构造函数 7.12 小结 7.13 练习 第8章 深入理解类 8.1 类的析构函数 8.1.1 析构函数的概念 8.1.2 默认的析构函数 8.1.3 析构函数与动态内存分配 8.2 实现复制构造函数 8.3 在变量之间共享内存 8.3.1 定义联台 8.3.2 匿名联合 8.3.3 类和结构中的联合 8.4 运算符重载 8.4.1 实现重载的运算符 8.4.2 实现对运算符的完全支持 8.4.3 重载赋值运算符 8.4.4 重载加法运算符 8.4.5 重载递增和递减运算符 8.5 类模板 8.5.1 定义类模板 8.5.2 根据类模板创建对象 8.5.3 使用有多个形参的类模板 8.6 使用类 8.6.1 类接口的概念 8.6.2 定义问题 8.6.3 实现CBox类 8.6.4 定义CBox类 8.6.5 使用CBox类 8.7 组织程序代码 8.8 C++/CLI编程 8.8.1 在数值类中重载运算符 8.8.2 重载递增和递减运算符 8.8.3 在引用类中重载运算符 8.9 小结 8.1O 练习 第9章 类继承和虚函数 9.1 面向对象编程的基本思想 9.2 类的继承 9.2.1 基类的概念 9.2.2 基类的派生类 9.3 继承机制下的访问控制 9.3.1 派生类中构造函数的操作 9.3.2 声明类的保护成员 9.3.3 继承类成员的访问级别 9.4 派生类中的复制构造函数 9.5 友元类成员 9.5.1 友元类 9.5.2 对类友元关系的限制 9.6 虚函数 9.6.1 虚函数的概念 9.6.2 使用指向类对象的指针 9.6.3 使用引用处理虚函数 9.6.4 纯虚函数 9.6.5 抽象类 9.6.6 间接基类 9.6.7 虚析构函数 9.7 类类型之间的强制转换 9.8 嵌套类 9.9 C++/CLI编程 9.9.1 C++/CLI类的继承 9.9.2 接口类 9.9.3 定义接口类 9.9.4 类和程序集 9.9.5 被指定为new的函数 9.9.6 委托和事件 9.9.7 引用类的析构函数和结束函数 9.9.8 通用类 9.10 小结 9.11 练习 第10章 调试技术 10.1 理解调试 10.1.1 程序故障 10.1.2 常见故障 10.2 基本的调试操作 10.2.1 设置断点 10.2.2 设置跟踪点 10.2.3 启动调试模式 10.2.4 修改变量的值 10.3 添加调试代码 10.3.1 使用断言 10.3.2 添加自己的调试代码 10.4 调试程序 10.4.1 调用堆栈 10.4.2 单步执行到出错位置 10.5 测试扩展的类 10.6 调试动态内存 10.6.1 检查自由存储器的函数 10.6.2 控制自由存储器的调试操作 10.6.3 自由存储器的调试输出 10.7 调试C++/CLI程序 10.8 小结 第11章 Windows编程的概念 11.1 Windows编程基础 11.1.1窗口的元素 11.1.2 Windows程序与操作系统 11.1.3事件驱动型程序 11.1.4 Windows消息 11.1.5 WindowsAPI 11.1.6.Windows数据类型 11.1.7 Windows程序中的符号 11.2 Windows程序的结构 11.2.1 WinMain()函数 11.2.2消息处理函数 11.2.3简单的Windows程序 11.3 Windows程序的组织 11.4 MFC 11.4.1 MFC标记法 11.4.2 MFC程序的组织方式 11.5使用Windows Forms 11.6小结 第12章 使用MFC编写Windows程序 12.1 MFC的文档,视图概念 12.1.1 文档的概念 12.1.2 文档界面 12.1.3 视图的概念 12.1.4 连接文档和视图 12.1.5 应用程序和MFC 12.2 创建MFC应用程序 12.2.1 创建SDI应用程序 12.2.2 MFCApplicationwizard的输出 12.2.3 创建MDI应用程序 12.3 小结 12.4 练习 第13章 处理菜单和工具栏 13.1 与Windows进行通信 13.1.1 了解消息映射 13.1.2 消息类别 13.1.3 处理程序中的消息 13.2 扩充Sketcher程序 13.3 菜单的元素 13.4 为菜单消息添加处理程序 13.4.1 选择处理菜单消息的类 13.4.2 创建菜单消息函数 13.4.3 编写菜单消息函数的代码 13.4.4 添加更新用户界面的消息处理程序 13.5 添加工具栏按钮 13.5.1 编辑工具栏按钮的属性 13.5.2 练习使用工具栏按钮 13.5.3 添加工具提示 13.6 小结 13.7 练习题 第14章 在窗口中绘图 14.1 窗口绘图的基础知识 14.1.1 窗口客户区 14.1.2 Windows图形设备界面 14.2 Visual C++中的绘图机制 14.2.1 应用程序中的视图类 14.2.2 CDC类 14.3 实际绘制图形 14.4 对鼠标进行编程 14.4.1 鼠标发出的消息 14.4.2 鼠标消息处理程序 14.4.3 使用鼠标绘图 14.5 练习使用Sketcher程序 14.5.1 运行这个示例 14.5.2 捕获鼠标消息 14.6 小结 14.7 练习题 第15章 创建文档和改进视图 15.1 什么是集合类 15.1.1 集合的类型 15.1.2 类型安全的集合类 15.1.3 对象集合 15.1.4 类型化指针集合 15.2 使用CList模板类 15.2.1 绘制曲线 15.2.2 定义CCurve类 15.2.3 实现CCurve类 15.2.4 练习使用CCurve类 15.3 创建文档 15.4 改进视图 15.4.1 更新多个视图 15.4.2 滚动视图 15.4.3 使用MM_LOENGLISH映射模式 15.5 删除和移动形状 15.6 实现上下文菜单 15.6.1 关联菜单和类 15.6.2 选择上下文菜单 15.6.3 醒目显示元素 15.6.4 处理菜单消息 15.7 处理被屏蔽的元素 15.8 小结 15.9 练习 第16章 使用对话框和控件 16.1 理解对话框 16.2 理解控件 16.3 创建对话框资源 16.4 对话框的编程 16.4.1 添加对话框类 16.4.2 模态和非模态对话框 16.4.3 显示对话框 16.5 支持对话框控件 16.5.1 初始化控件 16.5.2 处理单选按钮消息 16.6 完成对话框的操作 16.6.1 给文档类添加存储线宽的成员 16.6.2 给元素添加线宽 16.6.3 在视图中创建元素 16.6.4 练习使用对话框 16.7 使用微调按钮控件 16.7.1 添加Scale菜单项和工具栏按钮 16.7.2 创建微调按钮 16.7.3 生成比例对话框类 16.7.4 显示微调按钮 16.8 使用比例系数 16.8.1 可缩放的映射模式 16.8.2 设置文档的大小 16.8.3 设置映射模式 16.8.4 同时实现滚动与缩放 16.9 使用状态栏 16.10 使用列表框 16.10.1 删除比例对话框 16.1O.2 创建列表框控件 16.11 使用编辑框控件 16.11.1 创建编辑框资源 16.11.2 创建对话框类 16.11.3 添加Text菜单项 16.11.4 定义文本元素 16.11.5 实现CText类 16.11.6 创建文本元素 16.12 小结 16.13 练习 第17章 存储和打印文档 17.1 了解串行化 17.2 串行化文档 17.2.1 文档类定义中的串行化 17.2.2 丈档类实现中的串行化 17.2.3 基于CObject的类的功能 17.2.4 串行化的工作方式 17.2.5 如何实现类的串行化 17.3 应用串行化 17.3.1 记录文档修改 17.3.2 串行化文档 17.3.3 串行化元素类 17.4 练习串行化 17.5 移动文本 17.6 打印文档 17.7 实现多页打印 17.7.1 获取文档的总尺寸 17.7.2 存储打印数据 17.7.3 准备打印 17.7.4 打印后的清除 17.7.5 准备设备上下文 17.7.6 打印文档 17.7.7 获得文档的打印输出 17.8 小结 17.9 练习题 第18章 编写自己的DLL 18.1 了解DLL 18.1.1 DLL的工作方式 18.1.2 DLL的内容 18.1.3 DLL变体 18.2 决定放入DLL的内容 18.3 编写DLL 18.3.1 编写和使用扩展DLL 18.3.2 从DLL中导出变量和函数 18.3.3 将符号导入程序 18.3.4 实现符号从DLL的导出 18.4 小结 18.5 练习题 第19章 连接到数据源 19.1 数据库基础知识 19.2 SQL 19.2.1 使用SQL检索数据 19.2.2 使用SQL连接表 19.2.3 对记录进行排序 19.3 MFC中的数据库支持 19.4 创建数据库应用程序 19.4.1 注册ODBC数据库 19.4.2 生成MFC ODBC程序 19.4.3 了解程序结构 19.4.4 示例练习 19.5 对记录集进行排序 19.6 使用另一个记录集对象 19.6.1 添加记录集类 19.6.2 添加记录集的视图类 19.6.3 定制记录集 19.6.4 访问多个表视图 19.6.5 查看产品的订单 19.7 查看客户的详细情况 19.7.1 添加客户记录集 19.7.2 创建客户对话框资源 19.7.3 创建客户视图类 19.7.4 添加过滤器 19.7.5 实现过滤器参数 19.7.6 链接订单对话框和客户对话框 19.7.7 练习使用数据库查看器 19.8 小结 19.9 练习题 第20章 更新数据源 20.1 更新操作 20.1.1 CRecordset更新操作 20.1.2 事务 20.2 简单的更新示例 20.3 管理更新过程 20.4 向表中添加行 20.4.1 订单录入过程 20.4.2 创建资源 20.4.3 创建记录集 20.4.4 创建记录集视图 20.4.5 给对话框资源添加控件 20.4.6 实现对话框切换 20.4.7 创建订单ID 20.4.8 存储订单数据 20.4.9 为订单选择产品 20.4.10 添加新订单 20.5 小结 20.6 练习 第21章 使用Windows Forms的应用程序 21.1 理解Wqndows Forms 21.2 理解Windows Forms应用程序 21.2.1 修改窗体的属性 21.2.2 如何启动应用程序 21.3 定制应用程序GUI 21.3.1 给窗体添加控件 21.3.2 添加选项卡控件 21.3.3 使用CroupBox控件 21.3.4 使用Button控件 21.3.5 使用WebBrowser控件 21.3.6 Winning应用程序的操作 21.3.7 添加上下文菜单 21.3.8 创建事件处理程序 21.3.9 处理Limits菜单的事件 21.3.10 创建对话框 21.3.11 使用对话框 21.3.12 添加第二个对话框 21.3.13 实现Help|About菜单项 21.3.14 处理按钮单击事件 21.3.15 响应上下文莱单 21.4 小结 21.5 练习 第22章 在Windows Forms应用程序中访问数据源 22.1 使用数据源 22.2 访问并显示数据 22.3 使用DataGridView控件 22.4 在无约束模式中使用DataGridView控件 22.5 定制DataGridView控件 22.5.1 定制题头单元格 22.5.2 定制非题头单元格 22.5.3 动态设置单元格样式 22.6 使用约束模式 22.7 BindingSource组件 22.8 使用BindingNavigator控件 22.9 绑定到单独的控件 22.10 使用多个表 22.11 小结 22.12 练习 附录A C++关键字 A1 ISO/ANSI C++关键宇 A2 C++/CLI关键字 附录B ASCII码

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

何以过春秋

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值