C++学习笔记之三(函数&指针、调用、动态内存、模板)

C++

1、函数&指针

1.1、指针函数

指针函数是函数,其中指针是定语,函数是名词。意为该函数的返回值为指针

int *Function_1(void);

int temp = 9;
int *p = &temp;

int *Function_1(void)     //Function函数返回一个int *的指针
{
	cout<<"I am a function"<<endl;
	return p;   //注意函数的作用域问题,别返回局部指针 
}

1.2、函数指针

函数指针是一个指针,其中函数是定语,指针是名词。意为该指针指向函数,即指针内存放的函数的地址

int (*point)(float);           //int指的是函数的返回值类型,float指的是函数的形参类型

举个例子:

int t_function(float);                  //define a functon

int t_function(float num)
{
	cout<<"the input parameter is: "<<num<<endl;
	return int(num);                   //forcing a floating type to an integer
}

int main()
{
	int (*p)(float);                   //define a function pointor
	p = t_function;                 //函数名经过运算之后以地址的形式出现
	cout<<(*p)(9.9)<<endl;
	
	return 0;	
} 

1.2.1、函数指针作为函数的传入参数

函数指针的本质就是指针,所以函数指针作为传入参数的用法和指针作为传入参数的用法是相同。和一般的数据类型一样,作为形参时,都需要给出完整的定义。而在调用的时候,传入其地址即可。

int add(int,int);
int sub(int,int);
int calc(int (*p)(int,int),int,int);         //function pointor as the input parameter

int add(int num1,int num2)
{
	return num1+num2;
}
int sub(int num1,int num2)
{
	return num1-num2;
}
int calc(int (*p)(int,int),int num1,int num2)
{
	return (*p)(num1, num2);
}

int main()
{
	int (*p)(int,int);             //define a function pointor
	
	cout<<calc(add,5,3)<<endl;     //input the function's address and the num1,num2.
	cout<<calc(sub,5,3)<<endl;
	
	return 0;	
} 

1.2.2、函数指针作为函数的返回值

函数指针的声明可以这样写:

int (*function(float))(int,int);

这里充分利用了C语言的运算优先级来分割语句,即括号的优先级大于其他运算符。所以以上的语句可以分为两个部分看,第一个部分 f u n c t i o n ( f l o a t ) function(float) function(float),第二个部分 i n t ( ∗ p ) ( i n t , i n t ) int (*p)(int,int) int(∗p)(int,int)。其中 p p p就是第一部分的 f u n c t i o n ( f l o a t ) function(float) function(float),相当于数学上的换元。

从第一部 f u n c t i o n ( f l o a t ) function(float) function(float)分可以看出,该语句所声明的是一个函数,且该函数有一个 f l o a t float float的传入参数。

从第二部分 i n t ( ∗ p ) ( i n t , i n t ) int (*p)(int,int) int(∗p)(int,int)可以看出,该函数的返回值是一个函数指针,且该函数指针的形式是 i n t ( ∗ p ) ( i n t , i n t ) int (*p)(int,int) int(∗p)(int,int),即该函数指针指向的是一个返回值为 i n t int int类型且两个传入参数都为 i n t int int类型的函数。

最后,在定义该函数的时候当然得把所以形参都加上:

int (*function(float num1))(int num2, int num3)
{
	cout<<"hello, i am a function and my return value is a function poineter"<<endl;
	//返回一个函数指针所指向的函数的地址
	return "a function's address to which the pointer points";   
}

2、传递

2.1、值传递

值传递是最简单的一种形式,但囿于太不灵活了。众所周知,每一个函数都有自己的栈区,而值传递就是栈区相互联系的其中一个桥梁。

void Square(int);

void Square(int temp)
{
	int sum = temp*temp;
	cout<<"the square of this number is:"<<sum<<endl;
}

int main()
{
	int num;
	cout<<"please enter the number:";
	cin>>num;
	Square(num);
	
	return 0;
}

该函数的功能是计算传入参数的平方,但无论在 S q u a r e Square Square里面怎么修改 t e m p temp temp,原来的数值 n u m num num还是没有任何改变。究其原因是传值的时候, S q u a r e Square Square会把 n u m num num的值复制下来,放在自己的栈区。

2.2、址传递

void Modify(int*);

void Modify(int *temp)
{
	*temp = 9; 
}

int main()
{
	int num;
	cout<<"please enter the number:";
	cin>>num;
	Modify(&num);
	
	cout<<"The revised number is:"<<num<<endl;
	
	return 0;
}

