C学习笔记.docx
- 文档编号:28659394
- 上传时间:2023-07-19
- 格式:DOCX
- 页数:16
- 大小:24.21KB
C学习笔记.docx
《C学习笔记.docx》由会员分享,可在线阅读,更多相关《C学习笔记.docx(16页珍藏版)》请在冰豆网上搜索。
C学习笔记
C++中public,protected,private访问标号小结
第一:
private,public,protected访问标号的访问范围。
private:
只能由1.该类中的函数、2.其友元函数访问。
不能被任何其他访问,该类的对象也不能访问。
protected:
可以被1.该类中的函数、2.子类的函数、以及3.其友元函数访问。
但不能被该类的对象访问。
public:
可以被1.该类中的函数、2.子类的函数、3.其友元函数访问,也可以由4.该类的对象访问。
注:
友元函数包括3种:
设为友元的普通的非成员函数;设为友元的其他类的成员函数;设为友元类中的所有成员函数。
第二:
类的继承后方法属性变化。
private属性不能够被继承。
使用private继承,父类的protected和public属性在子类中变为private;
使用protected继承,父类的protected和public属性在子类中变为protected;
使用public继承,父类中的protected和public属性不发生改变;
如下所示:
public:
protected:
private:
public继承 public protected 不可用
protected继承 protected protected 不可用
private继承 private private 不可用
protected继承和private继承能降低访问权限。
我们知道,用C++开发的时候,用来做基类的类的析构函数一般都是虚函数。
可是,为什么要这样做呢?
下面用一个小例子来说明:
有下面的两个类:
classClxBase
{
public:
ClxBase(){};
virtual~ClxBase(){};
virtualvoidDoSomething(){cout<<"DosomethinginclassClxBase!
"< }; classClxDerived: publicClxBase { public: ClxDerived(){}; ~ClxDerived(){cout<<"OutputfromthedestructorofclassClxDerived! "< voidDoSomething(){cout<<"DosomethinginclassClxDerived! "< }; 代码 ClxBase*pTest=newClxDerived; pTest->DoSomething(); deletepTest; 的输出结果是: DosomethinginclassClxDerived! OutputfromthedestructorofclassClxDerived! 这个很简单,非常好理解。 但是,如果把类ClxBase析构函数前的virtual去掉,那输出结果就是下面的样子了: DosomethinginclassClxDerived! 也就是说,类ClxDerived的析构函数根本没有被调用! 一般情况下类的析构函数里面都是释放内存资源,而析构函数不被调用的话就会造成内存泄漏。 我想所有的C++程序员都知道这样的危险性。 当然,如果在析构函数中做了其他工作的话,那你的所有努力也都是白费力气。 所以,文章开头的那个问题的答案就是--这样做是为了当用一个基类的指针删除一个派生类的对象时,派生类的析构函数会被调用。 当然,并不是要把所有类的析构函数都写成虚函数。 因为当类里面有虚函数的时候,编译器会给类添加一个虚函数表,里面来存放虚函数指针,这样就会增加类的存储空间。 所以,只有当一个类被用来作为基类的时候,才把析构函数写成虚函数。 类中的静态成员真是个让人爱恨交加的特性。 我决定好好总结一下静态类成员的知识点,以便自己在以后面试中,在此类问题上不在被动。 静态类成员包括静态数据成员和静态函数成员两部分。 一静态数据成员: 类体中的数据成员的声明前加上static关键字,该数据成员就成为了该类的静态数据成员。 和其他数据成员一样,静态数据成员也遵守public/protected/private访问规则。 同时,静态数据成员还具有以下特点: 1.静态数据成员的定义。 静态数据成员实际上是类域中的全局变量。 所以,静态数据成员的定义(初始化)不应该被放在头文件中。 其定义方式与全局变量相同。 举例如下: xxx.h文件 classbase{ private: staticconstint_i;//声明,标准c++支持有序类型在类体中初始化,但vc6不支持。 }; xxx.cpp文件 constintbase: : _i=10;//定义(初始化)时不受private和protected访问限制. 注: 不要试图在头文件中定义(初始化)静态数据成员。 在大多数的情况下,这样做会引起重复定义这样的错误。 即使加上#ifndef#define#endif或者#pragmaonce也不行。 2.静态数据成员被类的所有对象所共享,包括该类派生类的对象。 即派生类对象与基类对象共享基类的静态数据成员。 举例如下: classbase{ public: staticint_num;//声明 }; intbase: : _num=0;//静态数据成员的真正定义 classderived: publicbase{ }; main() { basea; derivedb; a._num++; cout<<"baseclassstaticdatanumber_numis"< b._num++; cout<<"derivedclassstaticdatanumber_numis"< } //结果为1,2;可见派生类与基类共用一个静态数据成员。 3.静态数据成员可以成为成员函数的可选参数,而普通数据成员则不可以。 举例如下: classbase{ public: staticint_staticVar; int_var; voidfoo1(inti=_staticVar);//正确,_staticVar为静态数据成员 voidfoo2(inti=_var);//错误,_var为普通数据成员 }; 4.★静态数据成员的类型可以是所属类的类型,而普通数据成员则不可以。 普通数据成员的只能声明为所属类类型的指针或引用。 举例如下: classbase{ public: staticbase_object1;//正确,静态数据成员 base_object2;//错误 base*pObject;//正确,指针 base&mObject;//正确,引用 }; 5.★这个特性,我不知道是属于标准c++中的特性,还是vc6自己的特性。 静态数据成员的值在const成员函数中可以被合法的改变。 举例如下: classbase{ public: base(){_i=0;_val=0;} mutableint_i; staticint_staticVal; int_val; voidtest()const{//const成员函数 _i++;//正确,mutable数据成员 _staticVal++;//正确,static数据成员 _val++;//错误 } }; intbase: : _staticVal=0; 二,静态成员函数 静态成员函数没有什么太多好讲的。 1.静态成员函数的地址可用普通函数指针储存,而普通成员函数地址需要用类成员函数指针来储存。 举例如下: classbase{ staticintfunc1(); intfunc2(); }; int(*pf1)()=&base: : func1;//普通的函数指针 int(base: : *pf2)()=&base: : func2;//成员函数指针 2.静态成员函数不可以调用类的非静态成员。 因为静态成员函数不含this指针。 3.静态成员函数不可以同时声明为virtual、const、volatile函数。 举例如下: classbase{ virtualstaticvoidfunc1();//错误 staticvoidfunc2()const;//错误 staticvoidfunc3()volatile;//错误 }; 问题: 在下面的templatedeclarations(模板声明)中class和typename有什么不同? template template 答案: 没什么不同。 在声明一个templatetypeparameter(模板类型参数)的时候,class和typename意味着完全相同的东西。 一些程序员更喜欢在所有的时间都用class,因为它更容易输入。 其他人(包括我本人)更喜欢typename,因为它暗示着这个参数不必要是一个classtype(类类型)。 少数开发者在任何类型都被允许的时候使用typename,而把class保留给仅接受user-definedtypes(用户定义类型)的场合。 但是从C++的观点看,class和typename在声明一个templateparameter(模板参数)时意味着完全相同的东西。 然而,C++并不总是把class和typename视为等同的东西。 有时你必须使用typename。 为了理解这一点,我们不得不讨论你会在一个template(模板)中涉及到的两种名字。 假设我们有一个函数的模板,它能取得一个STL-compatiblecontainer(STL兼容容器)中持有的能赋值给ints的对象。 进一步假设这个函数只是简单地打印它的第二个元素的值。 它是一个用糊涂的方法实现的糊涂的函数,而且就像我下面写的,它甚至不能编译,但是请将这些事先放在一边——有一种方法能发现我的愚蠢: template voidprint2nd(constC&container)//container; { //thisisnotvalidC++! if(container.size()>=2){ C: : const_iteratoriter(container.begin());//getiteratorto1stelement ++iter;//moveiterto2ndelement intvalue=*iter;//copythatelementtoanint std: : cout< } } 我突出了这个函数中的两个localvariables(局部变量),iter和value。 iter的类型是C: : const_iterator,一个依赖于templateparameter(模板参数)C的类型。 一个template(模板)中的依赖于一个templateparameter(模板参数)的名字被称为dependentnames(依赖名字)。 当一个dependentnames(依赖名字)嵌套在一个class(类)的内部时,我称它为nesteddependentname(嵌套依赖名字)。 C: : const_iterator是一个nesteddependentname(嵌套依赖名字)。 实际上,它是一个nesteddependenttypename(嵌套依赖类型名),也就是说,一个涉及到一个type(类型)的nesteddependentname(嵌套依赖名字)。 print2nd中的另一个localvariable(局部变量)value具有int类型。 int是一个不依赖于任何templateparameter(模板参数)的名字。 这样的名字以non-dependentnames(非依赖名字)闻名。 (我想不通为什么他们不称它为independentnames(无依赖名字)。 如果,像我一样,你发现术语"non-dependent"是一个令人厌恶的东西,你就和我产生了共鸣,但是"non-dependent"就是这类名字的术语,所以,像我一样,转转眼睛放弃你的自我主张。 ) nesteddependentname(嵌套依赖名字)会导致解析困难。 例如,假设我们更加愚蠢地以这种方法开始print2nd: template voidprint2nd(constC&container) { C: : const_iterator*x; ... } 这看上去好像是我们将x声明为一个指向C: : const_iterator的localvariable(局部变量)。 但是它看上去如此仅仅是因为我们知道C: : const_iterator是一个type(类型)。 但是如果C: : const_iterator不是一个type(类型)呢? 如果C有一个staticdatamember(静态数据成员)碰巧就叫做const_iterator呢? 再如果x碰巧是一个globalvariable(全局变量)的名字呢? 在这种情况下,上面的代码就不是声明一个localvariable(局部变量),而是成为C: : const_iterator乘以x! 当然,这听起来有些愚蠢,但它是可能的,而编写C++解析器的人必须考虑所有可能的输入,甚至是愚蠢的。 直到C成为已知之前,没有任何办法知道C: : const_iterator到底是不是一个type(类型),而当template(模板)print2nd被解析的时候,C还不是已知的。 C++有一条规则解决这个歧义: 如果解析器在一个template(模板)中遇到一个nesteddependentname(嵌套依赖名字),它假定那个名字不是一个type(类型),除非你用其它方式告诉它。 缺省情况下,nesteddependentname(嵌套依赖名字)不是types(类型)。 (对于这条规则有一个例外,我待会儿告诉你。 ) 记住这个,再看看print2nd的开头: template voidprint2nd(constC&container) { if(container.size()>=2){ C: : const_iteratoriter(container.begin());//thisnameisassumedto ...//notbeatype 这为什么不是合法的C++现在应该很清楚了。 iter的declaration(声明)仅仅在C: : const_iterator是一个type(类型)时才有意义,但是我们没有告诉C++它是,而C++就假定它不是。 要想转变这个形势,我们必须告诉C++C: : const_iterator是一个type(类型)。 我们将typename放在紧挨着它的前面来做到这一点: template voidprint2nd(constC&container) { if(container.size()>=2){ typenameC: : const_iteratoriter(container.begin()); ... } } 通用的规则很简单: 在你涉及到一个在template(模板)中的nesteddependenttypename(嵌套依赖类型名)的任何时候,你必须把单词typename放在紧挨着它的前面。 (重申一下,我待会儿要描述一个例外。 ) typename应该仅仅被用于标识nesteddependenttypename(嵌套依赖类型名);其它名字不应该用它。 例如,这是一个取得一个container(容器)和这个container(容器)中的一个iterator(迭代器)的functiontemplate(函数模板): template voidf(constC&container,//typenamenotallowed typenameC: : iteratoriter);//typenamerequired C不是一个nesteddependenttypename(嵌套依赖类型名)(它不是嵌套在依赖于一个templateparameter(模板参数)的什么东西内部的),所以在声明container时它不必被typename前置,但是C: : iterator是一个nesteddependenttypename(嵌套依赖类型名),所以它必需被typename前置。 "typenamemustprecedenesteddependenttypenames"(“typename必须前置于嵌套依赖类型名”)规则的例外是typename不必前置于在一个listofbaseclasses(基类列表)中的或者在一个memberinitializationlist(成员初始化列表)中作为一个baseclassesidentifier(基类标识符)的nesteddependenttypename(嵌套依赖类型名)。 例如: template classDerived: publicBase : Nested{ //baseclasslist: typenamenot public: //allowed explicitDerived(intx) : Base : Nested(x)//baseclassidentifierinmem { //init.list: typenamenotallowed typenameBase : Nestedtemp;//useofnesteddependenttype ...//namenotinabaseclasslistor }//asabaseclassidentifierina ...//mem.init.list: typenamerequired }; 这样的矛盾很令人讨厌,但是一旦你在经历中获得一点经验,你几乎不会在意它。 让我们来看最后一个typename的例子,因为它在你看到的真实代码中具有代表性。 假设我们在写一个取得一个iterator(迭代器)的functiontemplate(函数模板),而且我们要做一个iterator(迭代器)指向的object(对象)的局部拷贝temp,我们可以这样做: template voidworkWithIterator(IterTiter) { typenamestd: : iterator_traits : value_typetemp(*iter); ... } 不要让std: : iterator_traits : value_type吓倒你。 那仅仅是一个standardtraitsclass(标准特性类)的使用,用C++的说法就是"thetypeofthingpointedtobyobjectsoftypeIterT"(“被类型为IterT的对象所指向的东西的类型”)。 这个语句声明了一个与IterTobjects所指向的东西类型相同的localvariable(局部变量)(temp),而且用iter所指向的object(对象)对temp进行了初始化。 如果IterT是vector : iterator,temp就是int类型。 如果IterT是list : iterator,temp就是string类型。 因为std: : iterator_traits : value_type是一个nesteddependenttypename(嵌套依赖类型名)(value_type嵌套在iterator_traits 如果你觉得读std: : iterator_traits : value_type令人讨厌,就想象那个与它相同的东西来代表它。 如果你像大多数程序员,对多次输入它感到恐惧,那么你就需要创建一个typedef。 对于像value_type这样的traitsmembernames(特性成员名),一个通用的惯例是typedefname与traitsmembername相同,所以这样的一个localtypedef通常定义成这样: template voidworkWithIterator(IterTiter) { typedeftypenamestd: : iterator_traits : value_typevalue_type; value_typetemp(*iter); ... } 很多程序员最初发现"typedeftypename"并列不太和谐,但它是涉及nesteddependenttypenames(嵌套依赖类型名)规则的一个合理的附带结果。 你会相当快地习惯它。
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 学习 笔记