博文
多重继承和虚基类(2009-02-28 21:21:00)
摘要://派生类成员函数对基类成员函数的覆盖
#include "stdafx.h"
#include<iostream>
class A
{
public:
void Show()
{
std::cout<<"A::Show\n";
}
};
class B:public A
{
public:
void Show()
{
std::cout<<"B::Show\n";
}
void Display()
{
Show();//调用派生类B的成员函数,覆盖掉继承自基类的Show()成员方法
A::Show();//调用基类A的成员函数
}
};
int _tmain(int argc, _TCHAR* argv[])
{
A a;
B b;
a.Show();
b.Show();
b.Display();
std::cin.get......
C++继承(2009-02-28 21:16:00)
摘要:(1)派生方式
class <派生类名>:[派生方式]<基类名>
{
//派生类新增成员的声明
}
在上述派生类的定义中,“派生方式”决定了基类成员在派生类中的访问权限。派生的方式共有public、private、protected(默认派生方式为private)。
虽然派生类继承了基类的所有成员,但是为了不破坏基类的封装性,无论采用哪种继承方式,基类的私有(private)成员在派生类中都是不可见的,即不允许在派生类的成员函数中访问基类的私有成员。
class Point
{
private:
int prt;
public:
void setPrt(int t)
{
prt=t;
}
public:
void getPrt()
{
std::cout<<prt;
}
};
class Circle:public Point
{
public:
void visitPrt()
{
std::cout<<prt; //提示出错
getPrt();//利用基类封装的公有接口间接访问基类私有成员
}&......
C++构造函数和析构函数(2009-02-26 18:39:00)
摘要:(1)构造函数、析构函数与赋值函数
构造函数、析构函数与赋值函数是每个类最基本的函数。它们太普通以致让人容易麻痹大意,
其实这些貌似简单的函数就象没有顶盖的下水道那样危险。
每个类只有一个析构函数和一个赋值函数,但可以有多个构造函数(包含一个拷贝构造函数,其它的称为普通构造函数)。对于任意一个类A,如果不想编写上述函数,C++编译器将自动为A 产生四个缺省的函数,例如:
A(void); // 缺省的无参数构造函数
A(const A &a); // 缺省的拷贝构造函数
~A(void); // 缺省的析构函数
A & operate =(const A &a); // 缺省的赋值函数
这不禁让人疑惑,既然能自动生成函数,为什么还要程序员编写?原因如下:
<1>如果使用“缺省的无参数构造函数”和“缺省的析构函数”,等于放弃了自主“初始化”和“清除”的机会,C++发明人Stroustrup 的好心好意白费了。
<2>“缺省的拷贝构造函数”和“缺省的赋值函数”均采用“位拷贝”而非“值拷贝”的方式来实现,倘若类中含有指针变量,这两个函数注定将出错。
对于那些没有吃够苦头的C++程序员,如果他说编写构造函数、析构函数与赋值函数很容易,可以不用动脑筋,表明他的认识还比较肤浅,水平有待于提高。
下面以类String 的设计与实现为例,深入阐述被很多教科书忽视了的道理。String的结构如下:
class String
{
public:
String(const char *str = NULL); // 普通构造函数
String(const String &other); // 拷贝构造函数
~ String(void); // 析构函数
String & operate =(const String &other); // 赋值函数
private:
char *m_data; // 用于保......
define、const&inline(2009-02-26 18:28:00)
摘要:(1)#define宏的用法
#define用宏名代替一个字符串,这样便于修改,提高了程序的可移植性。编译器在编译预
处理时只对宏做文本替换,而不进行类型检查,所以替换后可能产生一些副作用。
带参数的宏类似于函数调用,但是宏替换不是函数,二者不是一回事。
#define square(n) (n)*(n)
for(int i=1;i<6;i++) printf("%d\n",square(i++));
以上语句执行输出的结果为:1,9,25
因为square(i++)被替换为(i++)*( i++),第一次执行后,i执行两次自增变为3……。
const的常量是一个Run-Time的概念,他在程序中确确实实的存在可以被调用、传递。而#define常量则是一个Compile-Time概念,它的生命周期止于编译期:在实际程序中他只是一个常数、一个命令中的参数,没有实际的存在。故使用宏,程序运行得较快,宏替换不占运行时间,只占编译时间;而使用函数调用,占有空间较小。
(2)const声明常量的用法
int const a;<==>const int a;//声明一个整数a,其值不可修改。
const int a=15;//可以声明时进行初始化以保常值。
int *pi;//pi是一个普通的指向整型的指针。
int const *pci;//const修饰的是int,pci是一个指向整型常量的指针,可以修改指针的值,但是不能修改它所指向的值,即无论pci指向哪一个sizeof(int)内存单元,其单元内容都是常整值。
int * const cpi;//const修饰的是int*,cpi是一个指向整型的常量指针,此时指针是常量,它的值无法修改,但可以修改它所指向的整型值,即无论cpi指向一个固定的sizeof(int)内存单元,其单元内容可以改变。
int const *const cpci;//第一个const修饰int,即后面的指针cpci指向整型常量;第二个const修饰int*,即指针也是常量。故无论是指针本身还是它所指向的值都是常量,不允许修改。
以下为示例程序片段:
int n1 = 2009;
int n2 = 2012;
&nbs......
字符串与指针(2009-02-26 11:07:00)
摘要:
字符'\0'和'0'的区别
字符’0’对应的ASCII码为十六进制30;而’\0’即为ASCII码中的0,其对应字符空字符NUL。
char c='\0';çèchar c=0;//NUL
char c='0';çèchar c=48;//0
最典型如memset函数:void *memset( void *buffer, int ch, size_t count );
将一段长为count字节的内存块初始化为ASCII码值0,字符为NUL:
memset(pBuffer, '\0', sizeof(pBuffer) );çèmemset(pBuffer, 0, sizeof(pBuffer) );
字符串
字符串常量是双引号括起的任意字符序列。如:"Hello World","Fantasy","Please enter
your full name:",……
C语言没有专门定义字符串数据类型(如其他语言中的string) ,所谓的字符串,只是对字符数组的一种特殊应用而已,它用以'\0'结尾的字符数组来表示一个逻辑意义上的字符串。
在字符串常量中,显然不能直接写双引号,因为这将被认为是字符串的结束。转义序列在字符串常量中要包含双引号,需要用“\"”表示。如:"Hello \"Accp\""
与字符数组不同的是:在存完字符串常量的所有字符之后,还要另存一个空字符'\0'作为结束的标志,空字符是ASCII码值为0的字符,C语言中用'\0'标识字符串的结束,所以也称为结束符。如果在程序里写了字符串:char hello[]="HELLO"或{"HELLO"};虽然只有5个字符,在内存中却需要占用6个字节存储,其中'\0'表示空字符。存储情况如:
H
E
L
L
O
\0
5005
5006
5007
5008
5009......
函数指针与指针传参(2009-02-26 10:51:00)
摘要:1.函数指针
函数指针形式说明如下:
<类型>(*指针变量名)();
其调用格式如下所示:
int max(int x,int y); //定义函数max
int (*funcp)();//定义返回值为整型的函数指针
funcp=max;//将函数名(函数入口地址)传给funcp,使其指向函数
//上两行代码等价于int (*funcp)(int,int)=&max;
(*funcp)(3,4));//利用函数指针调用函数: (*funcp)=max;
若把指向函数的指针作为参数传递到其他函数中,则可以编一个通用的函数来完成各种专用的功能,每次调用函数时给出不同的函数名作为实参即可,这样大大增加了函数使用的灵活性。C#中的委托类似函数指针。
int max(int x,int y){ return (x>y?x:y);}
int min(int x,int y){ return (x<y?x:y);}
int add(int x,int y){ return x+y;}
process(int x,int y,int(*funcp)())
{ printf("%d\n",(*funcp)(x,y));}
void main()
{
int a,b;
printf("Please input int a and int b:\n");
scanf("%d,%d",&a,&b); printf("\n");
printf("max(a,b)=");process(a,b,max);
printf("min(a,b)=");process(a,b,min);
printf("add(a,b)=");process(a,b,add);
}
函数指针是Windows回......
数组与指针(2009-02-26 10:48:00)
摘要: 任何能由数组下标完成的操作也可由指针来完成,一个不带下标的数组名就是一个指向此数组的指针,在C语言中数组名就是数组的地址。当一个指针变量被初始化为数组名时,就说该指针变量指向了数组。
char str[20],*pstr;
pstr=str等价于pstr=&str[0]; //指针被置为数组第一个元素的地址
访问数组第6个元素:str[5],pstr[5], *(str+5),*(pstr+5)。
值得注意的是pstr是一个可以变化的指针变量,因此pstr++;++pstr; pstr+=5都是正确的,而str是一个常数。因为数组一经说明,数组的地址也就被固定了,故str++;++str; str+=5都是错误的。
编译系统在处理str[i]时,实际上是将数组元素的形式str[i]转换为*(str+i),然后再进行运算的。相应的引用二维数组a[i][j]则等价于(*(a+i))[j]或*(*(a+i)+j),通常式子*(a+i)+j是用来计算元素所在内存地址,并不是它的内容。
int a[3][4]; //二维整型数组
int (*p)[4]; //整型指针数组
p=a;
则p+1不是指向a[0][1],而是指向a[1]。这是p的增值以一维数组长度为单位。
假设是这么一个数组:
int arr[20];
则arr 的内存示意图为:
(数组 arr 的内存示意)
和指针变量相比, 数组没有一个单独的内存空间而存放其内存地址。即:指针变量p是一个独立的变量,只不过它的值指向另一段连续的内存空间;而数组arr,本身代表的就是一段连续空间。
如果拿房间来比喻。指针和数组都是存放地址。只不过,指针是你口袋里的那本通讯录上写着的地址,你可以随时改变它的内容,甚至擦除。而数组是你家门楣上钉着的地址,你家原来是“复兴路甲108号”,你绝对不能趁月黑天高,把它涂改为“唐宁街10号”。
数组是“实”的地址,不能改变。当你和定义一个数组,则这个数组就得根据它在内存中的位置,得到一个地址,如上图中的“0x1A000000”。......
指针(2009-02-26 10:37:00)
摘要:1.指针,变量的指针,指针变量
由于通过地址能找到所需的变量单元,我们可以说,地址“指向该变量单元”,在C语言中,将地址形象化的称为“指针”,一个变量的地址称为该“变量的指针”,意思是通过它能找到以它为地址的内存单元。指针的值是指针本身存储的数值,这个值将被编译器当作一个地址,而不是一个一般的数值。
在32位程序里,所有类型的指针的值都是一个32位整数。因为32位机中的程序里内存地址全都是32位长,即sizeof(pointer)的值总为4—指针本身占据了4个字节的长度。在64位机中,sizeof(pointer)的值为8.
如果一个变量专门用来存放另一个变量的地址,则它称为“指针变量”,我们说它用来存放指针。定义了一个变量p,它用来保存另一个变量var的地址,这样的p就是指向var的指针变量。
指针变量也是变量,其定义格式为:类型标识符 * 指针标识符,*号为(地址解析符,表示“指向……的指针”,可以左结合,也可以右结合,其中类型标识符 *为指针的类型,类型标识符为指针所指向的类型。例如:
char *pc; pc具有char *类型,即pc指向char类型的变量,以1个字节为一个存取单元。
int *pi; pi具有int *类型,即pi指向int类型的变量,以4个字节为一个存取单元。
float* pf; pf具有float *类型,即pf指向float类型的变量,以4个字节为一个存取单元。
char *pc="hello"; <==>char *pc;pc="hello";
2.指针变量的引用
C语言中对指针变量的引用主要通过运算符“&”和“*”来实现的。
&——取变量的地址。
*——取指针变量所指向的变量的值。
观察下面的程序段:
int x,y,*p;//定义整型变量x、y和整型指针变量p
x=168;//初始化x
p=&x;//初始化p
y=*p;//初始化y
上述内存变化情况如图所示:
若int a=168; int *p=&a;则*&a表示变量a本身,而&......
内存和地址(2009-02-26 10:26:00)
摘要:计算机的内存由数以亿万计的位(bit)组成,每个位可以容纳值0和1。由于一个位所能表示的值得范围太有限,所以单独的位的用处不大,通常许多位组成一组作为一个存储单位,这样就可以存储范围较大的值。以下展示了现实机器中的一些内存位置:
100
101
102
103
104
105
106
107
这些位置的每一个都被称为字节(byte),每个字节都包含了存储一个字符所需要的位数。在很多现代的机器上,每个字节包含8个位,可以存储无符号值0至255,或者有符号值-128只127。实际内存中每个位置总是包含一些值。每个字节通过地址来标识,如上图中的数字所示。
为了存储更大的值,我们把两个或更多个字节合在一起作为一个更大的内存单位。例如,很多机器以字为单位存储整数,每个字一般由2或4个字节组成。下图所示内存位置与上图相同,但这次它以4个字节的字来表示。
100
104
尽管一个字(INT32)包含了4个字节,它仍然只有一个地址。至于它的地址是它最左边那个字节的位置还是最右边那个字节的位置,不同的机器有不同的规定。另一个需要注意的硬件事项是边界对齐(boundary alignment)。在要求边界对齐的机器上,整型值存储的起始位置只能是某些特定的字节,通常是2或4的倍数。但这些问题是硬件设计者的事情,它们很少影响C程序员。我们只对两件事情感兴趣:
1).内存中的每个位置由一个独一无二的地址标识。
2).内存中的每个位置都包含一个值。
在实际程序中我们经常根据需要借助强大的指针对一块内存进行操作,再按字节组合析取出所需数据,平时的程序中经常用到通用指针void*(LPVOID)的妙处就在于可以按照需要操作一块内存,以取所需值类型。
以下测试小程序向我们清晰的展示了三种字节析取情况:
#include <stdio.h>
#include <......
include头文件包含(2009-02-26 10:14:00)
摘要:(1)#include文件包含指令的两种形式
函数调用出现在函数定义之前,或者调用在其他文件(如系统库)定义的函数,必须先进行函数声明。系统函数按功能被分成几大类,对应每类函数有一个头文件,其中给出了这一类函数的原型声明。
#include <stdlib.h>//声明公共的系统标准函数
#include "MyPrg.h"//声明用户自定义的常量、变量及函数
第一种形式的#include指令告诉编译预处理程序在编译器自带的或外部库的头文件中搜索要嵌入的文件,它们一般是系统提供的公共头文件,存放在系统目录中的include文件夹下,如VS2005中的C标准库函数C:\Program Files\Microsoft Visual Studio 8\VC\include\stdlib.h(cstdlib)
第二种形式的#include指令告诉编译预处理指令现在当前子目录搜索要嵌入的文件,如果没有找到文件,则在去搜索编译器自带或外部库的头文件。
一般库函数的头文件一般用第一种形式因为这种访问比较快。
(2)头文件交叉包含
//A.h
#include "B.h"
//B.h
#include "A.h"
每个.h 都用条件编译限制
#ifndef A_H
#define A_H
//头文件类容
#endif
这样只要不是真的有重复定义的类、变量,那么交叉包含就不会有问题了.
......