C++认识函数
> 函数的种类大概分成两种,成员函数和普通函数,(后面c++11还会有 lambda表达式写的临时函数)
> 函数存放在内存的代码区里,所以在类中不占据空间大小。
>
> ##成员函数 成员函数又分成默认函数和自定义函数,默认函数前面介绍类的时候有提到,就是构造,析构,拷贝,赋值这几种。\
> 自定义函数就是在函数作用域内声明定义的函数,有虚函数,静态函数,常量函数,内联函数和静态函数。\
> 后面挨个介绍一下这些函数的使用。
>
> #默认函数:定义类的时候,不用自己定义声明就自动生成的函数。
> 主要功能给类提供一些类的最基本属性,构造,拷贝,赋值,析构。
> 这就能保证类具备了声明,定义,赋值,释放,拷贝的基本功能。
>
> 有什么基本功能都知道,但是我们因为一些原因,想让一个类不具备这些功能怎么办? 有两种方法: 将想要禁用的默认函数设置成私有
例如
class A
{
private:
A(){}
~A(){}
A(const A& a){}
A& operator=(const A& a) {}
};
用 delete 禁用掉不想要的操作 (c++11的方法)
class A
{
private:
A() = delete;
~A() = delete;
A(const A& a) = delete;
A& operator =(const A& a) = delete;
};
#自定义成员函数
在类里可以自定义成员函数,除了要清楚的区分出虚函数和其他函数,其他没啥要说的。
基类的虚函数需要用 virtual 来表明,子类不用。\
但是子类最好在声明的时候加上 virtual 和 override 关键字,\
这样可以减少因为手误导致的错误,也增加了代码的可读性。
class A
{
public:
virtual void pppddp() = 0;
};
class B : public A
{
public:
virtual void pppddp() override; //正确
virtual void ppddpd() override; // 错误
virtual void ppddpd(); //正确
};
##普通函数
普通函数和类的成员函数一样,可以把声明和定义分开。
例如:
fun.h:
void p();
fun.cpp:
void p(){}
需要注意的是,如果函数的声明和定义都在头文件里,那么可能会在编译的 过程中带来二义性问题。
head.h:
void p(){}
test.h:
#include "head.h"
void pp();
test.cpp:
#include "test.h"
void pp() {p();}
main.cpp:
#include "test.h"
int main()
{
pp();
return 0;
}
// 链接不过
// 解决这种问题的方式就是通常可以把函数的声明放在.h文件中,定义放在.cpp中,这样就不存在二义性问题。
// 造成这个二义性的原因是,函数可以在不同的文件声明,但是只能定义一次。
// 全局变量不能在.h中定义,应该放在.cpp中,如果定义在.h中,会有二义性问题,因为变量只能生命一次,除非加extern关键字
// 类的成员函数同样有这样的问题,会存在二义性问题
例如:
head.h
class A
{
public:
void fun();
};
void A::fun() {};
test.h
#include "head.h"
void p();
test.cpp:
#include "test.h"
void p() {A::fun();}
#include "test.h"
main.cpp:
int main()
{
p();
return 0;
}
// 编译不过,存在二义性问题
// 解决方案有2钟,一种是声明定义分开。另一种是把fun()定义成内联函数
class A
{
public:
void fun();
};
inline void A::fun() {}
##函数指针
函数指针和变量的指针差不多意思,都是存储函数的内存地址,\
因为函数是在代码存储区,所以相同的函数地址相同
格式:
返回值(*指针名) (参数列表)
例如:
int add(int a, int b) {return a + b;}
int(*p) (int,int) = add;
或者
int(*p) (int,int) = &add;
p(100,200); // 300
// 普通函数的函数指针算是好理解,一定要记住的就是,参数列表是独立的,这个是判断是否是一个函数指针的关键
// 但是类的函数指针就不好理解了
格式
返回值 (类名::*指针名) (参数列表)
例如:
class A
{
public:
int add(int a, int b) {return a + b;}_
};
//这里必须加取址符
int(A::*p)(int, int) = &A::add;
A a;
(a.*add)(100,200); //必须定义一个类的变量才能调用成员函数指针,并且要加括号把指针名括起来
a.*add(100,2000); // 错误
(a.*)add(100,200); // 错误
// 静态函数
静态函数就是在函数前加 static 。有类的静态函数和普通静态函数。
类的静态函数只能访问静态成员变量,访问非静态的时候会报错
例如
class A
{
public:
static int a;
int b = 10;
static int c1() {return a;} // 正确
static int c2() {return b;} // 错误
};
int A::a = 1000;
普通的静态函数会把函数的使用范围限制在本文件中,需要注意的是,静态函数最好定义在.cpp中,要不然就失去了它本身的意义
例如:
test.cpp:
static int p() {return 100;}
main.cpp:
extern int p();
p(); // 拿不到的,肯定会报错
## 递归函数
递归函数就是自己调用自己。递归次数较少的情况下建议使用递归,较多的情况下,不建议,因为会导致堆栈溢出。
操作系统会给每个线程分配一定的函数堆栈,这个堆栈通常不大,大概1M左右。每次调用一次函数,都会压栈一次,
所以调用次数过多就会导致堆栈溢出,但是循环就没有这样的烦恼。
格式:
返回值 函数名 (参数列表)
{
// 结束标志
if (...) {
return xx;
}
函数名(参数列表);
}
例如
int fib(int num)
{
if (num == 1 || num == 2) {
return 1;
}
return fib(num - 1) + fib(num - 2);
}
std::cout << fib(100) << std::endl;
// 斐波那契递归,就是每次上楼梯只能上1个或者2个台阶,又几种方式可以走上去
// 如果台阶是1000000的话,这个函数就会异常了
// 这个时候就建议改成非递归方式
int fib(int num)
{
int f1 = 1;
int f2 = 1;
int f3 = 1;
int i = 0;
for(i = 3;i <= num;i++)
{
f3 = f2+f1;
f1 = f2;
f2 = f3;
}
return f3;
}
// 函数的东西虽然比较少,但是应用确实非常多 // 设计接口是函数最重要的功能之一,也是最麻烦的一部分 //
通常代码逻辑较长的部分都会被拿出来单独起一个函数 // 代码的层次不宜较深,一般超过3层就证明逻辑较乱,需要精炼代码或者提取函数 //
通过函数指针,可以做到成员函数回调的功能 // 后一篇会总结一下函数的用法,这里只要把函数的基本概念搞清楚就行