以下是本人学习类与对象的一些总结笔记,尚不完整,在补充更新中~~~
一、类的定义
类一般在.h文件中声明,类中的成员函数的实现一般在.cpp文件中给出,注意需要在main函数外给出(例程1.4)。如果是单文件格式,类声明可以在main函数内也可以在main外(例程1.1),但是它的成员函数的定义不能main内。实际上当类在main内声明时成员函数的定义无论在main外还是main内都会有问题(我目前的发现是这样,见例程1.3),因此注意类的声明最好在main外,这样既清晰又不会错。注意类的声明之后要加分号。定义类时不能给成员赋初值(例程1.6),这跟结构体一致。
例程1.1:类在main中声明,正确
1 #include <iostream> 2 using namespace std; 3 int main() 4 { 5 class A 6 { 7 public: 8 void func(); 9 };//注意分号 10 return 0; 11 }
例程1.2:类在main外声明,正确
1 #include <iostream> 2 using namespace std; 3 class A 4 { 5 public: 6 void func(); 7 };//分号! 8 int main() 9 { 10 11 return 0; 12 }
例程1.3:成员函数main中定义,错误
1 #include <iostream> 2 using namespace std; 3 int main() 4 { 5 class A 6 { 7 public: 8 void func(); 9 }; 10 //成员函数 11 void A::func() 12 { 13 cout<<1; 14 } 15 return 0; 16 }
报错:a function-definition is not allowed here before '{' token
将成员函数的定义放在main外面是不是就正确了呢?
以下程序报错:'A' has not been declared
1 #include <iostream> 2 using namespace std; 3 void A::func() 4 { 5 cout<<1; 6 } 7 int main() 8 { 9 class A 10 { 11 public: 12 void func(); 13 }; 14 return 0; 15 }
以下同样报错:'A' has not been declared
1 #include <iostream> 2 using namespace std; 3 int main() 4 { 5 class A 6 { 7 public: 8 void func(); 9 }; 10 return 0; 11 } 12 void A::func() 13 { 14 cout<<1; 15 }
例程1.4:
类在"Student.h"中声明,成员函数在.cpp文件中定义
1 #include "Student.h" 2 #include <iostream> 3 #include <string> 4 using namespace std; 5 int main() 6 { 7 //main中定义 8 void Student::setinf(string setname,string setnum,int setage) 9 { 10 name=setname; 11 num=setnum; 12 age=setage; 13 } 14 void Student::print() 15 { 16 cout<<"Name: "<<name<<endl<<"Num: "<<num<<endl<<"Age: "<<age<<endl<<"Score: "<<score<<endl; 17 } 18 return 0; 19 }
报错:
'setinf' : local function definitions are illegal
'print' : local function definitions are illegal
以下正确:
1 #include "Student.h" 2 #include <iostream> 3 #include <string> 4 using namespace std; 5 6 void Student::setinf(string setname,string setnum,int setage) 7 { 8 name=setname; 9 num=setnum; 10 age=setage; 11 } 12 void Student::print() 13 { 14 cout<<"Name: "<<name<<endl<<"Num: "<<num<<endl<<"Age: "<<age<<endl<<"Score: "<<score<<endl; 15 } 16 17 int main() 18 { 19 20 return 0; 21 }
总结起来,比较好的做法是:类在main外声明,成员函数在main外实现。如下
例程1.5:
1 #include <iostream> 2 using namespace std; 3 4 class A 5 { 6 public: 7 void func(); 8 }; 9 void A::func() 10 { 11 cout<<1; 12 } 13 int main() 14 { 15 16 return 0; 17 }
例程1.6:
1 #include <iostream> 2 using namespace std; 3 4 class A 5 { 6 public: 7 i=1;//错误 8 }; 9 10 int main() 11 { 12 13 return 0; 14 }
二、public,private,protected 成员
类声明中通过以上三个关键字可分别将类的成员定义为公有成员、私有成语和保护成员。公有成员是类与外部的接口,可被类外的函数直接访问。私有成员只允许本类的函数访问。保护成员兼具私有特性和公有特性,不能被类外函数直接访问。类的默认访问权限是private。
三、对象的定义
在进入正式的讨论之前,先说一下本人曾出现的一个很囧的错误,就是未定义对象就使用类的成员(⊙▂⊙),程序如下:
例程2.1
#include <iostream>
using namespace std;
class A
{
public:
void func();
};
void A::func()
{
cout<<1;
}
int main()
{
func();//就是这里。。。
return 0;
}
下面的正确:
例程2.2
1 #include <iostream> 2 using namespace std; 3 4 class A 5 { 6 public: 7 void func(); 8 }; 9 10 void A::func() 11 { 12 cout<<1; 13 } 14 15 int main() 16 { 17 A a1;//先定义对象,再调用a1的成员函数 18 a1.func(); 19 return 0; 20 }
定义一个变量一般有两个步骤:1.为变量申请相应的内存空间;2.系统用默认的值或程序中提供的初值初始化申请得的内存空间。同样,对象的定义也有两个步骤:1.申请相应的内存空间;2.系统调用适当的构造函数初始化内存空间。
申请内存空间不谈,关于初始化,类使用的是构造函数。
1.构造函数
构造函数的使用有几点规则:(1)构造函数与所属的类同名,没有返回值类型;(2)构造函数在创建对象时被调用,用以对新创建的对象赋初值。(3)构造函数在创建对象时被系统自动调用,时隐式调用。(4)构造函数必须是类的公有成员,一般在.h文件中给出原型,在cpp文件中给出实现。(5)如果程序中没有给出构造函数,系统会自动生成一个默认形式的构造函数(无形参的空函数)。还有一些规则在这里不说明。
以下是使用构造函数的一个例子,注意对比构造函数A和普通成员函数print使用的不同。
例程2.3
#include <iostream> using namespace std; //类 class A { public: A();//公有成员,没有返回值类型 void print(); private: int i; }; //构造函数实现,没有返回值类型 A::A() { i=12; cout<<"调用此构造函数"<<endl; } void A::print() { cout<<"i="<<i<<endl; } int main() { A a; a.print(); return 0; }
输出结果:
调用此构造函数
i=12
若不在程序中给出构造函数,结果会怎么样?见下面的程序
例程2.4
#include <iostream> using namespace std; //类 class A { public: // A();//公有成员,没有返回值类型 void print(); private: int i; }; /*构造函数实现 A::A() { i=12; cout<<"调用此构造函数"<<endl; }*/ void A::print() { cout<<"i="<<i<<endl; } int main() { A a; a.print(); return 0; }
输出结果:i=2147328000
例程2.4中给出的构造函数的实现方法有一个缺点就是对于所有新创建的对象,初始化的值是相同的。下面给出可以根据需要用不同的值初始化所创建的对象的方法。
例程2.5
#include <iostream> using namespace std; //类 class A { public: A(int j);//带有形参j void print(); private: int i; }; //构造函数实现 A::A(int j) { i=j;//用j给i赋值,而j在创建对象时给出 cout<<"调用此构造函数"<<endl; } void A::print() { cout<<"i="<<i<<endl; } int main() { A a(16),b(11);//创建对象时给出j的值,注意若不给出参数会出错,因为程序中没有不带形参的构造函数 a.print(); b.print(); return 0; }
运行结果:
调用此构造函数
调用此构造函数
i=16
i=11
当程序中有不止一个构造函数时,系统视情况选择要调用的构造函数。见以下例程
例程2.6
1 #include <iostream> 2 using namespace std; 3 //类 4 class A 5 { 6 public: 7 A(int j);//带有形参的构造函数 8 A();//不带形参的构造函数 9 private: 10 int i; 11 }; 12 //构造函数实现 13 A::A(int j) 14 { 15 i=j;//用j给i赋值,而j在创建对象时给出 16 cout<<"调用带有形参的构造函数"<<endl; 17 } 18 //构造函数实现 19 A::A() 20 { 21 cout<<"调用不带形参的构造函数"<<endl; 22 } 23 24 int main() 25 { 26 A a(16),b; 27 return 0; 28 }
运行结果:
调用带有形参的构造函数
调用不带形参的构造函数
创建对象a时由于a带有参数故调用构造函数A(int j),创建b时由于b不带参数故而调用A().
当要自定义构造函数初始化对象中动态内存时,要注意防止以下的错误操作。
1 #include <iostream> 2 #include <cstring> 3 using namespace std; 4 5 class A 6 { 7 public: 8 A(char* set_name); 9 char* name;//为简便这里将name定为public 10 }; 11 12 A::A(char* set_name) 13 { 14 //name=*set_name;//错误 15 //正确:先申请内存空间,再初始化 16 name=new char[strlen(set_name)+1]; 17 if(name!=NULL) strcpy(name,set_name); 18 } 19 20 int main() 21 { 22 A a("123"); 23 cout<<a.name; 24 return 0; 25 }
运行结果:123.
当对象撤销就是生命期结束后,系统调用析构函数回收对象所占资源。
2.析构函数
关于析构函数,有几点说明::(1)构造函数名是所属的类的类名前加上“~”,没有返回值类型,没有形参(构造函数可有形参);(2)析构函数在撤销对象时被调用,用以释放对象的内存空间。(3)析构函数被系统自动调用,是隐式调用。(4)析构函数必须是类的公有成员,一般在.h文件中给出原型,在cpp文件中给出实现。(5)如果程序中没有给出析构函数,系统会自动生成一个默认形式的析构函数。还有一些规则在这里不说明。
以下是使用析构函数的一个例子,注意析构函数使用的格式。
例程2.6
#include <iostream> using namespace std; //类 class A { public: A(int j); ~A(); void print(); private: int i; }; //构造函数实现 A::A(int j) { i=j; cout<<"调用此构造函数"<<endl; } //析构函数实现 A::~A() { cout<<"调用此析构函数"<<endl; } void A::print() { cout<<"i="<<i<<endl; } int main() { A a(16); a.print(); //a.print(); return 0; }
运行结果:
调用此构造函数
i=16
调用此析构函数
注意当对象生命期结束时析构函数才被调用。例程12中对象a生命期的结束是在程序结束之时。见以下例程,注意输出结果中“调用此析构函数”的位置。
#include <iostream> using namespace std; //类 class A { public: A(int j); ~A(); void print(); private: int i; }; //构造函数实现 A::A(int j) { i=j; cout<<"调用此构造函数"<<endl; } //析构函数实现 A::~A() { cout<<"调用此析构函数"<<endl; } void A::print() { cout<<"i="<<i<<endl; } int main() { A a(16); a.print(); a.print();//第二次调用成员函数依然成功,此时析构函数未被调用 cout<<"***"<<endl; return 0; }
输出结果:
调用此构造函数
i=16
i=16
***
调用此析构函数
值得注意的是,系统生成的默认形式的析构函数只会释放对象本身所占据的内存,对象通过动态内存分配(new)获得的内存空间是不会被释放的。要释放这些资源要自定义析构函数。见以下例程:
1 #include <iostream> 2 #include <cstring> 3 using namespace std; 4 5 class A 6 { 7 public: 8 A(char* set_name); 9 ~A(); 10 char* name;//为简便这里将name定为public 11 }; 12 13 A::A(char* set_name) 14 { 15 name=new char[strlen(set_name)+1]; 16 strcpy(name,set_name); 17 cout<<"调用构造函数"<<endl; 18 } 19 20 A::~A() 21 { 22 delete[]name;//回收通过new获得的内存空间 23 name=NULL;//避免野指针的出现 24 cout<<"调用析构函数"<<endl; 25 } 26 27 int main() 28 { 29 A a("123"); 30 cout<<a.name<<endl; 31 return 0; 32 }
运行结果:
调用构造函数
123
调用析构函数
3.拷贝构造函数:
创建新的对象时,可以通过调用构造函数初始化新建对象,也可以利用已有的对象初始化新建的对象。此时要调用拷贝构造函数。拷贝构造函数也属于构造函数,类构造函数是在创建对象时被调用的。
拷贝构造函数是特殊的构造函数,它的形参是本类的对象引用,原型:类名 (const 类名 &对象名),这里”&对象名“的对象不是真的已创建的对象,加上const是为防止对象被修改,具体见下例:
1 #include <iostream> 2 #include <cstring> 3 using namespace std; 4 5 class A 6 { 7 public: 8 A(int j);//构造函数 9 ~A();//析构函数 10 A(const A&a);//拷贝构造函数,a是未经创建的对象,相当于形参 11 void print(); 12 private: 13 int i; 14 }; 15 //构造函数 16 A::A(int j) 17 { 18 i=j; 19 cout<<"调用构造函数"<<endl; 20 } 21 //析构函数 22 A::~A() 23 { 24 cout<<"调用析构函数"<<endl; 25 } 26 //拷贝构造函数 27 A::A(const A&a) 28 { 29 i=a.i; 30 cout<<"调用拷贝构造函数"<<endl; 31 } 32 33 void A::print() 34 { 35 cout<<"i="<<i<<endl; 36 } 37 38 int main() 39 { 40 A a1(123); 41 A a2=a1;//用a1初始化a1 42 a2.print(); 43 return 0; 44 }
结果:
调用构造函数
调用拷贝构造函数
i=123
调用析构函数
调用析构函数
解释:第一次调用构造函数初始化a1,之后调用拷贝构造函数初始化a2,接着输出a2中i的值,可以看到a2.i的值跟a1.i一样,表明”拷贝“的含义,之后两次调用析构函数撤销a1和a2的空间。
调用拷贝构造函数出现在以下几种情况:
(1)用一个对象初始化同类的另一个对象,如上例。
(2)当函数的形参为类的对象时,在调用函数,用实参给形参传值时,系统自动调用拷贝构造函数。见下例:
1 #include <iostream> 2 using namespace std; 3 4 class A 5 { 6 public: 7 A(A&a);//拷贝构造函数,a是未经创建的对象,相当于形参 8 A(int j); 9 void print(); 10 private: 11 int i; 12 }; 13 //构造函数 14 A::A(int j) 15 { 16 i=j; 17 } 18 //拷贝构造函数 19 A::A(A&a) 20 { 21 i=a.i; 22 cout<<"调用拷贝构造函数"<<endl; 23 } 24 25 void A::print() 26 { 27 cout<<"i="<<i<<endl; 28 } 29 //以类的对象为形参的函数 30 void func(A b) 31 { 32 cout<<"**"<<endl; 33 b.print(); 34 } 35 36 int main() 37 { 38 A a1(12); 39 cout<<"*"<<endl; 40 func(a1);//以a1为实参给形参传值 41 return 0; 42 }
输出:
*
调用拷贝构造函数
**
i=12
注意”调用拷贝构造函数“的输出在”*“和”**“之间,表明在调用函数时调用拷贝构造函数。
(3)当函数的返回值类型是类的对象时,系统自动调用拷贝构造函数。
注意,用对象a初始化对象b时调用构造函数,但赋值不会。如ClassA a;a=b。
当函数中没有给出拷贝构造函数时,系统将自动产生一个默认的拷贝构造函数,这个拷贝构造函数可以完成对象的数据成员的值的简单复制,这种复制是浅复制。以上的各个例子使用的也都是浅复制。浅复制在处理动态分配的空间时会出现障碍。见下例:
1 #include <iostream> 2 #include <cstring> 3 using namespace std; 4 5 class A 6 { 7 public: 8 A(char *q); 9 ~A(); 10 private: 11 char *p; 12 }; 13 14 A::A(char *q) 15 { 16 p=new char[strlen(q)+1]; 17 strcpy(p,q); 18 cout<<"调用构造函数"<<endl; 19 } 20 21 A::~A() 22 { 23 delete[]p; 24 p=NULL; 25 cout<<"调用析构函数"<<endl; 26 } 27 28 int main() 29 { 30 A a1("qwe"); 31 A a2=a1; 32 return 0; 33 }
运行程序,在输出“调用构造函数”和一次“调用析构函数”后就提示内存操作非法(需要说明的是,在VC++6.0中会提示出错,但在codeblocks上运行不会)。从输出结果可以分析出在系统尝试调用析构函数撤销对象a2时出错。系统调用自动生成的拷贝构造函数初始化a2,是浅拷贝,复制a1.*p存储的地址给a2.*p,就是说初始化之后,a2.*p和a1.*p指向同一片内存区域(存放“qwe”的内存区域),这样在撤销a1时回收存放qwe的内存区域,在撤销a2时再一次回收同一片内存空间,于是出现错误。
以上问题的解决方法是进行深拷贝。就是把a1.*p指向的内存区域存放的“qwe”复制给a2.*p指向的内存区域,就是对内存区域的内容进行复制而非对地址进行复制。上面的程序修改如下:
1 #include <iostream> 2 #include <cstring> 3 using namespace std; 4 5 class A 6 { 7 public: 8 A(char *q); 9 A(const A &b);//拷贝构造函数 10 ~A(); 11 private: 12 char *p; 13 }; 14 15 A::A(char *q) 16 { 17 p=new char[strlen(q)+1]; 18 strcpy(p,q); 19 cout<<"调用构造函数"<<endl; 20 } 21 22 //深拷贝 23 A::A(const A &b) 24 { 25 p=new char[strlen(b.p)+1]; 26 strcpy(p,b.p); 27 cout<<"进行深拷贝"<<endl; 28 } 29 A::~A() 30 { 31 delete[]p; 32 p=NULL; 33 cout<<"调用析构函数"<<endl; 34 } 35 36 int main() 37 { 38 A a1("qwe"); 39 A a2=a1; 40 return 0; 41 }
运行结果:
调用构造函数
进行深拷贝
调用析构函数
调用析构函数
4.对象数组
形式:类名 数组名[常量表达式];
如 ClassA a[10];
对象数组的初始化:ClassA a[2]={ClassA("123",1),ClassA("145",2)};
关于对象数组,可以类比结构体数组。
四、静态成员
类中的静态成员不是对象的成员,而是类的成员,它被类的所有对象共享。类中的静态成员的初始化必须在类外进行。静态成员的声明:static 数据类型 变量名,静态成员的访问:①类名::静态成员名,②对象名.静态成员名。静态成员跟非静态成员一样可以通过public,private,protected设置访问权限。
下例体现了静态成员的一些特点。
1 #include <iostream> 2 using namespace std; 3 4 class A 5 { 6 public: 7 A(int j); 8 static int static_i; 9 }; 10 //静态成员的初始化格式 11 int A::static_i=123; 12 13 A::A(int j) 14 { 15 static_i=j; 16 } 17 18 int main() 19 { 20 A a1(1),a2(2); 21 cout<<a1.static_i<<" "<<a2.static_i<<endl; 22 return 0; 23 }
运行结果:
2 2
由运行结果可以体会出静态成员是类中所有对象共享的。
关于静态函数成员,以下例程体现了它的几个特点。
1 #include <iostream> 2 using namespace std; 3 4 class A 5 { 6 public: 7 void print1();//非静态函数成员 8 static void print2();//静态函数成员 9 static int m;//公有静态数据成员 10 private: 11 static int n;//私有静态数据成员 12 int t;//非静态数据成员 13 14 }; 15 //静态成员的初始化格式 16 int A::m=1; 17 int A::n=2; 18 //非静态函数成员 19 void A::print1() 20 { 21 cout<<"m="<<m<<endl;//非静态函数成员可以访问静态成员 22 cout<<"n="<<n<<endl; 23 cout<<"t="<<t<<endl; 24 } 25 //静态函数成员 26 void A::print2() 27 { 28 cout<<"m="<<m<<endl;//静态函数成员可以访问静态成员 29 cout<<"n="<<n<<endl;//静态函数成员可以访问静态成员 30 //cout<<"t="<<t<<endl;//静态函数成员不能直接访问非静态成员 31 } 32 33 int main() 34 { 35 A a1; 36 a1.print1(); 37 a1.print2(); 38 return 0; 39 }
非静态函数成员可以访问静态成员,而静态函数成员不能直接访问非静态成员。静态成员是类中所有对象共享的,因而非静态函数成员能访问静态成员(非静态函数成员是属于对象自身的)。静态函数成员为什么不能直接访问非静态成员?如果可以的话,由于静态函数所有对象共享,于是一个对象通过调用静态函数访问其他所有对象的非静态成员,这明显是不合理的。
四、常成员与常对象
五、友元和友元函数
通过友元,一个
》》待续