c++源码 笔记

发布时间:2010-05-14 19:59:25   来源:文档文库   
字号:
C++笔记(day02) 2007-3-23 1、%ldd 可行文件的名字; ---可查看连接库 2、C++严格区分大小写,变量命名只能使用字母,数字,或下划线,第一个字母必须使用字母或下划线 3、float单精度,double双精度 sizeof(int)---返回int在内存中占的大小 size(bool)=1 size(char)=1 size(short)=2 size(int)=4 size(long)=4 size(float)=4 size(double)=8 无符号的数据一旦溢出,将成为0。 有符号的int最大值2147483647,益处变为最小值,负数 4、不同数据间赋值:有兼容性的,占空间小的数据类型,可以给占空间大的数据类型赋值 不兼容的,可能会造成数据丢失。int ->float可以转换;float->int 小数部分丢失 5、 运算符 要求数据类型要相同 i++ 先用后加 ++i 先加后用 布尔运算符号:结果真或假 逻辑运算符:做bool运算!(1)“与”运算&&,两条件要同时成立(2)“或”运算||,只要有一个条件是真,就成立 6、按位与“&” 有0则与的结果为0 按位或“|” 有1则或的结果为1 按位异或“^” 两位不同则为1,相同为0 7、左移 “<<" 左移1相当于乘以2 右移 “>>" 右移1相当于除以2 8、三目运算符 条件?真:假 9、返回变量 --- 表达式可以再次赋值,赋值语句返回变量本身 eg:(a=10)=20 返回变量的值 --- 不可再次赋值 eg:(a+5)=10 左值:变量、赋值表达式,++i的返回值是左值 右值:不能放在“=”左边的是右值,i++的返回值是右值 常量要在声明时就赋值,并且不能修改 10、流程控制 while(条件){循环体} 条件成立,则进入循环,知道条件为假时,退出循环 (1)声明循环变量 (2)while()确定循环条件 (3)循环变量要有所变化,避免形成死循环 for(变量初始化;条件;变量的变化){循环体代码} 开始时判断循环条件是否成立,循环结束后,到第三个条件,使用变量改变 do{循环体}while(条件); 先执行一次,再判断条件,选择是否继续执行循环 break; 跳出循环,执行循环之外的下一条语句 continue; 从次处开始,结束本次循环,进行下一次循环 11、课堂练习---求1到100之间的所有素数 C++笔记 (day03) 2007-03-26 1、cin 输入流一旦破坏,就不能恢复 2、函数:一段相关代码组成的集合,以便完成一个特定的目的。 (1)可以通过名字来使用,曾强了代码的可重用性。 (2)提高可维护性。 3、函数定义=声明+函数体: <返回类型> 函数名(<参数表>.. .){ return ... } 函数返回值:只有在调用函数的同时才能保存返回值 函数定义时,小括号里的参数称为形参,在函数被调用的时候才有意义。 函数调用时,小括号里的被传入的参数称为实参。 4、函数在调用之前只有声明就可以,因为在编译阶段,参照声明可以完成语法上的检测。 函数声明 <返回类型> 函数名(<参数表>...); 声明与定义的分离,实现并行开发! 5、全局变量,局部变量 局部变量:函数内部声明的变量,只能在它声明的函数内部使用。如果没有对其初始化就使用,则其值为垃圾数字。 全局变量:能被所有的函数共同使用,要声明在所有函数(包括main函数)之外。尽量少使用,影响太大。如果没有对其初始化就使用,系统默认为其初始化为0。 6、数据区:只存放全局数据,数据区的数据是稳定的,程序启动时,即创建,程序结束时,才消失 堆区 代码区 栈区:存储局部变量。插入数据从栈顶插入,先进后出结构。 如果函数不调用的话,局部变量是不存在的 在main函数与其他函数之间有个标记,一旦函数返回,栈顶下落到标记处 栈中的数据是变化频繁的 函数调用时创建,函数返回即消失 7、 值传递 函数调用并不是传递实参本身,而是创建一个临时变量传到函数中 (1)为形参创建一个临时变量,临时变量的值是拷贝实参的值 (2)对形参的使用,实际上是对临时变量的使用,不会改变实参的大小 8、默认参数 (1)area(float a , float b =10); 当只传递一个参数时,形参b的位置默认为10 (2)有默认值的形参靠右摆放 9、内联函数 声明时加“inline”,运行效率高,不支持结构控制语句,必须先定义,不支持函数原形。 10、递归函数 在函数内部自己调用自己。 必须有明确的返回条件----递归的出口 11、函数重载 (1)函数名相同。 (2)参数表不同(参数个数不同,参数类型不同,参数顺序不同)。 (3)与参数名和返回类型不关 调用时会根据参数表匹配自动调用相应的函数 编译之后,函数的名字都会发生改变,后面会加上形参缩写作为后缀,没有任何函数的名字是一样的 不会做自动类型提升 对重载的函数最好不使用参数默认值 12、const 在函数内部不允许修改此形参 13、需求分析、设计、代码实现 多文件示例: ========================================= chang.h ========================================= char change( char ); =============== ========================== chang_impl.cc ========================================= char change( char c ){ if( c >= 97 && c <= 122 ){ return c - 32 ; }else if( c >= 65 && c <= 90 ){ return c + 32 ; }else{ return c ; } } ========================================= main.cc ========================================= #include #include "change.h" using namespace std; int main(){ char c = '0' ; while( 1 ){ cout<<"enter a char [ 0 exit] >"; cin>>c ; if( c == '0' ){ break; } cout<<" ====> " << change( c ) <程序结束 数据区 Data 局部变量 函数内部 函数开始调用->函数返回 栈 Stack 静态局部 函数中 第一次调用函数时(只被初始化一次)->程序结束 数据区 ---只需要一份数据,并且不希望别的函数访问他 静态全局 本源文件中 程序开始->程序结束 数据区 作业:实现银行业务 ========= a.cc ================== long id; int password; double balance; long(id) create(balance , password); //创建账户 void save( sum ); int(staus 0,-1) withdraw(sum , password); float query(); =================================== generatorId(); //得到唯一的ID号 int showMenu(); //现实主菜单 saveMenu(); //收集开户信息,然后调用创建账户 createMenu(); withdrawMenu(); queryMenu(); =================================== ***今日重点:(1)形参和实参的区别 (2)递归 (3)函数重载 练习: 观察费波拉切数列1,1,2,3,5,8,13,21,34. . . . . .第一项和第二项都是1,后面的每一项是前面相邻两项的和。 使用递归方法计算第n项的费波拉切数列结果。 C++笔记 (day03) 2007-03-26 1、cin 输入流一旦破坏,就不能恢复 2、函数:一段相关代码组成的集合,以便完成一个特定的目的。 (1)可以通过名字来使用,曾强了代码的可重用性。 (2)提高可维护性。 3、函数定义=声明+函数体: <返回类型> 函数名(<参数表>...){ return ... } 函数返回值:只有在调用函数的同时才能保存返回值 函数定义时,小括号里的参数称为形参,在函数被调用的时候才有意义。 函数调用时,小括号里的被传入的参数称为实参。 4、函数在调用之前只有声明就可以,因为在编译阶段,参照声明可以完成语法上的检测。 函数声明 <返回类型> 函数名(<参数表>...); 声明与定义的分离,实现并行开发! 5、全局变量,局部变量 局部变量:函数内部声明的变量,只能在它声明的函数内部使用。如果没有对其初始化就使用,则其值为垃圾数字。 全局变量:能被所有的函数共同使用,要声明在所有函数(包括main函数)之外。尽量少使用,影响太大。如果没有对其初始化就使用,系统默认为其初始化为0。 6、数据区:只存放全局数据,数据区的数据是稳定的,程序启动时,即创建,程序结束时,才消失 堆区 代码区 栈区:存储局部变量。插入数据从栈顶插入,先进后出结构。 如果函数不调用的话,局部变量是不存在的 在main函数与其他函数之间有个标记,一旦函数返回,栈顶下落到标记处 栈中的数据是变化频繁的 函数调用时创建,函数返回即消失 7、 值传递 函数调用并不是传递实参本身,而是创建一个临时变量传到函数中 (1)为形参创建一个临时变量,临时变量的值是拷贝实参的值 (2)对形参的使用,实际上是对临时变量的使用,不会改变实参的大小 8、默认参数 (1)area(float a , float b =10); 当只传递一个参数时,形参b的位置默认为10 (2)有默认值的形参靠右摆放 9、内联函数 声明时加“inline”,运行效率高,不支持结构控制语句,必须先定义,不支持函数原形。 10、递归函数 在函数内部自己调用自己。 必须有明确的返回条件----递归的出口 11、函数重载 (1)函数名相同。 (2)参数表不同(参数个数不同,参数类型不同,参数顺序不同)。 (3)与参数名和返回类型不关 调用时会根据参数表匹配自动调用相应的函数 编译之后,函数的 名字都会发生改变,后面会加上形参缩写作为后缀,没有任何函数的名字是一样的 不会做自动类型提升 对重载的函数最好不使用参数默认值 12、const 在函数内部不允许修改此形参 13、需求分析、设计、代码实现 多文件示例: ========================================= chang.h ========================================= char change( char ); ========================================= chang_impl.cc ========================================= char change( char c ){ if( c >= 97 && c <= 122 ){ return c - 32 ; }else if( c >= 65 && c <= 90 ){ return c + 32 ; }else{ return c ; } } ========================================= main.cc ========================================= #include #include "change.h" using namespace std; int main(){ char c = '0' ; while( 1 ){ cout<<"enter a char [ 0 exit] >"; cin>>c ; if( c == '0' ){ break; } cout<<" ====> " << change( c ) <程序结束 数据区 Data 局部变量 函数内部 函数开始调用->函数返回 栈 Stack 静态局部 函数中 第一次调用函数时(只被初始化一次)->程序结束 数据区 ---只需要一份数据,并且不希望别的函数访问他 静态全局 本源文件中 程序开始->程序结束 数据区 作业:实现银行业务 ========= a.cc ================== long id; int password; double balance; long(id) create(balance , password); //创建账户 void save( sum ); int(staus 0,-1) withdraw(sum , password); float query(); =================================== generatorId(); //得到唯一的ID号 int showMenu(); //现实主菜单 saveMenu(); //收集开户信息,然后调用创建账户 crea teMenu(); withdrawMenu(); queryMenu(); =================================== ***今日重点:(1)形参和实参的区别 (2)递归 (3)函数重载 练习: 观察费波拉切数列1,1,2,3,5,8,13,21,34. . . . . .第一项和第二项都是1,后面的每一项是前面相邻两项的和。 使用递归方法计算第n项的费波拉切数列结果。 C++笔记 2007年3月28日 1、变量的存储 (1)内存是一块空间,把其中的每个字节做了编号,为了以后计算机能通过编号找到数据 (2)编址方式:绝对编址(在整个程序中使用),相对编址(字节相对于逻辑0偏移量,在进程中使用) 2、取变量地址 (1)"&" &i 表示取内存中i的地址 地址的编址用十六进制表示 (2)逻辑0在代码区 全局变量在数据区,地址的编址是大于0的 局部变量在栈区,地址的编址是小于0的 3、数组、结构的地址 (1)数组中的数据在内存中是连续存储的。 数组中每个元素的地址相差的值应为数组元素类型的大小。 (2)结构的地址: 结构的空间是连续的。 结构的起始地址与第一个成员变量的地址是一样的。 4、存储地址— 指针:存储变量的地址 指针的类型由将要保存的地址的变量类型决定 int*只能保存int变量的地址 指针赋值一定要是同类型的指针才能相互赋值! 5、指针的运算 (1)指针和指针之间的运算 “+”,“*”,“/” 指针与指针间是不能做这些运算,没有意义! “-” 可以做减法运算,以“sizeof(指针类型)”作为计算单位的! 注意:要同类型的指针才能做此运算,不同的话,会对运算单位产生歧义。 (2)指针和数字之间的运算(加、减都可以) int i = 100; int * p = &i; 打印 p+1 -> 相当于在地址上加4,因为存储的变量是int类型的 p+2 -> 相当于在地址上加8 6、通过指针访问所指向的变量 *p 代表指针p所指向的变量 *p <=> i 指针在声明的时候,即初始化 int * p = NULL; 表示没有明确指向,不能 *p ,会出现 “段错误”的异常 -->空指针 段错误原因 (1)空指针 (2)数组越界 (3)递归的条件不正确 7、课堂练习 用指针打印出数组中个元素的值 #include using namespace std; int main(){ int ai[6]={34,4,12,67,34,2}; int *p = &ai[0]; for(int i = 0 ; i < 6 ; i++){ cout <<"a[" << i << "] (与类型无关,因为保存地址的指针只保存地址) 保存int型指针(int* p = &i)的地址用int**保存(int** pp = &p) #include using namespace std; int main(){ int i = 0 ; int * p = &i ; int ** pp = & p ; cout<<"&i = " << &i << endl; cout<<"p = " << p << endl; cout<<"&p = " << &p << endl; cout<<"pp = " << pp << endl; cout<<"&pp = " << &pp << endl; cout<<"i = " < 相当于在地址上加4,因为存储的变量是int类型的 p+2 -> 相当于在地址上加8 6、通过指针访问所指向的变量 *p 代表指针p所指向的变量 *p <=> i 指针在声明的时候,即初始化 int * p = NULL; 表示没有明确指向,不能 *p ,会出现 “段错误”的异常 -->空指针 段错误原因 (1)空指针 (2)数组越界 (3 , pt+j*perLen); } } } (1)将输入参数int* -> void* (void* 可以存储任何类型的地址,可以通用 ) (2)表示数组的第i个元素,事先要知道数组每个元素的大小(参数传入) i*perLen => 当不知道指针类型时,表示走一步,应越过的字节数 p+i*perLen => 表示第i个元素的地址 (3)具体的数组要有具体的排序方法 调用函数,当不知道函数名时,可以通过函数指针调用 ================================================== sort.h ================================================== void sort( void * p , int len , int perLen , void (*pOrder)(void * , void * ) ); ================================================== sort.cc ================================================== void sort( void * p , int len , int perLen , void (*pOrder)(void * , void * ) ){ char* pt = (char*)p ; for( int i = 0 ; i < len ; i++ ){ for( int j = i ; j < len ; j++ ){ pOrder(pt+i*perLen,pt+j*perLen ) ; } } } ================================================== main.cc ================================================== #include #include "sort.h" using namespace std; void orderInt( void * p1 , void * p2 ){ int * t1 = (int*)p1 ; int * t2 = (int*)p2 ; if( *t1 > *t2 ){ int temp = *t1 ; *t1 = *t2 ; *t2 = temp ; } } struct Person{ char name[ 40 ] ; int age ; int id ; } ; void orderByAge( void * p1 , void* p2 ){ Person * t1 = (Person*)p1 ; Person * t2 = (Person*)p2 ; if( t1->age > t2->age ){ Person t = *t1 ; *t1 = *t2 ; *t2 = t ; } } void orderById( void *p1 , void* p2 ){ Person* t1 = (Person*)p1 ; Person* t2 = (Person*)p2 ; if( t1->id > t2->id ){ Person t = *t1 ; *t1 = *t2 ; *t2 = t ; } } void orderByName( void * p1 , void* p2 ){ Person* t1 = (Person*)p1 ; Person* t2 = (Person*)p2 ; if( strcmp( t1->name , t2->name ) > 0 ){ Person t = *t1 ; *t1 = *t2 ; *t2 = t ; } } int main(){ int ia[] = { 3,1,6,3,6,8,3,468,89 }; sort( ia , 9, sizeof(int), orderInt ); for( int i = 0 ; i < 9 ; i++ ){ cout< *p2){ int temp = *p1; *p1 = *p2; *p2 = temp; } } void sort (int *p , int len , int perLen , void (*pOrder)(void* , void*)){ char* pt = (char*)p; for(int i = 0 ; i < len ; i++){ for(int j = i ; j < len ; i ++){ pOrder(pt+i*perLen , pt+j*perLen); } } } (1)将输入参数int* -> void* (void* 可以存储任何类型的地址,可以通用 ) (2)表示数组的第i个元素,事先要知道数组每个元素的大小(参数传入) i*perLen => 当不知道指针类型时,表示走一步,应越过的字节数 p+i*perLen => 表示第i个元 素的地址 (3)具体的数组要有具体的排序方法 调用函数,当不知道函数名时,可以通过函数指针调用 ================================================== sort.h ================================================== void sort( void * p , int len , int perLen , void (*pOrder)(void * , void * ) ); ================================================== sort.cc ================================================== void sort( void * p , int len , int perLen , void (*pOrder)(void * , void * ) ){ char* pt = (char*)p ; for( int i = 0 ; i < len ; i++ ){ for( int j = i ; j < len ; j++ ){ pOrder(pt+i*perLen,pt+j*perLen ) ; } } } ================================================== main.cc ================================================== #include #include "sort.h" using namespace std; void orderInt( void * p1 , void * p2 ){ int * t1 = (int*)p1 ; int * t2 = (int*)p2 ; if( *t1 > *t2 ){ int temp = *t1 ; *t1 = *t2 ; *t2 = temp ; } } struct Person{ char name[ 40 ] ; int age ; int id ; } ; void orderByAge( void * p1 , void* p2 ){ Person * t1 = (Person*)p1 ; Person * t2 = (Person*)p2 ; if( t1->age > t2->age ){ Person t = *t1 ; *t1 = *t2 ; *t2 = t ; } } void orderById( void *p1 , void* p2 ){ Person* t1 = (Person*)p1 ; Person* t2 = (Person*)p2 ; if( t1->id > t2->id ){ Person t = *t1 ; *t1 = *t2 ; *t2 = t ; } } void orderByName( void * p1 , void* p2 ){ Person* t1 = (Person*)p1 ; Person* t2 = (Person*)p2 ; if( strcmp( t1->name , t2->name ) > 0 ){ Person t = *t1 ; *t1 = *t2 ; *t2 = t ; } } int main(){ int ia[] = { 3,1,6,3,6,8,3,468,89 }; sort( ia , 9, sizeof(int), orderInt ); for( int i = 0 ; i < 9 ; i++ ){ cout< using namespace std; class Person{ public: virtual double buy(){ return 2 ; } }; class Teacher : public Person{ public: virtual double buy(){ return 1 ; } }; class Student : public Person{ public: virtual double buy(){ return 0.5 ; } }; class CEO : public Person{ public: virtual double buy(){ return 1000 ; } }; void shoufei( Person * p ){ cout<< p->buy() << endl; } int main(){ Person p ; Teacher t ; Student s ; CEO c ; shoufei( &p ) ; //通过传入不同对象的地址,调用相应的函数 shoufei( &t ) ; //与if...else对比 shoufei( &s ) ; //写程序要尽量少改动写好的源码,这样实现代码的通用及低耦合 shoufei( &c ) ; return 0 ; } 作业:写一个校车类,用数组装乘客Bus Bus : 上车,下车 , 卖票等行为 车上有Teacher,Student ,计算总票价 C++笔记 2007年04月05日 1、 本对象 子类对象 其他函数 private属性 可见 不可见 不可见 protected属性 可见 可见 不可见 public属性 可见 可见 可见 public extends protected extends private extends 父类的private属性 不能访问 不能访问 不能访问 父类的protected属性 变成protected 不变 变成private,子类可以访问,子类的子类不能访问 父类的public属性 不变 变成protected 变成private,子类可以访问,子类的子类不能访问 2、 构造函数有很多种,因为没有指定构造函数,就会默认使用无参的构造函数。如果父类没有无参的构造函数, 那么就会出现编译错误。 可以使用这种形式Teacher(char* name, int age, double salary):Person(name,age){......},指定使用父类的有参构造函数。 3、多态的特征: 父类的指针可以指向子类的对象 通过父类指针只能调用父类中声明的方法 通过指针调用函数的时候,若函数是个虚函数,表现出来的行为是对象自身的行为 4、产生多态:(1)指针 (2)引用 父类的引用可以引用一个子类对象 通过父类引用只能调用父类函数 调用一个父类被覆盖了的,虚函数,能调用子类的函数 5、一个子类继承一个父类 --- 单继承 一个子类继承多个父类 --- 多继承 class SpiderMan : public Spider , public Person{....} 6、菱形继承,解决重复元素的冲突 让两个父类同时虚继承一个超类,把多继承中的重复元素放在超父类中 当有多个子类同时虚继承一个父类的时候,只有一个子类真正的构造父类 class Spider : vertual public Animal{.....}; class Person : vertual public Animal{.....}; class SpiderMan :public Person , public Spider{....}; 多继承尽量不要使用三层以上 7、抽象类 只有函数声明,没有函数实现 纯虚函数:没有实现的函数 virtual void writeLog(char*)=0; 若不写" =0 ",则系统会认为是函数声明,会试图去别的" .cc "文件中去找函数实现 含有纯虚函数的类称为抽象类,是抽象数据类型,不能创建对象 抽象类型就是为了被别人继承的,子类覆盖纯虚函数,提供函数实现 通过父类规范子类的用法 如果子类没有完全实现抽象父类的所有纯虚函数,则认为子类还是一个抽象数据类型 用到virtual的地方: (1)继承 (2)多继承 (3)纯虚函数 抽象类的规范很重要,在工程中,对于项目的并行开发很重要 而且对于项目中的改动,能方便的应付 用指针指向相应的子类对象,方便的调用子类的函数 8、友员 针对类来说,自身的私有变量,不能被别的类访问,但是,如果授权给一个类为自己的友员,就可以访问他的私有属性 可以作为友员的东西:另一个类,一个全局函数。 实现友员的授权: class Girl; class Person{ ........ friend class Girl; //友员类的声明-->授权给Girl类,成为自己的友员,可以访问自己的私有变量了 } ---------------------------------------------------------------------------------------------------------------- class Girl; class Person{ ........ friend void fn(); //友员函数的声明-->授 权给fn函数,成为自己的友员,可以访问自己的私有变量了 } 友员不是类的一部分 若不是互为友员,则不能访问友员类的私有变量 友员的使用: Bus把售票员作为自己的友员,访问自己的私有变量,即装载乘客的数组 友员在项目中的使用 9、静态数据 在类中定义一个静态数据 (实际上就是一种全局变量) (1)不依赖于对象,在对象不存在之前就已经存在了 (2)所有对象共享 与全局变量的区别: (1)使用的类中的静态变量,必须通过类名使用 (2)而且受访问控制符号的限制 (3)静态变量在类中声明的时候不能赋值,要在类外初始化 class A{ public : static int a; }; int A::a = 100; //此时才分配空间 int main(){ cout << A::a < using namespace std; class Person{ public: Person(){ cout<<"Person()"< a1.operator=(a2); */ Student& operator= (const Student &a){ age = a.age; id = a.id; strcpy(name , a.name); //不用new出一块空间,因为在声明a1和a2的时候,两个指针都指向一块自己的空间, 把指针指向的变量拷贝过去,即完成赋值 return *this; } 当在堆中申请空间,则覆盖赋值运算符(" = ") 作业:银行系统的账户类 把name , password 改用指针保存 (1)析构函数 (2)拷贝构造 (3)重载赋值运算符 C++笔记 2007年04月05日 1、 本对象 子类对象 其他函数 private属性 可见 不可见 不可见 protected属性 可见 可见 不可见 public属性 可见 可见 可见 public extends protected extends private extends 父类的private属性 不能访问 不能访问 不能访问 父类的protected属性 变成protected 不变 变成private,子类可以访问,子类的子类不能访问 父类的public属性 不变 变成protected 变成private,子类可以访问,子类的子类不能访问 2、 构造函数有很多种,因为没有指定构造函数,就会默认使用无参的构造函数。如果父类没有无参的构造函数,那么就会出现编译错误。 可以使用这种形式Teacher(char* name, int age, double salary):Person(name,age){......},指定使用父类的有参构造函数。 3、多态的特征: 父类的指针可以指向子类的对象 通过父类指针只能调用父类中声明的方法 通过指针调用函数的时候,若函数是个虚函数,表现出来的行为是对象自身的行为 4、产生多态:(1)指针 (2)引用 父类的引用可以引用一个子类对象 通过父类引用只能调用父类函数 调用一个父类被覆盖了的,虚函数,能调用子类的函数 5、一个子类继承一个父类 --- 单继承 一个子 类继承多个父类 --- 多继承 class SpiderMan : public Spider , public Person{....} 6、菱形继承,解决重复元素的冲突 让两个父类同时虚继承一个超类,把多继承中的重复元素放在超父类中 当有多个子类同时虚继承一个父类的时候,只有一个子类真正的构造父类 class Spider : vertual public Animal{.....}; class Person : vertual public Animal{.....}; class SpiderMan :public Person , public Spider{....}; 多继承尽量不要使用三层以上 7、抽象类 只有函数声明,没有函数实现 纯虚函数:没有实现的函数 virtual void writeLog(char*)=0; 若不写" =0 ",则系统会认为是函数声明,会试图去别的" .cc "文件中去找函数实现 含有纯虚函数的类称为抽象类,是抽象数据类型,不能创建对象 抽象类型就是为了被别人继承的,子类覆盖纯虚函数,提供函数实现 通过父类规范子类的用法 如果子类没有完全实现抽象父类的所有纯虚函数,则认为子类还是一个抽象数据类型 用到virtual的地方: (1)继承 (2)多继承 (3)纯虚函数 抽象类的规范很重要,在工程中,对于项目的并行开发很重要 而且对于项目中的改动,能方便的应付 用指针指向相应的子类对象,方便的调用子类的函数 8、友员 针对类来说,自身的私有变量,不能被别的类访问,但是,如果授权给一个类为自己的友员,就可以访问他的私有属性 可以作为友员的东西:另一个类,一个全局函数。 实现友员的授权: class Girl; class Person{ ........ friend class Girl; //友员类的声明-->授权给Girl类,成为自己的友员,可以访问自己的私有变量了 } ---------------------------------------------------------------------------------------------------------------- class Girl; class Person{ ........ friend void fn(); //友员函数的声明-->授权给fn函数,成为自己的友员,可以访问自己的私有变量了 } 友员不是类的一部分 若不是互为友员,则不能访问友员类的私有变量 友员的使用: Bus把售票员作为自己的友员,访问自己的私有变量,即装载乘客的数组 友员在项目中的使用 9、静态数据 在类中定义一个静态数据 (实际上就是一种全局变量) (1)不依赖于对象,在对象不存在之前就已经存在了 (2)所有对象共享 与全局变量的区别: (1)使用的类中的静态变量,必须通过类名使用 (2)而且受访问控制符号的限制 (3)静 态变量在类中声明的时候不能赋值,要在类外初始化 class A{ public : static int a; }; int A::a = 100; //此时才分配空间 int main(){ cout << A::a < using namespace std; class Person{ public: Person(){ cout<<"Person()"< a1.operator=(a2); */ Student& operator= (const Student &a){ age = a.age; 除错误,继续工作 #include using namespace std; int main(){ int age; char name[20] ; cout << "enter your age >"; cin >> age; cin.get(); //读取流中残余的回车,以便getline能正常工作 // cin.ignore(20,'\n'); //忽略20个字符或者碰到回车,从流中清除 cout << "enter your name >"; cin.getline(name,20); cout << "your age is :" << age << endl; cout << "your name is :" << name << endl; } (5)read(char*,int) char*是存结果的地址,int是读的长度,并且不能比前面的数组的空间大 读满为止 ,特殊字符也当做普通字符处理 超出的部分仍然存在流里面 只要数据没读满,一直阻塞 不会自动补'\0' --- 传参数的时候,数组长度传减1的长度,补齐'\0'以免乱码 所有输入流的共同特征:只要没数据就阻塞 读不完的就留在流里 (6)cin.ignore(255,'\n') 忽略255个字符或者遇到'\n',如果在前255个字符中出现'\n',则就忽略到'\n'之前的字符 (7)peek() 查看流里的第一个字符是什么 只察看,不读走 (8)putback() 向流中插入字符,前提必须有空位的时候 必须与get()一起使用,用get()取出一个字符,才能用putback()插入一个字符 (9)cin.fail()判断是否出现错误标志,一切正常返回false 当用cin读取的数据类型出现错误的时候,这是一种不可恢复的错误,用cin.clear()是不能清除错误标记的 在键盘上读取,用getline()可以确保输入流的正常形成,读取后采取强制转换类型得到自己需要的数据 8、ifstream (1)需要#include 头文件 ifstream ifs("test.txt"); //创建一个ifstream的对象,打开文件,给构造函数传如一个参数,就是文要打开的文件名 //文件的在当前目录下找,也可以用相对路径或绝对路径找文件 在打开文件之后,立即判断打开文件是否成功 if( ifs.fail() ){ cout << "Can't open test " <> (2)二进制文件:字节是连续的,不能用vi , more查看文件内容,read按字节数读取 , write 100 是整数的100,占4个字节 “100”是字符数组,占3个字节 2、异常 (1)人为错误:由于程序员编码不当 客观错误:不能避免的错误 (2)通过返回值判断程序的错误情况,对调用者的要求高,要写if()else()判断,而且对于返回值的含义要非常的清楚 所以C++中提供了异常处理机制 3 异常的工作原理: 1) 程序员写的代码在出现意外的地方自动产生一个异常,然后抛出一个异常对象。 2) 对象被传递到负责异常处理的地方。 throw 1; //抛出异常,让调用者处理 3) 由负责异常处理的代码进行统一的异常处理。 try{}catch(){} 4) 异常对象包含有意外发生的详细信息。 4 异常代码的格式: 抛出: throw 异常名字; 处理: ppt368 try { } catch( 异常1 int) { 处理代码; } catch( 异常2 const char* ) { 处理代码; } 程序正常的时候,catch块什么也不做,当出现异常的时候,程序从产生异常的地方跳到catch块中 异常处理完毕之后,不会回到发生异常的地方。 用try{}catch(){}处理异常是一种强制手段,出现异常,进程结束 catch()中的异常类型如果与抛出的异常类型不一致,则捕获不到 5 每个catch只能处理一种类型的异常,catch块会依次执行。 6 catch(...){ 处理代码; } 可以捕获任意类型的异常,但是它不能判断是什么类型的异常,一般把它放在最后一个catch块。 但这种捕获没有针对性 7 异常的传播特性: 层级传播:异常会逐层抛出 产生异常之后,程序立即跳转到最近的一层捕获异常的语句,如果当前没有捕获语句,或者没有匹配的catch块,那么程序会跳出当前的函数回到调用的地方。 如果向上跳到了main函数,还是没有处理异常,程序就会终止进程。 8、封装异常(1)使用字符串描述异常 (2)制定异常号 error No. (3)disp(); 当捕获的异常存在继承关系的时候,要先捕获子类异常,再捕获父 类异常 9、内联类 把一个类写到另一个类的内部 异常一般会做成内联类 A::exp ,靠前缀制定自己的命名空间 内联类,private修饰只能在本来中使用,对外是隐藏的,只有public 修饰的才能在外面使用 10、异常总结 (1)检查错误的一种手段 (2)可以用简单数据类型定义,还可以自定义类型 (3)产生 throw e; 异常对象 处理 try{}catch(异常类型){} --- 异常类型要与throw抛出的一样 (4)传播 11、银行项目 --- 面向对象 需求分析的时候,要画用力图 Biz Object 完成业务逻辑 Value Object 操作数据(Account) Menu 收集用户信息,用户选择的业务 Biz 针对某项业务收集信息 DA 提供文件访问服务 Account 保存数据 C++ 笔记 第十五天 2007年4月11日 1、在头文件中 #ifndef _ACCOUNT_ //预编译选项,表示如果没有定义这个宏 #define _ACCOUNT_ //创建以_ACCOUNT_命名的宏 并声明类 #endif 2、链表 (1)解决数组必须连续存储的问题 链表是可以不连续的,通过每个节点的指针连接 (2)节点中一部分空间用于存放数据,另一部分是一个指向下一个节点的指针 (3)每个节点都是一个结构 struct node{ int data; //存储数据 node* next; //指向下一个节点的指针,是自己这个结构的类型 } (4)尾节点 --- 链表中的最后一个节点 --- 指针指向NULL 头节点 --- 要访问链表中的元素,必须要知道头节点的位置 把地址放在一个指针中 --- 头指针指向头节点,只是一个指针 --- 是必须存在的元素 (5)对链表的常见操作 --- 增删改查 (6)链表与数组的区别 数组:空间必须连续,数组是定长的,插入和删除需要遍历整个数组,效率不高。 取元素可直接使用下标,访问方便 链表:空间在内存中不必连续,通过指针连接 链表是不定长的,可以随时添加新节点,通过指针关联 对链表的插入删除,不需要移动节点位置,只对指针操作即可 访问元素,要从头指针开始遍历 当数据需要频繁的插入删除的时候,需要使用链表 当改动不大,查询频繁的时候,使用数组 潜规则 : 能用数组就不用链表 ====================================================================== link.h ========================================================== ============ #ifndef _LINK_ #define _LINK_ using namespace std; class Node{ //节点类 public : int val; //保存数据 Node* next ; //保存下一个节点的地址 Node(){ //构造函数,把指针初始化为NULL next = NULL; } }; class Link{ protected : Node* head; //头指针 public : Link(); ~Link(); void insertTail(int); void insertHead(int); void del(int); int indexOf(int); //查询一个元素的下标 void update(int , int); void disp(); }; #endif ====================================================================== link.cc ====================================================================== #include "link.h" #include using namespace std; Link::Link(){ head = NULL; } Link:: ~Link(){//释放空间,从头向尾释放 if(head != NULL){ Node *p = head; head = head->next; //把头节点向后移动 delete p; //抛弃原来的那个头节点 cout << "delete one ... " << endl; } } //尾插入 void Link::insertTail(int v){ Node *p = new Node; p->val = v; if(head == NULL){ head = p; //让新节点的指针指向新节点,即把新节点的地址保存在头指针中 return ; } Node * temp = head ; //用一个临时指针,从头节点开始找到 尾 while(temp -> next != NULL){ //表示temp不是尾节点 temp = temp -> next ; //用temp后面的一个指针为自己赋值,即指向下一个节点 } temp -> next = p; //尾插入,最后一个节点的指针保存新节点的地址 } //头插入 void Link::insertHead(int v){ Node *p = new Node; //创建新节点 p->val = v ; //保存数据 p->next = head; //让新节点的指针和头指针一样指向第一个节点 head = p; //让头节点指向新节点 } void Link::del(int v){ //找到被删除的节点 , if(head == NULL ){ return ; } if(head -> val == v){ Node *p = head; head = head->next; delete head; } Node *p1 = head->next; //找值相同的一个 Node *p2 = head ; //跟在p1后面 while(p1 != NULL){ if(p1->val == v){ p2->next = p1 -> next; delete p1; break; } p1 = p1->next; p2 = p2->next; } } int Link::indexOf(int v){ //查询一个元素的下标 Node * p = head ; int counter = 0 ; while( p != NULL ){ if( p->val == v ){ return counter ; } p=p->next ; counter++ ; } return -1 ; } void Link::update(int v1 , int v2){ Node * p = head ; while( p != NULL ){ if( p->val == v1 ){ p->val = v2 ; } p = p->next ; } } void Link::disp(){ Node *p = head; while(p != NULL){ cout << p->val << " " ; p = p->next; } cout << endl; } 3、二叉树 每个节点最多只有两个分支的树,它有一个根指针,要指向这棵树的根节点(最顶端的节点). 左子树上的值小于其父节点的值,右子树上的值都大于其父节点上的值。 --- 排序二叉树 (1)周游(遍历) :先序 --- 中左右 中序 --- 左中右 后序 --- 左右中 (2)非常方便查找 二叉查找树的常见操作: 1) 插入. 示例代码如下: Node* Tree::_insert(int v, Node* r){ //真正实现插入操作,返回插入以后的根 if(r == NULL){ //是一棵空树 (空子树) Node* p = new Node(v); //创建新节点 r = p; //让新节点成为根或者子节点 return r; } if( v < r->val){ //插到左子树上 r->left = _insert(v,r->left); return r; }else{ //插到右子树上 r->right = _insert(v,r->right); return r; } } 2) 查找. 示例代码如下: Node* & find( bnode* & root, const DATA& cd ) { if( root==NULL ) // 如果root节点是空,则为空树 return root; // 返回root指向的地址,即NULL else if( root->data==cd ) // 如果root节点就是要查找的数值 return root; // 返回root指向的地址,为了清晰,和 上面的分开写 else if( cd < root->data ) // 如果root节点指向的值大于要查找的值 return find( root->left, cd ); // 返回查找root的左子树返回的地址 else return find( root->right, cd ); // 否则返回查找root的右子树返回的地址 } 3) 删除. 示例代码如下: 被删除的是树根(1)则选择右子树的树根做新树根,左子树可以整个挂在右子树最左侧的一个左节点上 右子树中最左边的一个节点,是最靠近左子树的树根的 (2)让左子树中的最大节点做新树根 Node* _del( int value , Node* r ){ if( r == NULL ){ //删除空树 return r ; } if( r->value == value ){ //删除树根 if(r->left==r->right){ //左右子树都是NULL的情况下 delete r ; return NULL; }else if( r->right == NULL ){ //只有右子树,没有左子树的时候 Node * p = r; r = r->left ; delete p ; return r ; }else if( r->left == NULL ){ //只有右子树,没有左子树 Node *p = r ; r=r->right ; delete p ; return r ; }else{ //左右子树都有 Node * p1 = r -> right ; Node * p2 = r -> right ; while( p2->left != NULL ){ p2 = p2->left ; } p2->left = r->left ; delete r ; return p1 ; } } if( value <= r->value ){ r->left = _del( value , r->left); return r ; }else{ r->right =_del( value, r->right ); return r ; } return r ; } 作业:修改链表程序,能够删除全部相同元素;在指定位置后插入数据 C++ 笔记 第十六天 2007年4月12日 1、算法 脱离具体的语言 有穷性 --- 在保证执行有限步骤之后确定能够结束 确切性 --- 每条语句具体干什么 输入输出 --- 所有的算法都有输出,打印屏幕,写文件,写DB 2、快速排序法 数据个数超过一个,任选其中一个数据作为分界值,把其他数据按大小关系分为2组,分界值在中间 对两组数据实行递归重组 //快速排序算法,效率最高的排序算法。第一个参数表示数组首地址,第二个参数表示数组起始位置,第三个参数表示结束位置 void mysort( int * p , int left , int right ){ int l = left ; //从左侧开始走 int r = right ; //从右侧开始走 int povit = p[(left + right)/2]; //把数组中间的一个数据作为分界点 do{ while( p[l]povit && r > left ){ r--; } if( l <= r ){ int t = p[l]; p[l] = p[r]; p[r] = t ; l++; r--; } }while( l <= r ); //条件就是左右的两个人还没有碰面 if( r > left ){ //只要右边的仍比左边的大,就要继续循环 mysort( p , left , r ); } if( l < right ){ //只要左边的仍比右边的大,也要继续循环 mysort( p , l , right ); } } 3、直接使用系统的qsort()函数 要自己定义一个排序规则 4、模版 (1)模版的参数至少出现一次,才能确定类型 (2)只能在紧跟的函数中使用,函数声明紧跟在后面 声明多个模版类型 template class关键字不能省略 (3)对于模版类型的要求,要能重载">","<","=" 建议:在编码时能用一种运算符完成的操作,就不要使用多个运算符,避免多个重载 (4)用模版写的函数叫函数模版 函数模版在调用的时候确定类型的 用模版写的类叫类模版 数据类型,参数类型,函数返回类型都可以使用模版 类模版不是类,是不完整的类 类模版要在声明时用类名指定,确定类型 (5)C++的泛型(模版)是编译时确定类型的 --- 效率 Java的泛型是运行时的 (6)模版类的声明和定义(多文件结构)是不能分开的 模版函数的声明和定义是可以分开的 template 在头文件和实现文件中都要出现 5、STL包含三大类,容器类(可以存储其他对象的对象),算法(一系列封装好的函数),迭代器(用于遍历操作的类) 容器可以直接存储对象,也可以存储对象的指针。成熟的程序员喜欢使用间接存储。 容器主要包括两种类型:序列类(一般是线形存储)和关联类(一般是非线性存储)。 vector ---- 数组 可变长 不提供pop_front()删除头元素的函数 list ----- 链表 (1)Vector v[1000]当越界的时候,会出现段错误 v.at(1000) 越界的时候,会抛出out_of_range的异常,在程序中捕获 v.size() 返回长度,可利用这个循环迭代 v.empty()判断容器是否为空 Iterator迭代器 : 可以做取*操作 *iterator iter->name <=> (*iter).name iter++ v.begin() 指向数组的开始 v.end() 指向数组最后一个元素的后面,是一个结束标志 vector v1; vector::iterator it; //iterator是vector的一个内部类 for( it = v1.begin(); it != v1.end(); it++ ) cout << *it << endl; v.insert(iter,5); //在iter所指的元素前面插入5 v.insert(iter,5,100); //在iter所指的元素前插入5个100 这样的插入操作,会造成原来的iterator失效,对起重新赋值,可以保证继续使用 (2)list 不能做at() 多了push_front(),pop_front() iter不能做加n操作 使用于做频繁的插入删除操作 6、关联式容器 (1)map 适合根据键查找值的操作 存储上按照键值排序 ,并且key值唯一 map m; Student s( 1 ,"liucy" ); m.insert( map::value_type( s.getId() , s ) ) ; //创建一个pair,并存到map的第一个位置中 value_type是map的静态函数 Student s2( 4, "tangliang" ); m.insert( map::value_type( s2.getId() , s ) ) ; map::iterator it ; for(it=m.begin();it!=m.end();it++ ){ cout<< it->first << " "<second; cout<::iterator it ; multimap::iterator lt ; multimap::iterator ut ; lt = m.lower_bound( 1 ); ut = m.upper_bound( 1 ); for( it=lt ; it != ut ; it++ ){ cout<first <<" " ; cout<second <*(m_pCurrentState = m_pNextState))()。如果失败则报错,同时让用户选择重试(Retry)还是放弃(Abort)还是忽略(Ignor)。 如果Abort则结束StateMachine, 如果Retry则再次叫当前函数this->*m_pCurrentState(); 如果Ignor则忽略当前错误继续下一步。this->*(m_pCurrentState = m_pNextState)(); 当没有下一个状态的时候StateMachine结束。 (m_pNextState == NULL) 这是标准工业中的用法,大家不妨看一看,写成一个标准的class。这将是个很有用的练习。

本文来源:https://www.2haoxitong.net/k/doc/f78ab2c69ec3d5bbfd0a748a.html

《c++源码 笔记.doc》
将本文的Word文档下载到电脑,方便收藏和打印
推荐度:
点击下载文档

文档为doc格式