1.函数指针
(1)基本概念:程序运行期间,每个函数都会占用一段连续的内存空间。而函数名就是该函数所占内存区域的起始位置(也称入口地址)。我们可以将一个函数的入口地址赋值给一个指针变量,使该指针变量指向该函数。然后通过指针变量就可以调用这个函数。这种指向函数的指针被称为函数指针。
(2)函数指针定义形式:
类型名(*指针变量名)(参数类型1,参数类型2,....)
例如:int(*pf)(int,char); 表示pf是一个函数指针,它所指向的函数返回值应该是int,该函数应有两个参数,第一个是Int类型,第二个是char类型。
(3)使用方法:可以用一个原型匹配的函数的名字给一个函数指针赋值。要通过函数指针调用它所指向的函数,写法为:
函数指针名(实参表);
例:
#include<stdio.h>
#include<iostream>
#include<stdlib.h>
using namespace std;
void PrinMin(int a, int b) {
if (a < b)
cout << a << endl;
else
cout << b << endl;
}
int main() {
void(*pf)(int, int);
int x = 4, y = 5;
pf = PrinMin;
pf(x, y);
getchar();
return 0;
}
(4)函数指针和qsort库
void qsort(void*base,int nelem,snsigned int width,int(*pfCompare)(const void*,const void*));
可以对任意类型的数组进行排序
对数组排序,需要知道:
(1)数组起始地址
(2)数组元素个数
(3)每个元素的大小(由此可以算出每个元素的地址)
(4)元素谁在前谁在后的规则
base:待排序数组的起始地址,nelem:待排序数组元素个数,width:待排序数组的每个元素的大小(以字节为单位)
pfCompare:比较函数的地址。比较函数是自己编写的。
排序就是一个不断比较并交换位置的过程。qsort函数在执行期间,会通过pfCompare指针调用“比较函数”,调用时将要比较的两个元素的地址传给“比较函数”,然后根据“比较函数”返回值判断两个元素哪个更应该排在前面。
比较函数编写规则:int 比较函数名(const void*elem1,const void* elem2)
a.如果*elem1应该排在*elem2前面,则函数返回值是负整数。
b.如果*elem1和*elem2哪个排在前面都行,那么函数返回0
c.如果*elem1应该排在*elem2后面,则函数返回值是正整数
例程:将数字按个位数从小到大排序
#include<stdio.h>
#include<iostream>
#include<stdlib.h>
using namespace std;
int MyCompare(const void*elem1, const void*elem2)
{
unsigned int*p1;
unsigned int*p2;
p1 = (unsigned int*)elem1;
p2 = (unsigned int*)elem2;
return *p1 % 10 - *p2 % 10;
}
#define NUM 5
int main()
{
unsigned int a[NUM] = { 5,9,13,14,28 };
qsort(a, NUM, sizeof(unsigned int), MyCompare);
for (int i = 0; i < NUM; i++)
cout << a[i]<<endl;
system("pause");
getchar();
return 0;
}
2.命令行参数
int main(int argc,char* argv[])
{
........
}
argc:代表启动程序时,命令行参数的个数。C/C++语言规定,可执行程序程序本身的文件名,也算一个命令行参数,因此argc的值至少是1.
argv:指针数组,其中每一个元素都是一个char*类型的指针,该指针指向一个字符串,这个字符串里就存放着命令行参数。
例如,argv[0]指向的字符串就是第一个命令行参数,即可执行程序的文件名,argv[1]指向第二个命令行参数,argv[2]指向第三个命令行参数。
#include<stdio.h>
#include<iostream>
#include<stdlib.h>
using namespace std;
int main(int argc, char*argv[])
{
for (int i = 0; i < argc; i++)
cout << argv[i] << endl;
return 0;
}
将上面的程序编译成sample.exe,然后在控制台窗口敲:
sample para1 pare2 s.txt5 "hello world"
3.位运算
有时候需要对某个整数类型变量中的某一位进行操作,例如,判断某一位是否为1或只改变其中某一位而保持其他位都不变。C++语言提供了6种位运算符来进行“位运算”操作:按位与(&)、按位或(|)、按位异或(^)、取反(~)、左移(<<)、右移(>>)。
4.引用的概念和应用
(1)引用定义的方式如下:
类型名&引用名=同类型的变量名。
某个变量的引用和这个变量是一回事,相当于该变量的一个别名。注意:
1.定义引用时一定要将其初始化,否则编译不会通过。通常会用某个变量去初始化引用。
2.初始化后,它就一直引用该变量,不会再引用其他变量了。也可以用一个引用去初始化另一个引用,这样两个引用就引用同一个变量了。
3.引用只能引用变量,不能用常量初始化引用。
(2)引用的应用
在C语言中,编写两个整形变量值交换的函数;
void swap(int a,int b)
{ int tmp;
tmp=a;a=b;b=tmp
}
int n1,n2;
swap(n1,n2)
在C语言中,这样写变量的值不会被交换,因为函数里改变形参的值不会影响到实参。
C语言中正确的写法:
void swap(int* a,int* b)
{ int tmp;
tmp=*a;*a=*b;*b=tmp
}
int n1,n2;
swap(&n1,&n2)
C++里,可以应用引用:
void swap(int& a,int& b)
{ int tmp;
tmp=a;a=b;b=tmp
}
int n1,n2;
swap(n1,n2)
这样写变量的值会改变,因为int& a表示引用,引用后两个变量是等价的,a等价于n1,b等价于n2,改变a的值n1也会跟着变。
(3)常引用
定义引用时,可以在前面加“const”关键字,则该引用就成为“常引用”。
如: int n;
const int&r=n;
常引用和普通引用的区别在于不能通过常引用去修改其引用的内容。注意,不是常引用所引用的内容不能被修改,而是不能通过常引用修改其引用的内容。如:
int n=100;
const int&r=n;
r=200; //编译出错,不能通过常引用修改其引用的内容
n=300; //没问题,n的值变为300.
注意:const T&和T&是不同的类型。
(1)T&类型的引用或T类型和变量可以用来初始化const T&类型的引用。
(2)const& T类型的常变量和const T&类型的引用则不能用来初始化T&类型的引用,除非进行强制类型转换。
5.const关键字的用法
(1)定义常量。定义变量时如果在类型名前面加上“const”关键字,该变量就变为“常变量”
const int a=5.
常变量的值只能用初始化的方式给出,此后不能被修改。对常变量赋值会导致编译出错。
(2)定义指针。定义指针的时候,可以在前面加const关键字,则该指针就成为“常量指针”。
const T* P;
常量指针和普通指针的区别在于:不能通过常量指针去修改其指向的内容。注意,不是常量指针所指向的内容不能被修改,只是不能通过常量指针修改其指向的内容,可以用别的办法修改。
const int* p;
int n=100;
p=&n;
*p=200; //编译出错,不能通过常量指针修改其指向的内容。
n=300; //编译正确,n的值变为300
注意:const T*和T*是不同的类型。T*类型的指针可以赋值给const T*类型的指针,反过来不行,除非进行强制类型转换。
函数参数为常量指针时,可避免函数内部不小心改变函数参数指针所指地方的内容。
6.动态内存分配
【1】. C++里可以用new运算符实现动态内存分配,使得程序可以在运行期间,根据实际需要,要求操作系统临时分配给自己一片内存空间用于存放数据。此种内存分配是在程序运行中运行的,而不是在编译时就确定的,因此称为“动态内存分配”。new运算符的两种用法如下:
(1)P=new T;
其中,T是任意类型名,P是类型为T*的指针。动态分配出一片大小为sizeof(T)字节的内存空间,并且将该内存空间的起始地址赋值给P。例如:
int *P;
p=new int;
*p=5;
(2)P= new T[N]
其中,T是任意类型名;P是类型为T*的指针;N代表“元素个数”,可以是任何值为正整数的表达式,表达式中可以包含变量、函数调用等。这样的语句动态分配出N*sizeof(T)个字节的内存空间,这片空间的起始地址被赋值给P。例如:
int *pn;
int i=5;
pn=new int[i*20]
pn[0]=20;
pn[100]=30'
最后一行编译时没问题,但运行时会导致数组越界。
注意:new 运算符返回值的类型:new T ,new T[N] 返回值的类型都是T*。
【2】C++里用delete运算符释放动态分配的内存,new出来的动态内存空间一定要用delete运算符进行释放。用法:
(1) delete 指针; //该指针必须是指向动态分配的内存空间的,否则运行时很可能会出错。例如:
int *p=new int;
*p=5;
delete p;
delete p; //本句会导致程序出错 因为p指向的空间已经释放了,p不再是指向动态分配的内存空间的指针了。
(2) delete[ ]指针;
例如:
int *p=new int[20];
p[0]=1;
delete[ ]p;
同样要求,被释放的指针p必须是指向动态分配的内存空间的指针,否则会出错。
!注意:如果动态分配了一个数组,但是却用“delete指针”的方式释放,没有用[ ],则编译时没有问题,运行时也一般不会发生错误,但实际上会导致动态分配的数组没有被完全释放。
7.内联函数
函数调用是有时间开销的,如果函数内部本身就没有几条语句,执行的时间本来就很短,而且函数被反复执行很多次,相比之下,调用函数所产生的开销就比较大。
为了减少函数调用的开销,引入了内联函数机制,编译器处理对内联函数的调用语句时,是将整个函数的代码插入到调用语句处,而不会产生调用函数的语句。
内联函数的写法:在定义函数时,在返回值类型前面加上“inline”关键字。例如:
inline int Max(int a,int b)
{
if(a>b)
return a;
return b;
}
内联函数将整个函数体的代码插入调用语句处,就像整个函数体在调用处被重写了一遍一样。内联函数比使用普通函数会使最终可执行程序的体积增加。以时间换取空间或增加空间消耗来节省时间,也是计算机学科中常用的办法。
另外需要注意的是,调用内联函数的语句前面,必须已经出现内联函数的定义(即整个函数体),而不能只出现内联函数的声明。
8.函数重载
一个或多个函数名字相同,然而参数个数或参数类型不相同,这叫做函数的重载。C语言里没有函数重载机制,但C++有。
以下三个函数是重载的关系:
int Max(double f1,double f2){}
int Max(int n1,int n2){}
int Max(int n1,int n2,int n3){}
函数重载使函数命名变得简单。编译器根据调用函数中的实参个数和类型判断应该调用哪个函数。
注意:同名函数只有参数不同才算重载,如果两个同名函数参数表相同而返回值类型不同,不是重载而是重复定义,是补允许的。
9.函数的缺省参数
C++中,定义函数的时候可以让最右边的连续若干个参数有缺省值,那么调用函数的时候,若相应位置不写参数,参数就是缺省值。
如:
void func(int x1,int x2=10,int x3=8){
func(10); //等效于func(10,10,8)
func(10,8);//等效于func(10,8,8)
func(10,,10); //不行,只能最右边的连续若干个参数缺省。
缺省参数优点:
(1)编写函数调用语句时可以少输入参数,尤其在函数参数个数多时能省事儿。
(2)使程序的可扩充性变好,即程序需要增加新功能时改动尽可能少。如果某个写好的函数需要添加新的参数,而原先那些调用该函数的语句,未必需要使用新增的参数,为了避免那些对原先写好的调用语句的修改,就可以使用缺省参数。