第一章 C++入门
C++ 程序经过编辑,编译和连接,产生 exe文件。
C++程序由main()函数开始运行。
函数调用前必须先声明。
函数定义包含函数声明。函数定义由函数头和函数体组成。
一个语句可以写在多个程序行上,一个程序行可以写多个语句,以封号结束。
C++通过标准输入输出流进行输入输出。
程序设计的目标在正确的前提下,其重要性依次排列为:可读,可维护,可移植和高效。
第二章 基本数据类型与输入输出
C++在ANSI C中规定的32个关键字上增加了29个关键字。
程序中用到的标识符不能与C++关键字有相同的拼法和大小写,关键字也不能重新定义。
C++中数据类型分为基本数据类型和用户定义数据类型。
基本数据类型有char 、int 、float 、double 、wchar_t和bool。
非基本数据类型包括,数组,指针,空类型,结构,联合,枚举和类。
数据类型修饰符用来改变基本数据类型的意义,有long,short,signed,unsigned。
Tips:short只能修饰int,short int 可简写成 short。
long只能修饰int和double 修饰int时一般为4个字节,double时为10个字节。
(具体可能因系统不同而有差异。如32位的机器上long int也占4个,与int一样,win32下,VC6.0中 sizeof (long double) 是8位)
unsigned 和signed只修饰char和int。
C++中,八进制以数字0开始如 0123 表示十进制的83
十六进制以0x或0X开始
常用转义字符:
\a 响铃 \t 制表符 \v 竖向跳格 \b 退格
\n 换行 \r 回车 \\ 输出”\” \” 输出“””
\’ ‘ \ddd 1-3位八进制 \x 1-2位十六进制
枚举常量:
enum COLOR {RED,BLUE,GREEN};不加说明,大括号内第一个值为0,依次递增。
常量名不是左值,所以不能放在=号的左边。
常量定以后不能重新赋值,所以定义时必须初始化。
控制符(manipulators)可以对I/O流进行格式控制,包含头文件
#include<iomanip.h>
常用控制符:
dec hex oct setfill(c) 设置填充字符为c
setprecision(n) 设置显示小数精度为n 默认输出有效位是6
setw(n) 设置域宽 ,不保留其效力,仅仅影响下一个输出 setiosflags(ios::fixed)固定浮点显示
setiosflags(ios::scientific) 指数表示 setiosflags(ios::left) 左对齐 setiosflags(ios::right) 右对齐
setiosflags(ios::skipws) 忽略导前空白 setiosflags(ios::uppercase) 16进制数大写输出
setiosflags(ios::lowercase) 16进制数小写输出 setiosflags(ios::showpoint)强制显示小数点。
setiosflags(ios::showpos) 强制显示符号
Tips:fixed于setprecison(n)合用控制小数点后位数,setprecision(n) 和scientific控制指数表示输出的位数。
printf函数
格式符 %5d 输出宽度为5,%ld 输出为常整型, %o, %x 输出十六进制的大小写取决于x的大小写。
%u 无符号10进制整数,带h表示是短整数 %c以字符方式输出,%s字符串
字符串输出时,可以有诸如:%-5.3,-表示左对齐,5是格式宽度,小数点后表示截取长度。
%f float %lf double %Lf long double
%e 指数方式输出
%g 输出浮点数,自动选取f或者e
输出%本身,用%%
第三章 表达式和语句
左值(lvalue)是能出现在赋值表达式左边的表达式,它具有存放数据的空间,且是允许存放的。
任何被转换的变量都不是左值。
操作符的优先级共有16级,一般来说,单目运算符的优先级最高,(除(),[],->,::),除了‘,’赋值语句优先级最低。
%只允许对整数操作。
一个整数类型的变量,用任何一个超过表示范围的整数初始化,得到的值为用该整数作模运算后的值。
在进行表达式计算中,转换朝表达数据能力更强的方向进行,并且总是逐个运算符进行的,即使转化局部化,例如:
float f=3.5; int n=6;long k=21; double ss=f*n+k/2;结果 ss= 31;
显示转换的过程中,如果数据类型带修饰符,则转换类型必须被括号包围。即形如:(unsigned long)1234
a++,a—都不是左值,++a,--a为左值,所以++(a++)是错误的。
注意短路表达式,会造成部分变量的值不会改变,这种情况一般发生在&&,和||连接的表达式中。
表达式和语句的区标是:表达式有值而语句没有。
副作用是以个表达式中嵌套表达式,在提供值的同时又修改值,导致了结果不可预知。
在编写表达式的时候,要尽量避免结果的摸棱两可,如果有必要,可以把一句话拆成几句来写。
Tips: C++中没有elseif这种关键字,必须拆开来写,且如果没有括号的话,后面的 else对应的是else if 后面的if.
第四章 过程化语句
while语句与do…while 的区别,while 总是先判断再执行,do..while是先执行后判断。
P.S. do..while使用的时候,while后面有封号。
为了与while 的空语句区分,一般的,do..while 语句即使只有一句也用大括号包围起来。
for 语句使用中,如果省略某一部分,但它的封号不能省略。
switch 后面括号中的表达式只能是整型,字符型或者枚举型,case后面类型必须与之匹配。且不能重复,因为case起标号的作用,否则会引起编译错误。
Switch的格式:switch(表达式)
{
case ‘值’ :表达式;break;
}
转向语句
break语句用在while do…while,for 和switch语句中。从最近的循环体中跳出。
continue和break区别在于,continue只结束本次循环,而不是中止整个循环,而break则是中止本循环,跳出。
第五章 函数
把相关语句组织在一起,给他们注明相应的名称,利用这种方法把程序分块,这种形式的组合就称为函数。也称例程或过程。
C++中,函数声明就是函数原型(prototype).原型不必包含参数的名字,只要包含参数的类型。即使有参数,也在声明的右括号处结束作用域。
在整个程序都可见的称为全局变量,在一个函数内部可见的,称为局部变量。
程序将OS分配给它的内存分为4个区:
(1)代码区:存放程序的代码
(2)全局数据区:存放全局数据与静态数据。建立时自动初始化为0. P.S:声明的字符串常量也放在其中
(3)堆区:存放动态数据
(4)栈区:存放局部数据,程序不会自动对其初始化。
全局变量是程序级的,所以对所有函数都是可见的,但由于C++支持在任何位置定义变量,所以 全局变量定义之前的函数都不会知道改变量。
函数调用时,返回值一般都放在eax中(VC6.0)。
静态变量存储在全局数据区,自动初始化为0,作用范围是分静态局部变量和静态全局变量,且只初始化一次。
函数调用机制:C++中代码是独立的,即同样的代码,由于调用点,调用时的状态,返回点不同,可以看做函数的一个副本,与调用函数的代码无关,被调用函数的栈空间独立与调用函数,所以之间的数据也是无关的,这中特性决定了可以使用递归调用。
对递归的理解:在使用递归时,把自身函数看做是别的函数,只要按照正确的调用方法调用即可,不必考虑那么多。
inline 函数必须先声明后调用,实际上只要在声明时加上inline关键字即可,编译器根据原型而不是函数定义来判断函数类型。
内联函数对函数体有一些限制:
(1) 函数体中不能含有复杂的控制语句,如switch,while,如果存在,编译器会把内联函数视为普通函数。
(2) 递归也不可以,内联函数只适用与小函数,大函数使用没有必要。
不同类型上作不同运算而且又使用同样名字的情况,叫做重载。
重载函数匹配规则:
(1)寻找严格匹配的。
(2)内部转化寻求匹配。
(3)用户定义的转换,如果能查出唯一的一组转换就可以使用。
重载函数至少要在参数个数,参数类型和参数顺序上有所不同才行,仅仅是返回类型不同是不行的。
给函数使用默认参数的时候,若函数声明和函数定义是分开的,则默认参数必须加在声明上,定义上不能够加默认参数。
默认参数在形参分布中,应该从右向左匹配,函数调用时候从左向右匹配。
第六章 程序结构
带extern的变量声明不占用空间,它只是告诉编译器,该变量不在本文件中分配空间在其他文件中分配空间(书上虽然是这么说,但是如果一个文件中有extern int n 和int n编译是可以通过的。同样,如果每个文件仅仅是有extern编译也是可以通的过的。如果本文件中如果有 extern修饰的变量话,编译器放弃对其存在的检查,编译器的检查是文件级别的)。
函数声明默认使用extern修饰符,如extern void fn1();告诉编译器在所有组成该程序的文件中找该函数。(这只有在连接的时候才去查找函数的存在)。
带extern的变量说明是声明,不是变量定义,如果仅有声明的话,在连接阶段会报错。
static全局变量是文件级的,他可以在定义它的文件中可见,外文件不可见,同样的,静态函数也只能在本文件里被调用,如果其他文件想调用改函数,可以先调用一个与静态函数在同一个文件中定义的函数,再由它来调用这个静态函数。
在文件域作用下声明的inline函数默认为static存储类型,const常量也是,它们如果加上关键字extern,则为外部存储类型。
C++的作用域:
局部作用域(又叫块作用域)
函数作用域 :
标号是唯一具有函数作用域的标识符
goto和switch使用标号,但这种跳转不能从声明作用域外的地方直接跳转到声明作用域内的地方,这样会引起变量不能初始化。
函数原型作用域:开始于函数原型声明的作括号,结束于函数原型声明的右括号。
文件作用域:作用域从函数声明点开始,作用到文件结束。
类作用域
关于头文件作用域的问题:
由于头文件是文件作用域的,在编译的时候加入包含它源文件中,但由于编译器的检查是文件级别的,这就造成了在多文件的工程中,如果你要在不同的文件中使用同一个头文件里的函数的话,不得不多次包含同一个头文件。因此如果讲类的成员函数的实现放在头文件中是很不明智的做法,这样会造成代码的重复编译。
利用{}我们可以使包在大括号之间的变量具有块作用域,这样在同一个函数中可以用同一个变量名表示不同的意义,且}结束后,上级的同名变量可见。不必为代码块定义名称,可以使用形如:
{
int a =0;
{
int a=3;
}
cout<<a<<endl;
}这样的代码段
如果块作用域内的变量使同名全局变量不可见,可以使用::变量名 访问全局变量。
一个函数中能够访问的数据只限数据区(保存的是文件级的全局和静态变量以及局部静态变量)和本函数的栈区,如果想直接用asm访问其他函数栈区的内容,当我没说。
#inlcude中<>和””的区别:查找头文件的时候前者只查找C++标准目录,而””查找用户当前文件所在目录然后查找标准目录。
P.S. 可以在VC6.0中的工具\选项\Directories中更改默认include路径
#define C中的#define最常用的是建立常量,在C++中已经被const取代,带参数的宏被inline取代,还有个作用是条件编译。
条件编译指令:
#if #else #elif #endif #ifdef #ifndef #undef
第七章 数组
数组是由若干个同类型的变量组成的集合。 数组的下标是数组元素到数组开始偏移量。
数组定义的方括号中,常量表达式可以包含枚举常量和字符常量,但不能包含const定义的常量数组中的某一数组元素。
编程时如果定义一个很大的数组,可以通过将其定义成静态或者全局数组来解决,或在在栈区中动态的分配。不能用常量数组中的元素来定义一定大小的数组是因为它在编译时不能用常量来代替,因为取值是一个操作,不是值本身,[]就是一个操作。属于RT范畴。
几种错误的数组初始化方式:
int array[5]={1,2,,3,4,5,6} //初始化值个数多于数组元素个数
int array1[5]={1,,2,3,4} //初始化值不能省略(C中可以省略)
int array2[5]={1,2,3,} //初始化值不能省略,去掉最后一个逗号就可以了
int array3[]={} //格式错误
初始化的值的个数可以少于数组元素个数,未被初始化的数组元素,若是全局或静态数组则被初始化为0,否则为不确定。
在初始化数组的时候可以省略大括号里面的数组元素个数,编译时编译器通过计算大括号里元素的个数得到数组大小,例如:
int a[]={1,2,3,4,5} 数组大小为sizeof(a)/sizeof(int) 有时候这个技巧很有用。
无论何时将数组作为参数传递给函数,实际上只是把数组的地址传递给函数,所以传递时要附加数组大小的信息。
memset(void *,int ,unsigned) 作用:一个字节一个字节的设置数组的值,第一个参数是地址,第二个是值的大小,第三个参数是需要设定的字节数。P.S.:写Shellcode的时候经常会用到 J
对二维数组所有元素赋初值,定义时可省略第一位大小,如:
int a[][4]={1,2,3,4,5,6,7,8};编译器自动识别其为2行4列数组。
若仅对部分赋值,则要分行写,如:
static int a[][4]={{1,2,3},{},{4,5}};
对二维数组进行降维处理的时候,传递给其他函数的是地址和参数个数,不能传递数组的名字,因为数组名表示的是二维数组的首地址,他与首地址虽然相同,但操作不同,传递时应用&a[0][0]取第一个元素的地址,作为参数传递。
第八章 指针
指针定义中,比如
int *iPtr;
int iCount=18;
iPtr=&iCount;
也可以写成一句:int iCount=18,*iPtr=&iCount
上例中,可以使用*iPtr来间接引用指针,获得该指针指向变量的内容。
放在可执行语句的指针之前,称之为间接引用操作符,放在指针定义中,称为指针定义符。
不能直接把指针附给一个整型数,即使他们都占用4个字节。
如:int *iPtr;
*iPtr=&iCount;//error
但是可以使用强制转换 *iPtr=(int)&iCount;
指针是有类型的,给指针赋值,不但必须是一个地址,而且应该是一个与该指针类型相符的变量或常量的地址。以便程序在使用指针的时候确定引用的单位是一个字,字节或者更多的单位。
由于指针是有某种数据类型的,所以其在自加或者自减时都会使地址值增加或减少所指向的数据类型的大小。比如:
int *ptr;
ptr++;
ptr里面的地址就增加了4(32位机)
Tips:只有加法和减法可以用于指针运算。*ptr++,由于++和*的优先级相同,所以相当于*(ptr++)
数组名称是指针常量,区别于指针变量,所以如果有int a[4],a++就会出错。对编译器来说,数组名表示内存中分配给数组了固定的位置,修改了这个数组名,就会丢失数组空间。
堆内存的分配:堆允许程序在运行时(而不是在编译时),申请某个大小的内存空间。
C中使用 void *malloc(size_t size)函数动态分配内存大小,由于malloc不知道用这些分配内存做什么,所以必须作显示转化才能使用. void free(void *)用来释放由malloc申请的内存,括号内是malloc函数返回的地址。
new 和delete是C++中用来动态申请内存的函数,返回申请类型的指针,所以不用强制转换,需要注意的是在使用delete删除数组是要形如:delete [] array;
const使用过程中需要注意的一些地方。
Const我们经常要用到,但有时不注意就会导致程序异常,而你还不知道怎么回事,一些是在用const要注意的:
1. 函数返回值为const时,返回的东西赋给一个类型相同的标示,其不能为左值;
2. 用const定义的int可用来开辟数组,但const定义的常量数组中的元素,不能用来定义数组。原因见上。
3. const int *i; int const *i; int * const i; 前两个功能相同,说明I所指向的内容不变;最后一 个说明指针指向的地址不变,但内容可变。《Thinking in C++》这本书中有变量定义的结合准则,可以参考下。
4. 类中的const成员函数,定义为在原型后加const。常量函数不能修改类中的任何属性。但有两种方法可以修改。
1) {(yourclass *)this->member = values;}
2) 将一个成员定义成mutable即可被常量函数修改。VC内部标识符,不是标准C++中定义的
5. 类中的常量const 类型的,不能在类中被用来定义数组。而enum {ONE=100; TWO=2};定义的ONE、TWO 却可以。通常的enum定义的置分配问题:enum A{ L=9, Z};此时Z的值为10
const指针 const做定语,呵呵
(1)指向常量的指针(常量指针)指针定义语句前或类型后*前加const,表示指向的对象是常量。
一个指针如果被定义成常量指针表示所指对象的值不能通过此指针通过解引用改变,但是如果此变量还有其他指针或它本身就有变量名,则可以改变其值。如:
int count=8;
const int *iptr=&count;
*iptr=9;//error常量指针,不能改变
count=9;//ok可以改变,因为count不是const类型的。
即使改变指针的指向,新指向的内容仍然不能够通过该指针改变。这一点在函数传递中比较有用,它能够防止对不想被改变的内容的误操作。
总之,定义指向常量的指针只限制指针的间接访问操作,而不能规定指针指向的值本身的操作规定性。
(2)指针常量,在指针定义语句指针变量名前加const定义指针常量。
由于指针变量里存储的地址不能够改变(即只能够指向内存中的固定地址),所以在定义的时候必须初始化。与初始化常量的意义一样。
由于*pi是可以改变的,所以它除了不能改变指向外其他属性于普通指针相同,所以不能用常量的地址附给pi这样会导致*pi可以改变常量的值。编译通不过。
(3)指向常量的指针常量(常量指针常量)
如:
const const int * const pi=&c; //ok
指针作为参数传递的时候尽管函数的形参如:int a[]的形式,但C++已经明确告诉作为参数传递的数组就是指针变量,所以a可以作为左值进行a++等计算。
指针函数:返回指针的函数,但它不能把它在累不说明的具有局部作用域的数据地址作为返回值。
指针函数可以返回全局或者静态变量的地址,但不能返回局部变量的地址。
void指针是空指针,仅仅是一个地址,它不能进行指针运算,也不能进行间接引用(用*取值).
其它指针都包含有地址信息,所以把其它指针赋给void*是合法的,反之则不行,会引起信息丢失,需要显示转换。
memcpy(dest,src,size_t size),从src拷贝size个字节到dest所指向的内存中。
原型:void* memcpy(void *d,const void *s,size_t n);
与字符数组不同的是,字符串常量是保存在data区的const区中的。
所以如下的改变是不合法的:
char *a=”123456”;
a[3]=’3’;
编译连接都不会出错,但是运行时报错。不知道为什么,可能是a 是指向const的指针吧,另:书中168页ch8_16在VC6.0下显示相等,跟进去,发现确实地址相同,可能是编译器为了节省空间,且又是const的,所以对于相同的字符串常量使用了相同的地址。不知道BC下怎么样。
C++中不能直接对字符数组赋予一个字符串,因为数组名是常量指针,不是左值,即实际上常量字符串右边是一个地址。
可以用strcpy来给一个字符数组赋’\0’结尾的字符串。
指针数组,数组元素均为指针,char * proname[]={“Fortran”,”C”,”C++”};
需要注意的是proname是二级指针,所以传递参数的时候传递的是二级指针。
在使用cout进行输出字符串,参数为指针时可直接用cout<<指针名,若要输出指针的值则需要显示转换,cout<<(int )指针名,这是因为cout是一个重载函数。
函数指针定义 int (* fun)(char a,char b); 定义了fun是个指向函数的指针,其有两个参数,返回int .
因为()优先级大于*,所以int * fun(char a,char b); 中fun 先与右边的()结合构成函数声明,然后确定返回值为int *.
同理int (* p)[]是指向一维数组的指针。
函数指针间的赋值要求函数类型一致(包括,参数,返回值等等)。
有了函数指针,可以使用2中方式调用函数,比如:
fp2=fn3;
fp2(5);ANSI C++标准
或者(*fp2)(5);兼容C
使用函数指针别名简化函数指针定义。
typedef int(*FUN)(int a,int b);
FUN funp;
Tips:FUN不是函数指针变量,只是个指针类型别名,通过函数指针定义,才确定函数指针funp
函数指针可以作为函数调用的参数存在,如:double sigma(double (* func)(double),double d1,double du);
就可以用sigma(cos,0.5,3.0)的形式调用。
书中提到stdlib.h里包含的一个可以对任意的数组排序的函数qsort()其原型为:
void qsort(void *,size_t nelem,size_t width,int(* fcmp)(const void*,const void *));
第一个参数是数组的地址,第二个参数是数组的元素个数,第三个是每个数组元素的大小,最后一个是比较函数,用户自行定义,使得:如果相等则返回0,前面的大返回>0的数,小于则返回<0的数。具体例子见:P180。
函数指针数组的例子,见P181。
第九章 引用
引用是个别名,当建立引用的时候程序用另一变量(目标)的名字初始化它,对引用的改动实际上就是对目标的改动。
引用不是值,不占用存储空间,声明引用时,目标的存储状态不会改变,引用只有声明,没有定义。
引用在声明的时候必须初始化,否则会产生编译错误。
“&”引用运算符只在声明的时候使用,其它时候使用都是地址操作符。
和指针一样,下面声明了一个引用,一个变量。
int& rInt,sa;
Tips:为了提高可读,一般不在同一行声明引用,指针和变量。
C++没有提供访问引用自己本身地址的方法所以用&+引用名得到的是所引用对象的地址。
引用一旦初始化,它就维系在一定的目标上,再也不分开。任何对该引用的赋值,都是对所引用维系的目标赋值,而不是将引用维系到另一个目标上。
指针是个变量,可以把它再赋值指向别的地址,但引用不同,它必须初始化,并且决不会再关联到其它不同的变量上去。
左值被引用时,形如:T&将会建立一个T类型的临时的变量或者对象,需要注意的是,左值引用的时候,需要加:
const T &这样的东西。需要注意的是:T不能是一个void,因为它本质上不是一个类型。也不能建立一个引用数组,数组名只是一个地址。
指针也是变量,可以被引用。定义指针的引用形如 nt *a; int * &p=a;
因为引用本身不是一个数据类型,所以没有引用的引用,也没有引用的指针。
引用在函数传递中隐含的传递地址,每当使用引用的时候,C++就去求该引用所含地址中的变量值。
引用带来的问题:
(1)造成值传递和引用分不清楚。
(2)由于(1)的缘故,所以无法从看到的函数调用判断其是传值传递还是引用传递,所以在同时存在引用和传值传递的时候重载将不能够进行。
函数返回值时,要生成值的副本,而引用返回值时,不生成值的副本。
返回引用的函数表达式可以成为左值。只要避免将局部变量的地址返回,就能使函数表达式成为左值来使用。
传递指针和引用的更大目的是效率,当一个数据类型很大的时候,传值要复制副本,所以不可取。
指针和引用存在着传值没有的危险,但为了保持这种效率性,可以使用const指针和引用。
由于引用本身就是黏附在变量上不可以改变,所以不存在这种定义:
const double & const a=1;第二个const 不起作用,可以有const double &a=1;或int const &a=1;都行。书上写的有误。
返回堆中变量的引用:由于 new在堆中分配的空间返回的是指针,且存在着NULL的现象,NULL被引用是不允许的,所以在使用堆空间的引用时,要先用一个指针保存返回值,判断是否为NULL后,再用形如:int& rInt=*pInt的形式进行引用,在使用delete的时候,可以使用delete+先前的那个指针,也可以用&取地址操作符对引用进行操作。
需要注意的几点:
不允许声明引用数组,可以用常量来初始化引用声明。返回引用的时,要注意局部对象返回的危险。要注意引用隐藏函数所使用的参数传递的类型。
第十章 结构
在使用结构之前必须声明结构,指定关键字struct 和结构名 用{}包住里面的内容,以封号结束。
声明一个结构并不分配内存,内存分配发生在定义这个数据类型的变量中。
结构中包含的数据变量称为这个结构的数据成员。可以使用大括号对结构体变量逐个初始化。
要访问结构的成员,使用.操作符,返回值可以是左值,也可以是右值。
结构不象数组,它可以相互赋值,数组不能够,因为数组名只是个常量指针,数组不是一种数据类型,但是结构体可以,不过需要注意的是,在相互赋值时必须结构名相同。两个不同结构名额变量是不允许相互赋值的,即使两者包含有相同的成员。(重要)
Tips:在C++中,定义struct 变量不需要在前面加struct。
根据结构类型我们能够定义一个变量,是变量就有地址,与数组不同的是,结构体变量名不是指针,可以通过&取得他的第一个成员的地址。可以把结构体地址赋给指针,指针通过->来操纵结构成员。
使用结构数组进行排序的时候,最好用数组的地址进行排序,这样能提高很高的效率。
结构体作为函数传递的时候一般使用引用传递,在编程经验上,除非是小结构,一般很少按值传递。
返回结构:结构体作为函数的返回值一般用于数组的赋值等操作,这样结构数组初始化就可以通过一个独立的函数来实现,只要在这个函数体中定义一个和=左边相同的临时变量既可。例题见P215,但这种做法不常用,通常都是通过引用来直接修改结构体内部的值。以达到节约系统开销的目的。
可以使用结构体来实现一种非常重要的数据结构:即链表。结构成员不能是自身的结构变量,但可以用自身结构指针作为成员。
Tips:在这里可能设计到一个定义完备性的问题,有兴趣的可以研究一下。
关于链表的实现,主要是一个思想问题,代码很简单,就不多说了。
第二部分 面向对象程序设计
第十一章 类
struct和对象的关系,在C++中结构也被允许定义包含函数,默认是Public的(而类默认是private的)。而且C++中的结构变量声明的时候不需要加struct关键字。
在类花括号中中定义的函数不能含有switch,因为他们一般为内联函数,即使没有用inline表示。
将类的定义和其成员函数定义分开是目前开发程序的通常做法。把类定义(头文件)看成是类的外部接口,类的函数定义看成类的内部实现。函数的实现代码即以.CPP结为的文件名不一定要与头文件名相同。
TIP:在类的外部定义成员函数的时候,由于类名是限制函数作用域的,所以它应该和函数名最近,即返回类型应放在类名的前面。
除了静态的对象变量,对函数的变量的调用都必须使用对象名.变量名,对象是每个类的实例化。由于函数体内隐含了指针this,在内部对变量的操作都默认是this->变量名,所以可以省略。
在类中设置保护屏障,不让外部访问,主要是由于面向对象的目标决定的
(1) 对于外部函数而言,保护类的内部数据不被任意破坏
(2) 使类对他本身的内部实现负责
(3) 限制类与外部世界的接口
(4) 减少类与其他代码的关联度
private和protect的区别在类的继承中才反映出来,private定义的部分继承类不能够访问。
类的作用域是指类的定义和相应的成员函数定义的范围。
名空间是指某名字在其中必须唯一的作用域。
一个名字不能同时指代两中类型,非类型名(变量名,常量名,函数名,对象名)不能重名。
类型和非类型不在同一个空间,即类型名可以和非类型名相同,形如 A A;的定义实际上是允许的,但不可取。
两者同时出现的时候,类型名要加前缀,以区别非类型名。
函数的封装:数据与算法(操作)相结合,构成一个不可分割的整体(对象)。在这个整体中一些成员是保护的,他们被有效的屏蔽,以防止外界的干扰和误操作。另一些成员是公共的以作为接口提供給外界使用。
在商业性的C++类库包括了一个类的定义成成员函数的定义。类定义以头文件的形式提供,成员函数定义则是以一定的计算机硬件和OS为背景而编译实现的代码方式提供。
包含标准类库中的头文件称为类库重用,自定义的库头文件称为类库设计。
第十二章 构造函数
根据变量定义,全局变量和静态变量在定义(分配空间)时将位模式清0,局部变量在定义时,分配的内存空间内容保持原样,故为随机数。
需要注意的是构造函数没有返回值,但是可以用return返回,这时这个函数最大的不同,析构函数也类似。
析构函数在类的对象声明周期结束的时候由系统自动调用。P.S有练习说明自己手动调用的析构函数实际上不起作用,只是运行了我们在大括号里面自定义的程序。如果有delete之类的代码有可能会造成二次释放出现错误。
当函数运行到结束的花括号时候,析构函数依次被调用,调用次序与构造函数相反。
关于默认构造函数:
(1) C++规定,每个类必须有一个构造函数,如果没有构造函数就不能创建对象。
(2) 若未提供构造函数,C++提供默认构造函数,该默认构造函数只负责创建对象,不负责初始化。
(3) 只要一个类定义了一个构造函数,不一定是(无参构造函数),C++就不在提供默认构造函数,即如果定义了代参数的构造函数,形如:TypeClass 变量名 这样的定义就是非法的,应为没有不代参数的构造函数出现,必须在类中加上TypeClass()这样的构造函数才行
(4) 与变量定义相类似,在使用默认构造函数时候,全局和静态对象被初始化为0,局部变量为随机数。
创建无参数对象的时候一定不能带括号,因为Tdate oneday();是声明了一个返回值为Tdate类型,无参数的函数。
在类中,如果数据成员包含另外一个类的对象,则被包含类先与包含类初始化,即先调用被包含类的构造函数,然后调用包含它的类的构造函数,为了初始化这个成员类,我们在包含类的构造函数()后面加:对象名(参数)以达到初始化的目的。
Tips:
说明一个变量并初始化有两钟形式:
void main()
{
int m=10; //ok
int n(20);
}
赋值时只有一种方法:m=10
m(10) //这不是赋值,这时函数调用
构造函数后面的冒号语法只能用()的形式
构造对象的顺序:
(1) 局部和静态对象,以声明的顺序构造,但在函数运行时统一定义。
(2) 静态对象只被构造一次
(3) 所有全局对象在主函数main之前被构造
(4) 全局对象构造无特殊顺序,所以不要使用一个全局对象访问另一个全局对象。
(5) 成员以其在类中声明的顺序构造
成员在类定义中的声明顺序进行构造,而不是按照构造函数说明中冒号后面的顺序。
第十三章 面向对象程序设计
结构化程序设计方法按功能分割问题。面向对象按对象分割问题。
因十三章主要是代码,笔记略。
第十四章
C++程序的内存格局分为四个区:
(1) 全局数据区
(2) 代码区
(3) 栈区
(4) 堆区
全局变量、静态数据、常量存放在全局数据区,所有类成员和非成员函数代码存放在代码区,为运行函数分配的局部变量、函数参数、返回数据、返回地址等存放在栈区,余下的都放在堆区。
通过malloc可以绕过构造函数,但需要注意的是,如果不初始化的话,free释放的内容将不确定。
使用new来給一个对象指针赋值时,可以象平时一样在classtype后面括号中加入构造函数的参数。
从堆上分配对象数组,只能调用默认的构造函数,不能调用其他的任何构造函数。如果该累没有默认构造函数,则不能分配对象数组。
使用堆空间往往由于:
(1) 知道运行时才知道需要多少对象空间
(2) 不知道对象的生存期有多长(如果不用delete释放,将一直存在到文件结束)
(3) 知道运行时候才知道一个对象需要多少内存空间。
拷贝构造函数
P14-1.CPP上机
拷贝构造函数以形如:Student(Student &s)为特征。
默认拷贝构造函数
如果未提供自己的拷贝构造函数,C++提供默认拷贝构造函数。完成一个成员一个成员的赋值。这时属于浅拷贝。
创建P2时,对象P1被复制給了P2,但资源并没有复制,因此P1,P2指向同一个资源,这称为浅拷贝。
需要自己定义拷贝函数的情形:当一个对象创建时,分配了资源,这就需要对他定义自己的拷贝函数,使之不但拷贝成员,也拷贝资源。
堆内存并不是唯一需要拷贝函数的资源,但是最常用的一个。打开文件,占用硬件等服务也需要深拷贝。比较好的经验是:如果你的类需要析构函数来析构资源,则它也需要一个拷贝构造函数。
临时对象 返回一个对象的时候创建。
一般规定,创建的临时对象,在整个创建他们的外部表达式范围内有效,否则无效。就是说s=fn(),fn()返回产生的临时对象拷贝給s后,临时对象就析构了。所以不能用引用来引用一个返回的临时对象。
无名对象:可以直接调用构造函数创建无名对象。
Tips:如果传递参数的时候使用的是无名函数形如fn(X(“Radny”)),
fn的原型是void fn(X x)则不使用拷贝构造函数,C++中认为x=X(“Radny”)直接是X x(“Radny”)