文章目录
前言
我想总结这些常见的特殊函数类型
1 函数的重载
-
函数的重载(founction overloading) 定义:C++允许同一函数名定义多个函数,而这些函数的参数个数和参数类型可以不相同,这就是函数的重载。 即对一个函数名重新赋予它新的含义,使得一个函数名可以多用。所谓重载,其实就是“一物多用”。
-
不仅函数可以重载,运算符也可以重载。例如
>>
和<<
,既可以作为移位运算符也可以作为输入输出流中的插入运算符和数据输入流中的提取运算符。 -
函数的重载并不要求函数体(即花括号{}里的内容)是相同的。重载函数除了允许参数的类型不同以外,还允许参数的个数不同。
-
重载函数的参数个数、参数类型或参数顺序三者中必须至少有一种不同,返回值类型可以相同也可以不同。
总结来说函数的重载就是函数名字是相同的。
2 函数的调用与声明
- 如果被调用函数的定义出现在主调用函数之前,可以不必加以声明。
- 函数声明的位置可以再调用函数所在的函数中,也可以在函数之外,只不过在函数之外的话其作用域是整个文件。
3 函数的递归调用
- 在调用一个函数的过程中又出现直接或间接的调用该函数本身,成为函数的递归调用。包含递归调用的函数称为递归函数。
4 内置函数
- 内置函数的目的或者作用就是为了提高程序的执行效率。省略了跳转的时间,直接顺序执行程序,节省了总程序的运行时间。
- C++提供了一种提高函数调用效率的方法,在编译时将所调用的代码直接嵌入到主调用函数中,而不是将流程转出去。这种嵌入到主函数中的函数称为内置函数(inline founction),又称为内嵌函数/内联函数。
- 指定内置函数只需要在函数首行左端加一个关键词inline即可。
5 函数模板
- 函数模板(founction temple)即建立一个通用函数,其函数类型和形参类型不具体指定,用一个虚拟的类型来代表。
- 函数模板的使用:
template <typename T> //模板声明,其中T为类型参数,代表一个虚拟的类型名
T max(T a, T b, T c) //定义一个通用函数,用T作虚拟的类型名
{
if(b>a) a=b;
if(c>a) a=c;
return a;
}
int main()
{
int x=1,y=2,z=3;
max(x,y,z); //调用模板函数,此时T被int取代
}
6 变量的存储方法与类别
6.1 变量的存储方法
内存中供用户使用的存储空间的情况:
程序区 |
---|
静态存储区 |
动态存储区 |
- 静态存储
全局变量都存储在静态存储区(加static声明),程序开始就给分配存储单元,直到程序执行完毕就释放这些空间。 - 动态存储
存储:函数形式参数;函数中定义的变量(未加static声明);函数调用时的现场保护和返回地址等。
6.2 变量的存储类别
变量的存储类别(storage class)共有4种:自动的(auto),静态的(static),寄存器的(register),外部的(extern)。
-
自动变量(auto)
其实就是函数的形参和在函数中定义的变量(包括在复合语句中如for中定义的变量),都属于自动变量。也就是平常在变量定义时前边不加任何东西的话,系统就默认为是auto自动变量了。属于一个动态的存储方式,在函数调用或复合语句结束时自动释放空间。其中auto和int的前后顺序随意,auto也可以省略,因此平常使用最多的就是auto了。声明方式:
auto int a,b;
- 静态局部变量(static):
就是在函数中的局部变量在函数调用结束后不消失保留原值,即占用的存储单元不释放,在下一次调用该函数时,该变量仍保留上一次函数调用结束时的值。
典型例子:
#include <iostream>
using namespace std;
int f(int a) //定义f函数,a为形参
{
auto int b=0; //定义b为自动变量
static int c=3; //定义c为静态局部变量
b=b+1;
c=c+1;
return a+b+c;
}
int main( )
{
int a=2,i;
for(i=0;i<3;i++)
cout<<f(a)<<″ ″;
cout<<endl;
return 0;
}
运行结果为
7 8 9
其中呢,c是静态局部变量,因此每回调用该函数时只是进行加操作而不赋初值3。
-
寄存器变量(register)
对于一些使用频繁的变量(比如这个函数中要执行10000次循环,每次循环都要引用某局部变量),则存取变量要花费不少时间,为了提高执行效率,C++允许将局部变量的值放在CPU的寄存器中,需要时直接从寄存器中取出参与运算,不必再到内存中去存取。实际很少用,了解即可。 -
外部变量(extern)
用extern是为了声明全局变量,以扩展全局变量的作用域。
比如这个已经定义好的全局变量没有在开头定义,在定义之前还想使用该变量就可以用extern来进行提前引用声明,以扩展该变量在程序文件中的作用域。
int main()
{
extern int a,b;//对全局变量a,b作提前引用说明
c=a+b;
return 0;
}
int a=15,b=7;//定义全局变量
7 内部函数和外部函数
- 内部函数: 如果一个函数只能被本文件中其他函数所引用,它称为内部函数。在定义时只需在函数名和函数类型前加static即可。
static 类型标识符 函数名()
- 外部函数: 函数定义左侧加extern表示此函数是外部函数,别的文件也可调用。
8 构造函数
- 构造函数(constructor)是用来对类对象进行初始化的,是一种特殊的成员函数,与其他成员函数不同,不需要用户来调用它,在建立对象时就自动执行了。它也可以定义在类外边。
- 构造函数的名字必须与类名相同,而不能任意命名,以便编译器能自动识别并把其当做构造函数处理。
- 利用构造函数是为了给对象中的数据成员函数赋初值。
- 构造函数也可以重载,称为构造函数的重载。
9 析构函数
- 析构函数(destructor)也是一个特殊的成员函数,作用与构造函数相反,名字是类名前加一个
~
符号,在C++中这是位取反运算符,从这点也可想到:析构函数是与构造函数作用相反的函数。 - 当对象的生命周期结束时,会自动执行析构函数。
- 析构函数的作用:析构函数的作用不是删除对象,而是在撤销对象占用的内存之前完成一些清理工作,为了释放资源。 它还可以被用来执行“用户希望在最后一次使用对象之后所执行的一些操作”。
- 具体是四种情况:
(1)普通情况: 函数中定义对象了,函数被调用结束时会自动执行析构函数。
(2)函数中有静态(static)局部变量了,那再函数调用结束时这个对象并不会释放,因此也不会调用析构函数,只有当main函数结束或者调用exit函数结束程序时,才调用static局部对象的析构函数。
(3)定义了一个全局的对象,那在程序流程离开其作用域时(如main结束或调用exit时),才调用该全局的对象的析构函数。
(4)new新建了一个对象,那当用delete释放该对象时才会调用执行该对象的析构函数。 - 析构函数由于没有函数参数所以是不能被重载的。它不返回任何值、没有函数类型、也没有函数参数。一个类只能有一个析构函数。
调用构造函数和析构函数的顺序
- 对统一存储类别的对象而言:先构造的后析构,后构造的先析构。相当于一个栈,先进后出。
10 this指针
在 C++ 中,每一个对象都能通过 this 指针来访问自己的地址。this 指针是所有成员函数的隐含参数。因此,在成员函数内部,它可以用来指向调用对象。
友元函数没有 this 指针,因为友元不是类的成员。只有成员函数才有 this 指针。
一个实例:
#include <iostream>
using namespace std;
class Box
{
public:
// 构造函数定义
Box(double l=2.0, double b=2.0, double h=2.0)
{
cout <<"Constructor called." << endl;
length = l;
breadth = b;
height = h;
}
double Volume()
{
return length * breadth * height;
}
int compare(Box box)
{
return this->Volume() > box.Volume();
}
private:
double length; // Length of a box
double breadth; // Breadth of a box
double height; // Height of a box
};
int main(void)
{
Box Box1(3.3, 1.2, 1.5); // Declare box1
Box Box2(8.5, 6.0, 2.0); // Declare box2
if(Box1.compare(Box2))
{
cout << "Box2 is smaller than Box1" <<endl;
}
else
{
cout << "Box2 is equal to or larger than Box1" <<endl;
}
return 0;
}
11 虚函数
- 虚函数的作用是为了实现动态多态性:即同一类族中不同类的对象,对同一函数调用做出不同的响应。
- 虚函数允许在派生类中重新定义与基类同名的函数,并且可以通过基类指针或引用来访问基类和派生类中的同名函数。
- 在声明虚函数时,在最左边加上一个关键字virtual,这样就把类中的函数声明成了虚函数。
//Student是基类,Graduate是派生类,他们都有display这个同名的函数
#include<iostream>
#include<string>
using namespace std;
//------------声明基类Student-------------
class Student
{
public:
Student(int, string, float); //声明构造函数
virtual void display(); //声明虚函数
protected: //受保护成员,派生类可以访问
int num;
string name;
float score;
};
//Student类成员函数的实现
Student::Student(int n,string nam, float s) //定义构造函数
{num=n;name=nam;score=s;}
void Student::display() //定义输出函数
{cout<<"num:"<<num<<"\nname:"<<name<<"\nscore:"<<score<<"\n\n";}
//------------声明公用派生类Graduate---------------
class Graduate:public Student
{
public:
Graduate(int, string, float, float); //声明构造函数
void display(); //定义与基类同名的函数
private:
float wage;
};
//Graduate类成员函数的实现
Graduate::Graduate(int n,string nam, float s,float w):Student(n,nam,s),wage(w){}
void Graduate::display()
{cout<<"num:"<<num<<"\nname:"<<name<<"\nscore:"<<score<<"\nwage:"<<wage<<endl;}
//--------------主函数-------------------
int main()
{
Student stud1(1001,"Wang",35.2);
Graduate grad1(2001,"Hang",74.2, 1500);
Student *pt=&stud1;
pt->display();//输出基类中数据
pt = &grad1;
pt->display();//输出派生类中数据
return 0;
}
输出:
12 格式控制符
- fixed,scientific,left,right,ws,setfill,setw,setprecision,这些是格式控制符,在使用时要加头文件
#include <iomanip>
。
fixed是固定的意思、precision是精度的意思、setw是宽度的意思、setfill是填充的意思、ios是输入输出流、flags是标志的意思。
例如:
double a=1.23456789;
cout<<setprecision(4)<<a 表示输出数a的4位有效数字
cout<<setiosflags(ios::fixed)<<setprecision(4)<<a 表示输出数a的4位小数
double b=10;
cout<<setfill('*')<<setw(8)<<b ;输出b的时候占8个位,不够的用*填充
输出:
13 类的嵌套使用
在一个类的内部定义另一个类,我们称之为嵌套类(nested class),或者嵌套类型,外边这个类称为外围类。 之所以引入这样一个嵌套类,往往是因为外围类需要使用嵌套类对象作为底层实现,并且该嵌套类只用于外围类的实现,且同时可以对用户隐藏该底层实现。
虽然嵌套类在外围类内部定义,但它是一个独立的类,基本上与外围类不相关。它的成员不属于外围类,同样,外围类的成员也不属于该嵌套类。嵌套类的出现只是告诉外围类有一个这样的类型成员供外围类使用。并且,外围类对嵌套类成员的访问没有任何特权,嵌套类对外围类成员的访问也同样如此,它们都遵循普通类所具有的标号访问控制。
嵌套类可以直接引用外围类的静态成员、类型名和枚举成员(假定这些成员是公有的)。 类型名是一个typedef名字、枚举类型名、或是一个类名。
嵌套类的特点:
- 嵌套类不可以访问外围类的任何成员;
- 外围类可以通过访问对象访问嵌套类的公有成员(public),但不能访问保护(protected)或私有成员(private);
- 嵌套类只能由外围类使用。
14 C++中的重载、重写、隐藏的区别浅析
14.1 重载
重载来自(overload):指的是同一可访问区内被声明的几个具有不同参数列(参数的类型,个数,顺序不同)的同名函数,根据参数列确定调用哪个函数,重载不关心函数返回类型。
class A{
public:
void test(int i);
void test(double i);
void test(int i, double j);
void test(double i, int j);
int test(int i); //错误,非重载
};
比如这前四个就是重载函数。
14.2 隐藏
隐藏指的是派生类的函数屏蔽了与其同名的基类函数。 注意只要同名函数,不管参数列表是否相同,基类函数都会被隐藏。
比如以下实例:
#include<iostream>
using namespace std;
class A
{
public:
void fun1(int i, int j)
{
cout<<"A::fun1():"<<i<< " " <<j<<endl;
}
};
class B:public A
{
public:
//隐藏
void fun1(double i)
{
cout<<"B::fun1():"<<i<<endl;
}
};
int main()
{
B b;
b.fun1(5);//调用B类中的函数
b.fun1(1, 2);//出错,因为基类函数被隐藏
system("pause");
return 0;
}
基类
已存在的类称为基类或父类,比如“马”;
派生类
-
从基类派生出来新建立的类称为派生类或子类,比如“公马”;
从已有的类(父类)产生一个新的类,称为类的派生。 -
定义派生类的一般形式:
class 派生类名:基类列表
{
成员列表
}
- 派生类的构成:
- 派生类的访问控制
单个类的访问控制:
public(公有成员)
: 在类的内部和外部都可以访问的成员。
private(私有成员)
: 在类的内部可以访问, 在类的外部不可以访问, 只能通过成员函数或友元函数进行访问。
protected(保护成员)
: 与私有成员类似, 但在派生类中可以访问。
类之间的关系
类之间的三种关系:
- 包含关系(has-A)
class B{ private: A a;}
- 使用关系(uses-A)
class B{public: void method(A &a);}
- 继承关系(is-A)
class B: public A{}
继承的概念
- 继承: 通过继承联系在一起的类构成一种层次关系。
- 通常在层次关系的根部有一个基类, 也称为父类。
- 其他类直接或间接地从基类继承而来, 这些继承得到的类称为派生类, 也称为子类。
- 基类负责定义在层次关系中所有类共同拥有的成员, 而每个派生类定义各自特有的成员。
14.3 重写
重写翻译自(override),也翻译成覆盖,**指的是派生类中存在重新定义的函数。**其函数名,参数列表,返回值类型,所有都必须同基类中被重写的函数一致。只有函数体不同(也就是花括号{}内的内容)。
派生类调用时会调用派生类的重写函数,不会调用被重写函数。重写的基类中被重写的函数必须有virtual修饰。
比如以下例子:
#include<iostream>
using namespace std;
class A{
public:
virtual void fun3(int i){
cout << "A::fun3() : " << i << endl;
}
};
class B : public A{
public:
//重写
virtual void fun3(double i){
cout << "B::fun3() : " << i << endl;
}
};
int main(){
A a;
B b;
A * pa = &a;
pa->fun3(3);
pa = &b;
pa->fun3(5);
system("pause");
return 0;
}
15 关于取值符&
与指针*
简述
*
: 间接运算符。 用来说明该变量是指针变量。&
: 取址运算符。*&
:表示地址值所代表的内存块的内容。int *p;
中,我们称int是指针变量p的基类型。
- 给指针变量赋值
int k=1,*p; p = &k;//将变量k的地址赋给了p,也就是p指向了变量k。int j = *p;
这个例子中&k
表示取出变量k
的地址赋给p
,*p
表示取出p
也就是取地址&k
中的内容给了变量j
。 - 给指针变量赋空值
p=NULL;
等价于p='\0';
或p=0;
- 像
*&i
、(*p)++
、++*p
、*p++
:这些都是自右向左计算的。 *p +1
表取指针变量p所指存储单元中的内容后加1*p+=1
可写成(*p)++
或++*p
*++p
表示指针地址向后移一位之后返回;++*p
表示指针所指的值+1返回,比如以下例题很经典:
又比如:
int a = 3;
int *p = &a;
cout << *p;
//输出:3
16 拷贝构造函数(又称复制构造函数)
- 拷贝构造函数(复制构造函数)就是在创建对象时,使用同一类中之前创建的对象来初始化新创建的对象。
- 剑指offer面试题1:定义赋值运算符函数
//原类定义
class CMystring
{
public:
CMystring(char* pData = nullptr);
CMystring(const CMystring& str);
~CMystring(void);
private:
char* m_pData;
}
//定义拷贝(复制)构造函数
CMystring& CMystring::operator == (const CMystring &str)
{
if(this != &str)
{
CMyString strTemp(str);
char* pTemp = strTemp.m_pData;
strTemp.m_pData = m_pData;
m_pData = pTemp;
}
return* this;
}