C++高质量编程总结-1-R(2)
38:free和delete对指针的实质处理:
delete对指针的真实处理情况: (1)他们的作用是释放内存 (2)但是他们并没有对指针作处理,那个原来的指针还是指向原来的地址,但是原来的内存已经 是不合法的了 (3)如果释放内存之后不对指针进行处理(p = NULL),那么原来的指针便成为了野指针 (4)在释放一个内存以后,如果不对指针指针进行赋空值的处理,那么贬毁出现这样的问题: 在检查一个一个指针是否合法的时候检查不出来: if( p == NULL ) ; //这句话没有作用,因为这个指针还是指向那个原来的空间 39:动态内存会自动释放吗? void fun() { char *p = new char() ; //指针p是一个函数内部建立的局部变量 } 在上面的那个函数中,应该注意几点: (1)指针p是一个局部变量,在这个函数结束的时候,指针变量p就已经消亡 (2)指针p在函数结束的时候消亡,但是指针p所指的内存空间还没有得到释放 (3)所以: 指针消亡,指针指向的空间不一定消亡 一个内存空间消亡,但是指向这个内存空间的指针不一定消亡,如果不对这个指针进行NULL 处理,那么这个指针有可能成为野指针 40:关于‘野指针’: (1)定义: 野指针不是NULL指针,它是指向‘垃圾’内存的指针 (2)形成野指针的三个原因: (1)指针变量没有初始化 一个指针变量在声明之后如果不给出初值,那么这个指针便会乱指一气 所以一个指针如果不想成为野指针: (1)赋值NULL (2)指向一个合法的内存空间 (2)内存空间进行delete之后,没有把指向这个内存空间的指针赋值为NULL (3)指针操作超过了变量的作用范围 例如传递一个函数里边的局部变量的地址 41:内存耗尽的处理方法:(三种) (1)如果在一个函数里面,申请新的内存失败,指针返回NULL值,可以直接结束函数的运行: 直接用return语句调处本函数 (2)如果遇到这种情况,可以直接结束程序,这种做法的原因: 如果不结束程序,可能会导致系统崩溃 (3)在new的语句进行一场捕获 42:关于内存耗尽: 在内存耗尽的时候,系统是没有办法救的,这种情况下的唯一结束办法就是exit(1) ;使得程序退出 43:如果创建对象数组,那么只能使用创建无参对象,例如: (1)这样是正确的: Object *p = new Object[100] ; (2)这样是错误的: Object *p = new Object[100](1) ; //企图调用有参数的构造函数,这种做法是错误的 44:要养成使用调试器来跟踪程序的习惯,只有这样才会发现问题的本质 45:函数的参数是指针的情况: 如果参数仅作输入使用,那么应该在参数的前面加上const,这样的作用就是: 可以避免在函数体内遭受改变 46:char的取值范围 [-128.127] 47:函数中错误的返回 如果要返回错误的标志,则直接用return ;就可以了 48:如果数据参数作为输入参数用,函数参数最好用const &,这样采用的是值传递,省掉了构造对象和析构对象的时间 49:C语言中,凡不加类型说明的函数,一律按整型处理,所以在C语言中,标准的写法:void Fun(void) ;,而不是:void Fun() 在C++中,这两种形式都是正确的 50:关于inline函数 (1)适用情况: <1>规模比较小 <2>调用次数较多 (2)如果在类中实现一个函数,那么系统会自动把这个函数作为内联函数 (3)如果类外实现成员函数,应该作显式声明(在函数声明或者函数定义处均可) (4)若作为内联函数,则不应该包括循环等复杂结构 51:C++中关于类的面向对象的思想 (1)类的定义和类成员函数的实现一般放在两个不同的文件中,这样符合软件工程思想 例如: /***************************Student.H********************************/ class Student { public: Student() ; Student(int No) ; inline void Print() const ; private: int m_No ; } ; /**************************Student.H********************************/ /**************************Student.CPP***********************************/ #include <iostream> #include <Student.H> /* 要把此头文件包含进来 */ Student::Student() { m_No = 100 ; } Student::Student(int No):m_No(No) {}
inline void Student::Print() const
{ cout << "m_No :" << m_No << endl ; } /*************************Student.CPP*******************************/ /***************************Main.CPP******************************/ #include <iostream> #include <Student.H> void main() { ... } (2)编译过程 <1>先同时对两个源文件Student.CPP和Main.CPP进行编译,生成Student.OBJ和Main.OBJ文件 <2>把两个目标文件,和系统库函数文件进行链接,生成可执行文件Main.EXE (3)注意:类的声明和实现一般放在两个不同的文件中 52:注意 C中不支持函数有默认参数 53:this指针 (1)存在的位置:在每个成员函数中都是存在的 (2)它的值:指向当前对象的地址 (3)*this :它的含义就是“当前对象” 54:常对象,常数据成员,常成员函数的关系 (1)如果一个对象是常对象,那么只允许调用该对象的常成员函数,非常成员函数禁止调用 (2)双保险:将某些不能改变的数据成员声明成const类型,再用const函数来访问他们 (3)非const成员函数:可以访问非const数据,也可以访问const数据,但不能修改他们 (4) const成员函数:可以访问非const数据,也可以访问const数据,不可以修改非const数据 ---- const函数决定函数的属性:不能改变数据的值,不管是const数据还是非const数据 非const函数有修改的权利,但还得看人家数据同意不同意修改,非const类型的同意,const类型的不同意 55:常对象里面的常成员函数都是常成员函数吗? 如果一个对象是常对象,只能说明这个对象里面的数据是常量,和成员函数没有任何关系 56:const小结 (1)const对象: 如果一个对象声明成const,那么不能调用它的非const成员函数,只能调用它的const成员函数 ------ 注意,当一个对象声明成const对象之后,如果一定要改变某个变量的值,可以在变量的前面加上关键字:mutable,这样就可以利用const成员函数(一定要注意是const成员函数,不是非const成员函数)来修改了。 (2)指向对象的const指针的写法: Time* const ptr ; // 注意*的位置 (3)指向对象的常指针和指向常对象的指针: <1>指向对象的常指针: Time* const ptr ; // const 距离指针比较近 <2>指向常对象的指针: const Time *ptr ; // const 距离对象比较近 (4)注意: 如果一个变量(包括对象)是const类型的,那么只能让const指针指向它,非const指针不能指向它 (5)函数形参的分析: <1>如果形参是const类型:那么实参可以是const类型,也可以是非const类型 <2>如果形参是非const类型:那么实参只能是非const类型,不能是const类型 57:面试题 看看下面的程序有什么问题? -------------------------------------------- class LongArray; //Manipulates a variable number of long ints. class Widget
{ public: Widget(); ~Widget(); void Add(long l);// Add a long to the array
void Transform();// Perform transformation on the array void Print();// Print the array private: LongArray* pLongArray; }; Widget::Widget()
{ pLongArray = new LongArray; } Widget::~Widget()
{ delete pLongArray; } // The members Add(), Transform(), and Print() are implemented
// elsewhere. voidDoSomething(Widget x)
{ x.Transform(); x.Print(); } void main()
{ Widget x; x.Add(200); 本文出自 51CTO.COM技术博客x.Add(210); DoSomething(x); // Continue working after transformation… } -------------------- 问题:在函数DoSomething()中,由于形参是值传递,即所谓的“浅拷贝”,拷贝时会直接把指针的值拷贝过来,但是析构的时候会把内存删掉。但是当原对象在此调用自己的析构函数时,这个时候就会出现问题了,因为一块内存被释放了两次。 解决办法: 如果类数据成员有指针的,那么应该重新写复制构造函数 58:面向对象的两个条件: <1> 支持类 <2> 支持多态 59:多态的分类 <1> 静态多态 函数重载,运算符重载 <2> 动态重载 虚函数 60:虚函数存在的目的 让一个指向基类的指针指向一个子类对象的成员函数 61:让一个基类指针指向一个派生类对象,实质上指向的是派生类的基类部分 62:注意: 如果在类外定义虚函数时,不能再加virtual关键字,这样会编译错误 63:类中的const和virtual函数比较: <1> 如果在类外定义const 函数,那么声明和定义的地方都要有const关键字 <2> 如果在类外定义virtual函数,那么在声明的地方要加上virtual关键字,在定义的地方不能加virtual关键字,否则会出现错误 64:注意: C++规定,当一个成员函数被声明成virtual函数之后,那么它的派生类中的同名,同参的函数也自动成为virtual函数,但是为了更加清晰,最好在前面也加上virtual关键字 65:virtual关键字的注意点 只有在类中才会出现这个关键字,一般函数不能定义成virtual函数 66:virtual函数的缺点: 使用虚函数,系统要有一定的空间开销。当一个类有虚函数时,则编译系统会为此类构造一个虚函数表( virtual function ),它是一个指针数组,存放每个虚函数的入口地址。 虚函数:virtual function table 67:关于有有默认参数的函数注意点: <1> 如果函数的定义和声明在一块,那么默认参数就出现在(参数列表中)(这时函数的定义和声明是一块的) <2> 如果函数的定义部分和声明部分不在一块,那么默认参数只能出现在函数的声明中,不能出现在函数的定义中,否则将是错误的 68:指向函数的指针(分两种情况) <1> 指向普通函数的指针: 例如: 有一函数形式: int Add(int no1 = 0, int no2 = 0) { return (no1+no2) ; } // int (*ptr) (int, int) = Add ; // 函数的名称就是函数的入口地址 ptr(2, 3) ; // 调用函数 <2> 指向类成员函数的指针: 形式: 类型 (类名::函数名称) (参数列表) ; 例如: class Number { public: int Add(int no=0) ; private: int No ; } ; int Number::Add(int no) { return (Number.No + no) ; } // int (Number::*ptr) (int) = Number::Add ; // ptr是指向类Number的成员函数指针 void main() { Number N = Number() ; (N.*ptr)(2) ; // 利用指针来调用函数 } 69:关于“指向类成员函数”的注意点: <1> 正确的调用形式: (object.*ptr) (2) ; // 正确 <2> 错误的调用形式: object.*ptr (2) ; // 错误 (object. (*ptr) ). (2) ; // 错误 70:定义对象的方法 <1> C1 c1() ; // 这样定义好像是错误的 <2> C1 c1 = C1() ; //这样定义是正确的 |


yaya123
博客统计信息
热门文章
最新评论
友情链接