C++ Primer学习 《Classes》
来源:互联网 发布:免费下载visio软件 编辑:程序博客网 时间:2024/05/18 15:52
Classes(基础)
1. 类的基础介绍
定义在class内部的函数默认是inline的。
1.1 this指针介绍
当我们通过object调用类成员函数时,有一个指向这个object的指针被传递给这个函数,这个指针叫做this指针。
例如当我们调用:
total.price();实际上相当于:
price(&total);//pseudo-code因为this永远指向当前object,因此this是一个const指针,我们不能改变this的地址。但是this指向的object当然是可以改变的。因此this是一个top-level const pointer,不一定是low-level const pointer。
1.2 const 成员函数介绍
类成员函数有时被声明为const类型,这意味着该类成员函数不会修改this指向的object。而没有被声明为const类型的类成员函数,则自然可以修改这个object。因此,如果object是const,那么我们就不能调用nonconst类成员函数。
class testConst{public:int constMemberFunction(){ return 0; } int constMemberFunction2() const { return 0; }//const 类成员函数};testConst a;a.constMemberFunction();//okconst testConst b;b.constMemberFunction();//error!b.constMemberFunction2();//ok!
具体说来,在上面的例子中,第一个类成员函数constMemberFunction将this指针定义为int* const类型,而在第二个类成员函数constMemberFunction2中将this指针定义为const int * const类型。大家可以自行体会。
1.3 类内顺序(class scope)
在类的定义中,先出现的函数可以在函数体内调用后出现的变量。即:
class scopeTest{ int print(){cout << answer << endl;}//ok!尽管answer在后面定义 int answer;};这是因为,C++编译器在编译类时分两步,第一步:对所有变量和类成员函数的声明进行编译;第二步:处理函数体。
Return “this” object
直接返回this指向的object,一般的写法是:
class thisTest{...};thisTest& thisTest::add(const thisTest&){...return *this;}
1.4 构造函数(Constructors)
和其他函数不同的是,构造函数没有返回类型。一个类可以有多个构造函数,只要满足函数重载的规则即可。
构造函数不能定义为const函数,这很容易理解。即使我们要创建一个const object,在创建的过程中我们无疑需要对这个object进行一些赋值。
默认构造函数(default constructor)
默认构造函数没有任何参数。默认构造函数可以显式声明,只要没有参数就属于默认构造函数。
编译器自动生成的构造函数通常被称作:synthesized default constructor。在这个函数中,编译器遵循如下法则初始化类内各个成员:
1.如果有in-class initializer(在类内直接赋初值),则使用这个值。
2.否则,对成员进行default initialize。(int等被赋为0等)
有时不能依赖synthesized default constructor
1.只有当类没有定义任何构造函数时,编译器才会为我们定义一个synthesized default constructor
2.当成员是built-in type 或 compound type时(arrays , pointers等),default initialize会产生undefined value,这是我们绝对不希望的。
3.有时,编译器无法完成synthesized default constructor的工作。比如,一个类内部有一个成员是另一个类,而那个类没有构造函数,等等情况。
=default
有时,我们在定义各个类内成员时,已经写好了in-class initializer,那么可能会觉得再写一个构造函数比较麻烦,这时可以用=default 语句。
class defaultConstructorClass{...defaultConstructorClass() = default;};一般来说,只有当我们同时需要定义其他的构造函数时,才会这么写,否则,直接不定义不就好了嘛。
constructor initializer list
在定义构造函数时,有一个比较与众不同的东西叫做constructor initializer list。
形式如下:
className(string &s) : bookNo(s){}className(string &s,unsigned n,double p) : bookNo(s),units_sold(n),revenue(p*n) {}
及时我们需要将构造函数定义在类外,这种constructor initializer list也依然是可以使用的。
一般来说,最好能使用in-class initializer,但如果编译器不支持,那我们必须这样声明。
2. 控制Class以及包裹Class
2.1 class和struct关键词
struct和class几乎是可以互换的,只是一种风格的不同。
唯一的区别是,默认情况下(没有声明public,private等),struct下的成员是public的,而class下的成员是private的。
至于编程风格上,如果我们的类中所有成员都是public的,那么我们用struct。如果有private成员,那我们用class。
2.2 友元(Friends)
将其他类或函数声明为友元(friends)后,该类或函数就能调用本类中的非public成员。
例如:
class friendClass{friend friendClass add(...);//一个类外函数friend anotherClass;//另一个类};friends函数不受private或public影响,但是作为好的习惯,我们应该将他们放在一起,放在class中的最开始,或者最后。
另外,class内的友元函数的声明,只是表明一个权限(access),而不是一般意义上的声明。如果在类的外部我们需要使用这个友元函数,那么我们还需要再次声明这个函数!
一般来说,我们会将这些友元函数的声明和这个类放在同一个头文件中。
而如果是将一个类声明为友元,则可以在两个头文件中都包含一下另一个类的简单声明。(这个是我自己认为的)
3.类的其他特性
3.1 类成员
Type Member
在类的定义中,我们还可以定义type。type也会受到public和private的限制。
class testConst{public:using publicint = int;private:using privateint = int;};testConst::publicint a = 0;//ok!testConst::privateint b = 0;//error!
值得注意的是,和其他类成员不同,type member必须先定义,再使用!因此,一般讲typedef / type alias声明在类的最前面。
Inline Members
前面提到过,在类的内部完全定义好的函数默认是inline的。同时我们也可以显式要求某个函数是inline的。
我们可以在类的内部声明inline标示符,可以在类外部定义时声明inline标示符,也可以在两个地方都声明,效果是一样的。但是,C++ Primer建议,只在类的外部声明inline标示符。(猜测这是为了避免类的内部东西太多,毕竟inline对理解函数和类的作用不提供任何帮助)
基于和之前在《函数》一章我们提到过的同样的原因,inline函数应该和类的定义放在同一个头文件中,而不是放在一个源文件内。
Mutable Data Members
有时候我们希望类中的某个成员,即使在一个const object中,也能够被改变,这个成员就是mutable data member。我们使用mutable关键词来表明。
class testConst{public:mutable int a = 0;int b = 0;void testMutable() const{ a++; }//ok!void testMutable2() const{ b++; }//error!};
Initializers for Data Members of Class Type
我们提到了C++ 11中可以使用in-class initializer,之前我们只用了赋值操作符(=),我们还可以使用大括号({}),事实上,也只能用这两种方式。
一个使用大括号的例子:
class c{public:vector<int> a { 10 };};
事实上,我在这里遇到了很奇怪的现象。
首先,这个构造函数不允许接收多于两个参数,会提示重载函数不存在。例如:vector<int> a{1,2,3,4,5}似乎应该是很可行的,但是编译器会报错。
其次,构造函数将a理解为一个具有10个0的vector,而不是在一般情况下a是一个vector只有一个元素10.
我还在困惑之中。
另外值得注意的是,{}的初始化,其内部的变量类型必须和声明的变量类型完全符合,不能有任何转换,这个之前已经提过很多次了。
Returning *this from a const Member Function
class d{public:const d& get() const//ok!{return *this;}d& get2() const//error!{return *this;}};
Overloading Based on Const
和其他的overload函数一样,有时我们希望const和nonconst object获得不同的函数来执行。在类中,如果我们希望const object和nonconst object调用一个重载函数,可以有两个相应的函数来执行,那么应该如下:
class c{c& get(){}//nonconst 版本const c& get() const {}//const 版本};
3.2再看看友元函数
友元函数可以直接在类的内部直接定义,这样的函数默认是inline的。当然,你还需要在类的外部声明一下,才能在类的外部使用。
Friendship between classes
在A类中声明B类是它的友元,那么B类就能访问A类中任何成员。
class b{//可以访问a内任何成员};class a{friend class b;};值得注意的是,友元不具有传递性!即如果B是A的友元,C是B的友元,那么C并不是A的友元!
Making A Member Function a Friend
我们也可以只将B类中的一个成员函数声明为A类的友元。例如:
class a{friend void b::get(a);};然而,这样做需要我们仔细安排我们的程序结构,使得声明和定义之间的依赖关系得以确保。完成上面例子中的友元声明需要这么做:
1.声明a类,但不定义a。
2.首先定义b类,在b类中要声明,但不能定义get()函数。a必须在get()函数使用其内部的成员之前被定义。
3.定义a类,包括将get()声明为友元。
4.定义get()函数,这个函数现在可以使用a类中的成员了
感觉还是蛮麻烦的!!
Overloaded Functions and Friendship
对于重载函数,如果希望将它们都声明为友元,则需要将每一个函数都声明为友元,即不能只声明一个函数为友元,而期望其他重载函数都自动成为友元。
Friend Declarations and Scope
这个指的是之前提过很多次的概念,一个友元函数在类中被声明并不表示在类的外部我们可以使用这个友元函数。在类的外部,即使是在类成员函数的定义中,如果我们没有在类外声明这个友元函数,而直接调用它,那么就会出错。
简言之,在类的外部,我们也必须声明这个友元函数!
4. Class Scope
4.1 Scope and Members Defined outside the Class
这部分主要说的是,如果我们的类成员函数在类的外部被声明,那么在“总的类scope”(我自己起的名字哈哈)出现之前,如果出现了其他的类内的成员,那么必须特别声明,如果出现在其后,那么无需再次声明。
用例子来说明:
class c{private:using cint = int;void get1(cint);cint get2();};//下面这些类成员函数的定义中,get1和get2前的那个"c::"就是我说的"总的类scope",因为无论是哪一个类成员函数都得有这个c::void c::get1(cint){}//这里参数列表中的cint无需再次声明是c的成员,因为它在总的类scope之后,编译器能找到它c::cint c::get2(){ return 0; }//这里返回类型的cint需要声明是c的成员,因为在总的类scope之前,编译器不知道
4.2 Name Lookup Class Scope
这一节,从这一部分开始,我觉得是一些比较诡异,或者比较tricky的内容,是我们一般不会注意的,但是如果想打好C++基础的话则应该掌握。
一般来说,name lookup(就是我们使用一个变量,编译器会去寻找这个变量对应的是之前声明的哪一个)的过程是很直观的,如下:
1.在这个name被使用的block中查找,当然之查找name出现前的代码。
2.如果这个block没找到,就到enclosing scopes中查找。这个意思是,比如一个while中又有一个while,就会有两层大括号,如果最内层是我们的block,那么外层就是enclosing scopes(嵌套域)。
3.如果一直找不到,那么报错。
而在类的内部,这个过程有一些不一样,正如刚刚已经提到过的:
1.所有的成员的声明会被编译。
2.成员函数的定义只有在所有成员声明结束后,即整个类是可见的(entire class has been seen)后,才会被编译。
4.3 Name Lookup For Class Member Declarations
class内部的和外部同名的变量,内部会覆盖外部。
string a;class c{double get(){return a;}//ok!string get2(){return a;}//error!double a;};
4.4 Type Names Are Special
如果在类的外面已经typedef了一个类型,那么在类的内部不能对这个名称重定义同一个类型或其他类型。
但是我在VS2013中测试时,发现重定义不会编译出错。
尽管如此,这么做有很大问题。这是Primer上的一个例题:
typedef string TYPE;TYPE initVal();class ex{public:typedef double TYPE;TYPE setVal(TYPE);TYPE initVal();private:int val;};TYPE ex::setVal(TYPE parm)//在这里,第一个返回类型的TYPE是string!!而parm的类型是double!!所以编译报错!!{val = parm + initVal();return val;}基于以上这个例子,我们应该尽量避免类内typedef和类外typedef重名。
4.5 Normal Block-Scope Name Lookup inside Member Definitions
在类成员函数内部:
1.在类成员函数的定义内寻找。
2.如果成员函数定义中没有找到,就在类中寻找,所有的类成员都会被考虑到。
3.如果依旧没有找到,就在类成员函数的定义前的所有范围内寻找。
4.6 三种类成员引用方法
在类成员函数中,如果我们想要使用类成员,可以用this,也可以用::。
void get(){int a = this->someint;int b = className::someint;//equivalent!}另外,如果我们希望显式表明使用global的变量,可以这样:
void get(){int a = ::someint;//someint是global one!}
5.深入了解构造函数
5.1构造初始表(constructor initializer list)
className(string &s,unsigned n,double p) : bookNo(s),units_sold(n),revenue(p*n) {}事实上我们当然也可以用赋值的方法来实现:
className (string &s,unsigned n,double p) { bookNo = s; units_sold = n; revenue = p*n;}这种做法完全是合法的,但却显得臃肿。
class a{public: int i; const int ci; int &ri;}a::a(int ii){ i=ii;//ok! ci=ii;//error! const类型不能赋值 ri = i;//error! ri未被初始化};而如果我们改用构造初始表则可以完成:
a::a(int ii):i(ii),ci(ii),ri(i){} //ok!另外,如果两种情况都可以使用的话,使用构造初始表效率相对较高。因为赋值语句做了两件事:首先initialize,然后赋值;而构造初始表通过一步initialize完成。
a(int ii = 0){}那么就不能再定义:
a() = default;当然,也不能定义其他同类的构造函数。
5.2 委托构造函数(delegating constructor)
class a{public: a(int i,double j){} a() : a(1,2.0){} a(int i) : a(i,0){} a(istream &is) : a(){read(is, *this);}};对于委托构造函数,如果一个函数A委托了函数B来构造自己,那么调用函数A时,函数B的初始化列表以及函数B的函数主体都会被执行(虽然上面例子中函数主体都是空的)。
class a{public:int i;a(){ cout << "1" << endl; }a(int i):a(){ cout << "2" << endl; }a(string s) :a(1){ cout << "3" << endl; }};这是如果我们调用 a aa(s);
避免自己委托自己
a(double i) :a(i){}而我们调用 a aa(1);
5.3 默认构造函数的作用
class a{public: a(string s){}};int main(){ a aa;//error!不存在a的默认构造函数!}记住!当一个类已经有了至少一个构造函数以后,编译器就不会再自动为你补足默认构造函数!(VS2013测试)
默认构造函数的使用
class a{public: int i=0; a() = default;};<pre name="code" class="cpp">int main(){ a aa(); aa.i = 1;//error!!}这里为什么会报错?原因在于我们自以为定义了一个名为aa的object,其实我们定义了一个名为aa的函数!它不需要任何参数,返回一个class a!
int main(){ a aa; aa.i = 1;//ok!}注意这里有一个括号的区别!
5.4 Implicit Class-Type Conversions
class a{public: int i; a(int ii):i(ii){}};void testA(a aa){...}int main(){ testA(1);//ok!}这里testA本该接收一个类型为a的变量,但是因为存在int到a的转换!所以调用int类型参数也可以!
class a{public: a(string s){}};void testA(a aa){...}int main(){ string s = "1"; testA(s);//ok! testA("1");//error!不存在const char [1]到a的转换}
再举一例:
class a{public: a(vector<int> v){}};void testA(a aa){...}int main(){ vector<int> v = {1,2,3}; testA(v);//ok! testA({1,2,3});//error!不存在initializer_list到a的转换}
但是,如果是double,char,int这类转换,或者const转换,并不是class-type转换,则可以有多次,例如:
class a{public: int i; a(int ii):i(ii){}};void testA(a aa){...}int main(){ double d = 1.0; testA(d);//ok!}
使用explicit关键字来阻值implicit conversion
class a{public: int i; explicit a(int ii):i(ii){}};void testA(a aa){...}int main(){ double d = 1.0; testA(d);//error!}explicit只对接受一个参数的构造函数有效!当一个构造函数需要多个参数时,一个类型转换是绝对不存在的,因此一个explicit也是没有必要的。
这点其实是很显而易见的,如果一个构造函数是explicit,那么我必须使用初始化的方式来定义一个类对象,而不能使用赋值的方式。
class a{public: int i; explicit a(int ii):i(ii){}};a aa(1);//ok!a aa = 1;//error!如果不是explicit,那么可以
显式调用构造函数来完成类型转换
class a{public: int i; explicit a(int ii):i(ii){}};void testA(a aa){...}testA(a(1));//ok!testA(static_cast<a>(1));//ok!无论a的构造函数是explicit还是不是explicit,使用这种显式转换都是可行的,而且代码更加清晰易懂。
Library Classes with explicit Constructors
5.5 Aggregate Class
struct Data{ int ival; string s;};<pre name="code" class="cpp">Data d1 {0,"1"};//okData d1 = {0,"1"};//ok甚至结构中有数组也可以:
struct Data{ int ival; int a[5];};Data d1 {0,{1,2,3,4,5}};//okData d1 {0};//ok,数组被value initialized另外要注意的是,初始化的顺序必须和aggregate class中定义的顺序一致。
5.6 Literal Classes
一个aggregate class,如果所有的成员都是literal type,那么它就是literal type。
class Debug{public: constexpr Debug(bool b=ture):hw(b),io(b),other(b){} constexpr Debug(bool h,bool i,bool o):hw(h),io(i),other(o){} constexpr bool any(){return hw||io||other;}private: bool hw; bool io; bool other;};constexpr构造函数一般用来省城一些objects,这些object常作为常量参数或constexpr函数的返回类型。
constexpr Debug io_sub(false,true,false);if(io_sub.any())//equivalent to if(true)...constexpr Debug prod(false);if(prod.any())//equivalent to if(false)...
6.静态类成员(static class members)
6.1定义static member
class a{string s;double amout;static double rate;static double initRate();};因为static member是独立于所有objects之外的,因此所有的class a的object都只有两个成员:s和amount。只存在一个rate,被所有class a的objects共享。
6.2 使用static member
我们可以直接用scope operater来调用
double r = a::rate;double r2 = a::initRate();或者,虽然static member不属于任何object,我们还是可以通过object来调用它们。
a aa;double r = aa.rate;double r2 = aa.initRate();类的内部,成员函数可以直接调用static member,而不需要scope operator
double a::calculate(){return 2*rate;}
6.3定义static member
当定义一个static成员函数时,我们既可以在类的内部定义,也可以在外部定义,形式和其他的函数定义完全一样。
不过注意的是,在类的内部我们要有static关键词,但是在类的外部不能出现。
另外,static member并不在哦我们创建类的时候被定义,因此类的构造函数无法初始化static member。我们也不能在类的内部对static member进行in-class initialize。相反的,我们必须在类的外部初始化static member。
double a::rate = 1.0;//okdouble a::rate = initRate();//ok这里,即使initRate()是private也没关系,毕竟static member是类内成员,有权利调用类内任何成员。
class a{ static int ii; static int f(){return ii};};int a::ii = f();完全可以运行,而且ii的输出的结果为0,总觉得非常诡异,还有待进一步了解。
6.4 in-class initialization of static data members
class a{ static const int ii = 1;//const int可以有in-class initialization};const int a::ii;//在外部再次声明,不过不要重定义
6.5 static member可以有ordinary member不能操作的操作
class a{static a a1;//ok,static member can have incomplete typea *a2;//ok,pointer member can have incomplete typea a3;//error! data members must have complete type!};
另一点是,static member可以作为类内函数的默认参数。
class a{static const int ii;int get(int i = ii){...}//ok!};一般的数据成员不能作为默认参数,因为函数不知道从哪个object来获取那个数据成员。但是static member是一致的,因此可以作为默认参数。
- C++ Primer学习 《Classes》
- The Primer of Classes
- c++primer学习小记
- C++Primer学习小记
- C++PRIMER学习笔记
- C++Primer 学习
- c++primer学习笔记
- c++primer学习笔记
- C Primer Plus学习
- C++primer学习笔记
- c++primer 学习 第一章
- c++primer 学习笔记
- C++primer 语法学习
- C++Primer 学习笔记
- 初学者:学习C++primer
- c++primer学习笔记
- C++Primer学习笔记
- C++primer学习:再探迭代器
- 烤串有哪些讲究?
- Android - 文件读写操作 总结
- Java遍历指定目录下的所有文件
- visual studio 2010 的openGL环境的搭建
- C#实现一个最简单的HTTP服务器
- C++ Primer学习 《Classes》
- nodejs多房间web聊天室
- strlcpy 和strlcat
- Java中XSLT转换的简单实例
- python学习
- [Leetcode]LeetCode Single Number II 位运算法解析理解
- Servlet3.0的异步处理
- linux下火狐浏览器安装flash player插件
- 笔记75--自定义Toast