1. 函数指针
- 程序运行期间,每个函数都会占用一段连续的内存空间。而函数名就是该函数所占内存区域的起始地址(也称“入口地址”)。我们可以将函数的入口地址赋给一个指针变量,使该指针变量指向该函数。然后通过指针变量指向该函数。这种指向函数的指针变量称为“函数指针”。
- 定义形式:
类型名(*指针变量名)(参数类型1,参数类型2,...)
- 可以用一个原型匹配的函数的名字给一个函数指针赋值;
调用时: 函数指针名(实参表)#include <stdio.h> void PrintMin(int a, int b) { if (a < b) printf("%d", a); else printf("%d", b); } int main() { void(*pf)(int, int); , y = ; pf = PrintMin; pf(x, y); ; }
- 指针函数的作用:以C语言快速排序库函数为例
void qsort(void* base, int nelem, unsigned int width, int(*pfCompare)(const void*, const void*))
该函数可以对任意类型数组进行排序。
而对一个数组进行排序,需要知道:
1)数组起始地址
2)数组元素的个数
3)每个元素的大小
4)元素谁在前谁在后的规则
pfCompare:函数指针,它指向一个“比较函数”。该比较函数应为以下形式:int 函数名(const void*elem1, const void* elem2);
比较函数是程序员自己编写的:
实例:下面的程序,功能是调用qsort库函数,将一个unsigned int数组按个位数从小到大进行排序。比如8,23,15三个数,按个位数从小到大排序,就应该是23,15,8#include<iostream> using namespace std; int MyCompare(const void* elem1, const void* elem2) { unsigned int *p1, *p2; p1 = (unsigned int *)elem1; p2 = (unsigned int *)elem2; ) - (*p2 % ); } #define NUM 5 int main() { unsigned , , , , }; qsort(an, NUM, sizeof(unsigned int), MyCompare); ; i < NUM; i++) { cout << an[i] << " "; } ; }
2. 命令行参数
int main(int argc, char* argv[]) { ...... }
argc: 代表启动程序时,命令行参数的个数。C/C++语言规定,可执行程序程序本身的文件名,也算一个命令行参数,因此,argc的值至少是1。
argv: 指针数组,其中的每个元素都是一个char* 类型的指针,该指针指向一个字符串,这个字符串里就存放着命令行参数。
例如,argv[0]指向的字符串就是第一个命令行参数,即可执行程序的文件名,argv[1]指向第二个命令行参数,argv[2]指向第三个命令行参数……。
3. 位运算
位运算:用于对整数类型(int,char, long 等)变量中的某一位(bit),或者若干位进行操作。比如:
1) 判断某一位是否为1
2) 只改变其中某一位,而保持其他位都不变
C/C++语言提供了六种位运算符来进行位运算操作:
& 按位与(双目) | 按位或(双目) ^ 按位异或(双目) ~ 按位取反(单目) << 左移(双目) >> 右移(双目)
- 按位与“&”
通常用来将某变量中的某些位清0且同时保留其他位不变。也可以用来获取某变量中的某一位。
例如,如果需要将int型变量n的低8位全置成0,而其余位不变,则可以执行:n = n & 0xffffff00;
如何判断一个int型变量n的第7位(从右往左,从0开始数)是否是1 ?
只需看表达式 “n & 0x80”的值是否等于0x80即可。 - 按位或“|”
按位或运算通常用来将某变量中的某些位置1且保留其他位不变。
例如,如果需要将int型变量n的低8位全置成1,而其余位不变,则可以执行:n |= 0xff;
- 按位异或“^”
只有对应的两个二进位不相同时,结果的对应二进制位才是1,否则为0。
按位异或运算通常用来将某变量中的某些位取反,且保留其他位不变。
例如,如果需要将int型变量n的低8位取反,而其余位不变,则可以执行:n ^= 0xff;
异或运算的特点是:
如果 a^b = c, 那么就有 c^b = a 以及 c^a = b. - 按位非“~”
按位非运算符“~”是单目运算符。其功能是将操作数中的二进制位0变成1,1变成0。
- 左移运算符“<<”
a << b
将a各二进位全部左移b位后得到的值。左移时,高位丢弃,低位补0。a 的值不因运算而改变。
实际上,左移1位,就等于是乘以2,左移n位,就等于是乘以$2^n$。而左移操作比乘法操作快得多。
- 右移运算符“>>”
a >> b
将a各二进位全部右移b位后得到的值。右移时,移出最右边的位就被丢弃。 a 的值不因运算而改变。
实际上,右移n位,就相当于左操作数除以$2^n$,并且将结果往小里取整。#include "stdio.h" using namespace std; int main() { ; ; unsigned short n3 = 0xffe0; ; n1 = n1 >> ; n2 >>= ; n3 >>= ; c >>= ; printf("n1=%d, n2=%x, n3=%x, c=%x", n1, n2, n3, c); ; }// 输出结果是:n1=3,n2=fffffffe,n3=ffe,c=1
思考题:有两个int型的变量a和n(0 <= n <= 31),要求写一个表达式,使该表达式的值和a的第n位相同。
(a >> n) &
4. 引用
下面的写法定义了一个引用,并将其初始化为引用某个变量。
类型名 & 引用名 = 某变量名 ; int &r = n;// r引用了变量n,r的类型是 int &
某个变量的引用,等价于这个变量,相当于该变量的一个别名。
注意:
1) 定义引用时一定要将其初始化成引用某个变量
2)初始化后,它就一直引用该变量,不会再引用其他变量了
3)引用只能引用变量,不能引用常量或表达式
例子:
// 交换两个数 void swap( int &a, int &b) { int tmp; tmp = a; a = b; b = tmp; } int n1, n2; swap(n1,n2) ; // n1,n2的值被交换
// 引用作为返回值 ; int & SetValue() {return n; } int main() { SetValue() = ; cout << n; ; } //输出:40
常引用:定义引用时,前面加const关键字,即为“常引用”
; const int &r = n;
r的类型是 const int &, 不能通过常引用去修改其引用的内容
; const int & r = n; r = ; //编译错 n = ; // 没问题
5. 常量
- 定义常量
; const double Pi = 3.14; const char *SCHOOL_NAME = "Pekong University";
- 定义常量指针
// 不可以通过常量指针修改其指向的内容 int m, n; const int *p = &n; *p = ; // 编译出错 n = ; // ok p = &m; // ok, 常量指针的指向可以变化
// 不能把常量指针赋值给非常量指针,反过来可以 const int* p1; int* p2; p1 = p2; //ok p2 = p1; //error p2 = (int* ) p1; //ok,强制类型转换
// 函数参数为常量指针时,可避免函数内部不小心改变参数指针所指地方的内容 void MyPrintf( const char * p ) { strcpy( p,"this"); //编译出错 printf("%s",p); //ok }
- 定义常引用
// 不能通过常引用修改其引用的变量 intn; const int& r = n; r = ; //error n = ; //ok
- 常量指针和指针常量
* (指针)和 const(常量) 谁在前先读谁 ;*象征着地址,const象征着内容;谁在前面谁就不允许改变。; ; ; int const *p1 = &b;//const 在前,定义为常量指针 int *const p2 = &c;//*在前,定义为指针常量
常量指针p1:指向的地址可以变,但内容不可以通过p1重新赋值,内容的改变只能通过修改地址指向后变换。
p1 = &a是正确的,但 *p1 = a是错误的。
指针常量p2:指向的地址不可以重新赋值,但内容可以改变,必须初始化,地址跟随一生。
p2= &a是错误的,而*p2 = a 是正确的。
6. 动态内存分配
- 分配一个变量
T *P = new T;
T是任意类型名,P是类型为T *的指针。
动态分配出一片大小为sizeof(T)字节的内存空间,并且将该内存空间的起始地址赋值给P。比如:int* pn; pn= new int; * pn= ;
- 分配一个数组
T* P = new T[N];
T是任意类型名,P是类型为T *的指针,N 是要分配的数组元素的个数,可以是整型表达式。
动态分配出一片大小为sizeof(T*N)字节的内存空间,并且将该内存空间的起始地址赋值给P。比如:int* pn; inti= ; pn= ]; pn[] = ; pn[] = ; //编译没问题。运行时导致数组越界
- 用delete运算符释放动态分配的内存
用“new”动态分配的内存空间,一定要用“delete”运算符进行释放。int* p = new int; * p = ; delete p; delete p;//导致异常,一片空间不能被delete多次
用“delete”释放动态分配的数组,要加“[]”
]; p[] = ; delete [ ] p;
7. 内联函数inline
函数调用是有时间开销的。如果函数本身只有几条语句,执行非常快,而且函数被反复执行很多次,相比之下调用函数所产生的这个开销就会显得比较大。
为了减少函数调用的开销,引入了内联函数机制。编译器处理对内联函数的调用语句时,是将整个函数的代码插入到调用语句处,而不会产生调用函数的语句。
inline intMax(inta,intb) { if( a > b) return a; return b; }
在声明时就实现的函数自动成为内联函数。
内联函数的实现写在了源文件中并且在这个源文件以外的文本文件中调用了此内联函数,那么编译可以通过,但是链接器会报“无法解析的外部符号”的错误。
8. 函数重载
一个或多个函数,名字相同,然而参数个数或参数类型不相同,这叫做函数的重载。
9. 函数的缺省参数
C++中,定义函数的时候可以让最右边的连续若干个参数有缺省值,那么调用函数的时候,若相应位置不写参数,参数就是缺省值。
void func( intx1, intx2 = 2, intx3 = 3) { } func( ) ; //等效于func(10,2,3) func(,) ; //等效于func(10,8,3) func(, , ) ; //不行,只能最右边的连续若干个参数缺省
函数参数可缺省的目的在于提高程序的可扩充性。
即如果某个写好的函数要添加新的参数,而原先那些调用该函数的语句,未必需要使用新增的参数,那么为了避免对原先那些函数调用语句的修改,就可以使用缺省参数。