内联函数、重载函数
在调用一个函数的时候,需要首先要把参数放到栈中, 返回地址也要放到栈中。 这个函数执行完返回以后,要从栈中取出返回地址,再跳转到返回地址去执行。
内联函数
函数调用是有时间开销的。如果函数本身只有几条语句,执行非常快,而且函数被反复执行很多次,相比之下调用函数所产生的这个开销就会显得比较大。
为了减少函数调用的开销,引入了内联函数机制。编译器处理对内联函数的调用语句时,将整个函数的代码插入到调用语句处,而不会产生调用函数的语句。
定义内联函数时,在定义前面加上inline
关键字。
inline int Max (int a, int b) { if (a > b) return a; return b; }
内联成员函数
其定义方式可以为:
inline
+成员函数
在类定义内部定义整个函数体
class B { inline void func1 () ; void func2 () {}; }; void B::func1(){}
函数重载
一个或多个函数,名字相同,参数个数或参数类型相同,叫做函数的重载。
如下面函数为重载函数:
int Max (double f1, double f2) {}Max(3.4 , 2.5 ); int Max (int n1, int n2) {}Max(2 , 4 ); int Max (int n1, int n2, int n3) {}Max(1 , 2 , 3 ) Max(3 , 2.4 )
其优点是:
简化函数命名规则
编译器只需要根据语句中的实参个数和类型判断应调用的函数
成员函数的重载
#include <iostream> using namespace std ;class Location { private : int x, y; public : void init (int x = 0 , int y = 0 ) ; void valueX (int val) { x = val; } int valueX () { return x; } };
valueX()
为成员函数重载,使用时仍然可以参数缺省。
void Location::init(int X, int Y){ x = X; y = Y } int main(){ Location A; A.init(3 ); A.valueX(5 ); cout <<A.valueX(); return 0 ; }
函数缺省
C++中,定义函数时,可以让最右边的连续若干个参数 有缺省值,调用函数时,若相应位置不写参数,参数就是缺省值。 如:
void func (int x1, int x2 = 2 , int x3 = 3 ) {}func(10 ); func(10 , 8 ); func(10 , , 8 );
其优点为:
函数参数可缺省的目的在于提高程序的可扩充性。
如果某个写好的函数要添加新的参数,而原先那些调用该函数的语句,未必需要使用新增的参数,为了避免对原先那些函数调用语句的修改,就可以使用缺省参数。
成员函数缺省
使用缺省参数时,需要避免函数重载时的二义性。
class Location { private : int x, y; public : void init (int x = 0 , int y = 0 ) ; void valueX (int val = 0 ) { x = val; } int valueX () { return x; } };
使用时:
int main () { Location A; A.valueX(); }
程序设计方法
类基础
类定义变量的过程为实例化。
class CRectangle { public : int w, h; void init (int w_, int h_) { w = w_; h = h_; } int area () { return w * h; } int perimeter () { return 2 * (w + h); } };
对象之间可以用 ‘=’ 进行赋值 ,不能用 ‘==’, ‘!=’, ‘>’, ‘<’, ‘>=’, ‘<=’进行比较 ,除非运算符进行重载
访问类成员变量
对象名.成员名
CRectangle r1, r2; r1.w = 5 ; r2.init(3 , 4 );
指针->成员名
CRectangle r1, r2; CRectangle *p1 = & r1; CRectangle *p2 = & r2; p1->w = 5 ; p2->inti(3 , 4 );
引用名.成员名
CRectangle r2; CRectangle & rr = r2; rr.w = 5 ; rr.init(3 , 4 )
类的成员函数
成员函数体和类的定义可以分开写:
class CRectangle { public : int w, t; int area () ; int perimeter () ; void init (int w_, int h_) ; }; int CRectangle::area(){ return w * h; } int CRectangle::perimeter(){ return 2 * (w + h); } void CRectangle::init(int w_, int h_){ w = w_; h = h_; }
类的构造函数
类的构造函数是成员函数的一种,但需要遵循特殊的规律:
名字与类名相同,可以有参数,不能有返回值(void也不行)
其作用是初始化对象,比如给成员变量赋初始值
对象生成时自动调用构造函数,对象一旦生成,就无法再执行构造函数
一个类可以有多个构造函数
如果定义类时未写构造函数,编译器自动生成一个无参数的构造函数,不做任何操作;
如果定义类时定义了构造函数,编译器不生成无参数构造函数
构造函数作用
执行必要的初始化工作
有时对象没被初始化就使用,会导致程序出错
class Complex { private : doubel real, imag; public : void Set (double r, double i) ; }; Complex c1; Complex *pc = new Complex;
编写构造函数时:
class Complex { private : double real, imag; public : Complex(double r, double i = 0 ); }; Complex::Complex(double r, double i){ real = r; imag = i; } Complex c1; Complex *pc = new Complex; Complex c2 (2 ) ; Complex c2(2, 4), c3(3, 7); Complex *pc = new Complex(5 , 9 );
有多个构造函数的情况:多个构造函数重载
class Complex { private : double real, imag; public : void Set ( double r, double i ) ; Complex(double r, double i ); Complex(double r ); Complex(Complex c1, Complex c2); }; Complex::Complex(double r, double i) { real = r; imag = i; } Complex::Complex(double r) { real = r; imag = 0 ; } Complex::Complex (Complex c1, Complex c2){ real = c1.real+c2.real; imag = c1.imag+c2.imag; }
实例化时:
Complex c1 (3 ) ; Complex c2 (1 , 0 ) ; Complex c3 (c1, c2) ;
构造函数最好是public的,private构造函数不能直接用来初始化对象。
构造函数在数组中的使用
class CSample { int x; public : CSample(){ cout << "Constructor 1 Called" << endl ; } CSample(int n) { x = n; cout << "Constructor 2 Called" << endl ; } };
实例化时:
int main () { CSample array1[2 ]; cout << "step1" <<endl ; CSample array2[2 ] = {4 ,5 }; cout << "step2" <<endl ; CSample array3[2 ] = {3 }; cout << "step3" <<endl ; CSample * array4 = new CSample[2 ]; delete []array4; return 0 ; }
输出结果为:
Constructor 1 Called Constructor 1 Called step1 Constructor 2 Called Constructor 2 Called step2 Constructor 2 Called Constructor 1 Called step3 Constructor 1 Called Constructor 1 Called
另一个例子:
class Test { public : Test(int n){ } Test(int n, int m){ } Test(){ } }; Test array1[3 ] = {1 , Test(1 ,2 )}; Test array2[3 ] = {Test(2 ,3 ), Test(1 ,2 ), 1 }; Test *pArray[3 ] = {new Test(4 ), new Test(1 ,2 )};
第三个定义了一个指针数组,不会引发Test构造函数被调用,因为其每个元素都是指针,并不是对象,因此没有进行实例化。对于这个指针数组,可以不进行初始化操作。当new
出来两个对象后,new
表达式的返回值为Test *
类型的指针。该表达式只生成了两个对象,而不是三个。pArray[2]
是一个未经初始化的指针。
复制构造函数
复制构造函数(copy constructor)只有一个参数,即对同类对象的引用 ,不能直接为同类的对象。
形如:
X::X(X &)
或X::X(const X &)
,二者选一。后者能以常量对象作为参数。
若未定义复制构造函数,编译器生成默认复制构造函数,默认复制构造函数完成复制功能。
例:
class Complex { private : double real, imag; }; Complex c1; Complex c2 (c1) ;
当定义了复制构造函数之后,编译器不会自动生成复制构造函数。
class Complex { public : double real, imag; Complex(){} Complex(const Complex & c){ real = c.real; imag = c.imag; cout <<"Copy constructor called" ; } }; Complex c1; Complex c2 (c1) ;
作用
当用一个对象去初始化同类的另一个对象时。
如上面例子中用c1初始化c2。其中Complex c2 = c1
也是初始化语句,等价于Complex c2(c1)
,是非赋值语句。
如果某函数有一个参数是类 A 的对象,该函数被调用时,类A的复制构造函数被调用
class A { public : A(){}; A(A & a){ cout <<"Copy constructor called" ; } } void Func (A a1) {} int main () { A a2; Func(a2); return 0 ; }
此时的形参a1和实参a2的值可能不相同,因为自定义的复制构造函数没有进行复制工作。当未自定义复制构造函数时,形参a1的值和实参a2的值是相等的,因为编译器自动生成的复制构造函数会进行复制工作。
输出结果:
如果函数的返回值是类A的对象,则函数返回时,A的复制构造函数被调用
class A { public : int v; A(int n){v = n;} A(const A & a){ v = a.v; cout <<"Copy constructor called" ; } }; A Func () { A b (4 ) ; return b; } int main () { cout <<Func().v<<endl ; return 0 ; }
调用Func()
函数时,首先需要生成临时返回值对象,调用复制构造函数,初始化临时返回对象,形参为b,因此输出“Copy constructor called”,且这里复制构造函数对v的值进行了复制操作,因此输出“4”。因此函数的返回值可能和b不是相等的,这取决于复制构造函数的写法。
输出结果:
类型转换构造函数
类型转换构造函数的目的是实现类型的自动转化。
特点:
只有一个参数
不是复制构造函数
编译系统自动调用转换构造函数同时创建临时变量
例:
class Complex { public : double real, imag; Complex(int i){ cout <<"IntConstructor called" <<endl ; real = i; imag = 0 ; } Complex(double r, double i){ real = r; imag = i; } }; int main () { Complex c1 (7 , 8 ) ; Complex c2 = 12 ; c1 = 9 ; cout <<c1.real<<"," <<c1.imag<<endl ; return 0 ; }
输出:
IntConstructor called IntConstructor called 9,0
析构函数
析构函数是成员函数的一种
名字与类名相同,形如~X()
没有参数和返回值
一个类最多只有一个析构函数
析构函数在对象消亡时会自动被调用,释放给类分配的对象空间。未定义析构函数时,编译器会自动生成缺省析构函数,但该析构函数不涉及释放用户申请的内存释放等清理工作。当自定义析构函数时,编译器不会生成缺省构造函数。
class String { private : char * p; public : String(){ p = new char [10 ]; } ~String(); }; String::~String(){ delete [] p; }
在初始化构造函数时,new了一个char型数组,分配了一个空间。调用析构函数时,需要将new出来的空间delete掉。
析构函数和数组
在对象数组生命周期结束时,对象数组的每一个元素的析构函数都会被调用。
class Ctest { public : ~Ctest(){ cout <<"Destructor called" <<endl ; } }; int main () { Ctest array [2 ]; cout <<"End Main" <<endl ; return 0 ; }
在程序结束之前,编译器会调用析构函数,因为是两个对象,因此会执行两次析构函数,最后输出为:
End Main Destructor called Destructor called
delete运算符
delete
运算符会导致析构函数的调用
Ctest * pTest; pTest = new Ctest; delete pTest; pTest = new Ctest[3 ]; delete [] pTest;
构造函数和析构函数调用例子
定义一个Demo类,并定义构造函数和析构函数。
class Demo { int id; public : Demo(int i){ id = i; cout <<"id=" <<id<<"Constructed" <<endl ; } ~Demo(){ cout <<"id=" <<id<<"Destructed" <<endl ; } };
Demo d1 (1 ) ; void Func () { static Demo d2 (2 ) ; Demo d3 (3 ) ; cout << “Func” << endl ; } int main () { Demo d4 (4 ) ; d4 = 6 ; cout << “main” << endl ; { Demo d5 (5 ) ;} Func(); cout << “main ends” << endl ; return 0 ; }
在main函数结束后,会依次析构局部变量、局部静态变量、全局变量。
最终的输出结果为:
id=1 Constructed id=4 Constructed id=6 Constructed id=6 Destructed main id=5 Constructed id=5 Destructed id=2 Constructed id=3 Constructed Func id=3 Destructed main ends id=6 Destructed id=2 Destructed id=1 Destructed
类成员的访问权限
通过关键字,可以定义类成员在什么位置允许访问。关键字分为三种:
private:私有成员,只能在成员函数内被访问到;缺省为私有成员
public:公有成员,在任意位置可以被访问
protected:保护成员,在被继承时使用
根据当前位置的不同,访问到类的成员也不同
在类成员函数的内部:当前对象的全部属性、函数;同类其他对象的全部属性、函数
在类的成员函数外部:只能访问该对象的公有成员
class CEmployee { private : char szName[30 ]; public : int salary; void setName (char *name) ; void getName (char *name) ; void averageSalary (CEmployee e1, CEmployee e2) } void CEmployee::setName(char *name){ strcpy (szName, name); } void CEmployee::getName(char *name){ strcpy (name, szName); } void CEmployee::averageSalary(CEmployee e1,CEmployee e2){ salary = (e1.salary + e2.salary )/2 ; } int main () { CEmployee e; strcpy (e.szName, "Tom1234567889" ); e.setName( "Tom" ); e.salary = 5000 ; return 0 ; }
设置私有成员的目的是,强制对成员变量的访问一定要通过成员函数进行,可以将私有成员隐藏起来。
第一周作业
测验
以下说法正确的是:
[ ] 每个对象内部都有成员函数的实现代码
[ ] 类的成员函数之间可以互相调用
[X] 一个类的私有成员函数内部不能访问本类的私有成员变量
[ ] 编写一个类时,至少要写一个成员函数
以下对类A的定义,哪个是正确的?
class A { int v; public : void Func () ; }; A::void Func () { }
class A { private : int v; public : void Func () { } }
class A { int v; public : A next; void Func () { } };
class A { int v; A *next; void Func () { } };
假设有以下类A:
class A { public : int func (int a) { return a * a; } };
以下程序片段,哪个是不正确的?
[ ] A a; A & r = a; r.func(5);
[ ] A *p = new A; p->func(5);
[ ] A a; a.func(5);
[x] A a, b; if( a!= b ) a.func(5);
以下程序,哪个是不正确的?
int main () { class A { public : int v; }; A * p = new A; p->v = 4 ; delete p; return 0 ; }
int main () { class A { public : int v; A * p; }; A a; a.p = new A; delete a.p; return 0 ; }
int main () { class A { public : int v; A * p; }; A a; a.p = & a; return 0 ; }
int main () { class A { int v; }; A a; a.v = 3 ; return 0 ; }
以下说法中正确的是:
[ ] 构造函数的返回值类型是 void
[ ] 一个类只能定义一个构造函数,但可以定义多个析构函数
[x] 一个类只能定义一个析构函数,但可以定义多个构造函数
[ ] 一个类一定会有无参构造函数构造函数的返回值类型是 void
对于通过 new 运算符生成的对象
[ ] 在程序结束时自动析构
[x] 在执行 delete 操作时会析构,如果没有执行delete操作,则在程序结束时自动析构
[ ] 执行 delete 操作时才能析构
[ ] 在包含该 new 语句的函数返回时自动析构
编程
描述
在一个学生信息处理程序中,要求实现一个代表学生的类,并且所有成员变量都应该是私有的。
输入
姓名,年龄,学号,第一学年平均成绩,第二学年平均成绩,第三学年平均成绩,第四学年平均成绩。
其中姓名、学号为字符串,不含空格和逗号;年龄为正整数;成绩为非负整数。
各部分内容之间均用单个英文逗号","隔开,无多余空格。
输出
一行,按顺序输出:姓名,年龄,学号,四年平均成绩(向下取整)。
各部分内容之间均用单个英文逗号","隔开,无多余空格。
样例
输入:
输出:
答案
#include <iostream> #include <cstdio> #include <string> using namespace std ;class Student {private : char name[100 ], number[100 ]; int age, grade1, grade2, grade3, grade4; public : Student(char pname[], int page, char pnumber[], int pgrade1, int pgrade2, int pgrade3, int pgrade4){ strcpy (name, pname); age = page; strcpy (number, pnumber); grade1 = pgrade1; grade2 = pgrade2; grade3 = pgrade3; grade4 = pgrade4; } int getAverageGrade () { return ((grade1 + grade2 + grade3 + grade4) / 4 ); } char * getName () { return name; } char * getNum () { return number; } int getAge () { return age; } }; int main (int argc, char * argv[]) { char name[100 ], number[100 ]; int age, grade1, grade2, grade3, grade4; cin .getline(name, 100 , ',' ); cin >> age; char a = getchar(); cin .getline(number, 100 , ',' ); cin >> grade1 >> a >> grade2 >> a >> grade3 >> a >> grade4; Student s (name, age, number, grade1, grade2, grade3, grade4) ; cout << s.getName() << "," << s.getAge() << "," << s.getNum() << "," << s.getAverageGrade(); return 0 ; }
输出:
Tom,22,2017110,99,98,89,95 Tom,22,2017110,95