该函数的功能是把原来的值修改为9,传地址有两个好处,其中一个就是如上的代码所示,可以通过解引用地址来修改原来的值。另一个好处就是,可以传输数组、结构体、类等等东西,因为归根结底,拿到首地址都是可以访问这些结构的。

但传地址的本质还是 M o d i f y Modify Modify把拿到的地址复制一份到自己的栈区,然后再通过解引用自己栈区中的这个地址,去访问或修改原始数据。

2.3、引用传递

通俗的讲,引用传递是给一个变量取一个别名,且通过该别名索引到的内存空间依旧是原来的内存空间。

但从本质上来看,变量名在计算机底层本身就是不存在的,只是给编译器看的而已,所以何来再取一个别名这样的操作。所以在我个人来看,引用还是和指针一样,传递的是一个地址,只不过形式不一样。且引用传递的时候,所调用函数不不复制它到栈区中。这样既节省了内存,也简化了形式。

void Modify(int&);

void Modify(int &temp)
{
	temp = 9; 
}

int main()
{
	int num;
	cout<<"please enter the number:";
	cin>>num;
	Modify(num);
	
	cout<<"The revised number is:"<<num<<endl;
	
	return 0;
}

3、多态

多态性是指用一个函数名字定义若干个函数实现不同的函数,达到在不同情况调用同一函数名执行不同操作的功能。而为了更好的利用多态性,虚方法和抽象方法应运而生。

3.1、虚方法和抽象方法

虚方法 是指父类定义一个虚方法,子类可以决定是否要覆盖。如果子类覆盖了,在"父类的指针,子类的对象 "这种情况下,以子类的覆盖为准(如 f u n c t i o n 2 function2 function2),否则以父类的函数为主(如 f u n c t i o n 1 function1 function1)。
抽象方法 即在形式上和虚方法很像,在其末端加一个 0 0 0而已。但不同的是,抽象方法在父类中是只定义不声明,等到了子类再定义,那么当函数执行的时候,当然有且只有以子类的函数实现为主。

class Superclass
{
public:
	void function_1(void){
		cout<<"the father's function 1"<<endl;
	}
	virtual void function_2(void){
		cout<<"the father's function 2"<<endl;
	}
	virtual void function_3(void)=0;                

};

class Subclass:public Superclass
{
public:
	void function_1(void){
		cout<<"the son's function 1"<<endl;
	}
	void function_2(void){
		cout<<"the son's function 2"<<endl;
	}
	void function_3(void){
		cout<<"the son's function 3"<<endl;
	}

};

int main()
{
	Superclass *p = new Subclass;
	
	p->function_1();		//print "the father's function 1"
	p->function_2();		//print "the son's function 2"		
	p->function_3();		//print "the son's function 3"
	
	return 0;
}

4、动态内存

内存按申请阶段分为静态内存和动态内存两种。静态内存是指程序在编译阶段申请内存空间,一般大小固定且不能再修改。比如 i n t int int t e m p temp temp = = = 3 ; 3; 3;这种基本数据类型,都是在编译阶段就给分配好的内存。

动态内存则指在运行阶段才向堆申请内存空间,一般用 n e w new new和 m a l l o c malloc malloc申请的都属于动态内存。

int main()
{
	int count;
	
	cout<<"please assign a value to count:";
	cin>>count;
	
	int *p = new int[count];
	
	
	for(int i=0;i<count;i++)
	{
		*(p+i) = i;
		cout<<*(p+i)<<endl;
	}
	
	delete []p;              //用这种方式可以释放掉动态数组
	
	return 0;
}

5、模板

5.1、函数模板

一般用 t e m p l a t e < t y p e n a m e template<typename template<typename T > T> T>或者 t e m p l a t e < c l a s s template<class template<class T > T> T>来声明一个函数模板。使用模板的好处在于 T T T可以按照需求变成整型 ( i n t ) (int) (int)、浮点型 ( f l o a t ) (float) (float)、地址 ( a d d r e s s ) (address) (address)等等。这避免了传不同的参数就要写不同的重载函数的麻烦。

template <typename T>   	
void t_function(T);	

template <typename T>   	
void t_function(T temp)	
{
	cout<<"the size of temp is:"<<sizeof(temp)<<endl;
}


int main()
{
	int a = 1;
	double b = 1.0;
	
	t_function(a);			//print:"the size of temp is 4"
	t_function(b);			//print:"the size of temp is 8"

	return 0;
}

但使用模板也有一个比较繁琐的点就是每次使用前,都得声明一次。因为我们需要它来告诉编译器如何定义这个函数。

5.2、类模板

类模板其实和函数模板差不多了,只不过是在类的定义前加一个模板罢了。需要注意的部分我也打在注释上了。

template <typename T>   	
class Car
{
public:
	Car(T number){
		cout<<"the car license number is: "<<number<<endl;	
	}
	~Car(void)
	{
		cout<<"over"<<endl;
	}
	void run(T);
};

template <typename T>                          //you have to declare the template every times you use it.
void Car<T>::run(T kilometer)               //Car后面还要加个<T>,代表这个类方法定义的时候用了函数模板
{
	cout<<"the car ran "<<kilometer<<"km"<<'\n';
}

int main()
{
	class Car <int>mycar(888);                 //在实例化的时候必须定义类模板是什么类型
	mycar.run(39);
	
	return 0;
}

当然,我们也可以同时使用多个函数模板,像这样。

template <typename T,typename U>   	
class Car
{
public:
	Car(T number){
		cout<<"the car license number is: "<<number<<endl;	
	}
	~Car(void)
	{
		cout<<"over"<<endl;
	}
	void run(U);
};

template <typename T,typename U>        //即便只要到了模板U,还是得吧T,U都带上
void Car<T,U>::run(U kilometer)
{
	cout<<"the car ran "<<kilometer<<"km"<<'\n';
}

int main()
{
	class Car <int,float>mycar(888);                  //实例化的时候为定义了两种模板
	mycar.run(39.5);
	
	return 0;
}

5.3、内联函数

内联函数是 C + + C++ C++为了提高程序运行速度所做出的一项改进,在函数声明和定义之前加一个 i n l i n e inline inline关键字即可以使函数变成内联函数。

程序运行过程中调用常规函数,跳到对应的栈区执行该函数,执行之后再跳回来,而这会减缓程序运行的速度。内联函数的出现就是为了解决这一问题,内联函数是程序在编译过程中,将函数代码写入到调用它的函数的栈区。从而避免从栈区跳来跳去。譬如 A f u n c t i o n Afunction Afunction中调用到了 B f u n c t i o n Bfunction Bfunction,如果 B f u n c t i o n Bfunction Bfunction是一个常规函数,那么需要从 A f u n c t i o n Afunction Afunction的栈区跳到 B f u n c t i o n Bfunction Bfunction的栈区去执行 B u n c t i o n Bunction Bunction函数再跳回来。但如果 B f u n c t i o n Bfunction Bfunction是一个内联函数,则可以在编译的时候直接把 B f u n c t i o n Bfunction Bfunction的代码写入 A f u n c t i o n Afunction Afunction的栈区,那么当 A f u n c t i o n Afunction Afunction调用到 B f u n c t i o n Bfunction Bfunction的时候,就不需要在栈区之间跳来跳去了。

但这样做有一个缺点就是,如果在这段代码中有 A f u n c t i o n Afunction Afunction, C f u n c t i o n Cfunction Cfunction, D f u n c t i o n Dfunction Dfunction都调用到了 B f u n c t i o n Bfunction Bfunction,那这就会占用更多的内存。所以是否使用内联函数终究是效率和资源之间的平衡。

#include<iostream>
using namespace std;

inline void Run(void);

inline void Run(void)
{
	cout<<"running!"<<endl;
}


int main()
{
	Run();
	return 0;
}
相关推荐
Tang Paofan40 分钟前
C++ constexpr
开发语言·c++
AICodeThunder1 小时前
C++知识点总结(57):STL综合
java·c++·算法
薔薇十字1 小时前
【代码随想录day32】【C++复健】509. 斐波那契数;70. 爬楼梯;746. 使用最小花费爬楼梯
开发语言·c++·算法
qq_1873526341 小时前
c++基础36时间复杂度
c++·c++基础36时间复杂度
理论最高的吻2 小时前
222. 完全二叉树的节点个数【 力扣(LeetCode) 】
c++·算法·leetcode·职场和发展·二叉树
ducking__2 小时前
设计模式练习(二) 简单工厂模式
c++·设计模式·简单工厂模式
捕鲸叉2 小时前
C++创建型设计模式综合示例
开发语言·c++·设计模式
捕鲸叉2 小时前
C++创建型设计模式体现出的面向对象设计原则
开发语言·c++·设计模式
yangmc042 小时前
区间和 离散化 模板题
c语言·数据结构·c++·算法·矩阵·objective-c
No0d1es2 小时前
2024年9月青少年软件编程(C语言/C++)等级考试试卷(七级)
c语言·开发语言·c++·算法·青少年编程·电子学会·七级