C++函数指针、引用、位运算

函数指针

基本概念

程序运行期间,每个函数都会占用一段连续的内存空间。而函数名就是该函数所占内存区域的起始地址(也称“入口地址”)。 可以将函数的入口地址赋给一个指针变量,使该指针变量指向该函数。然后通过指针变量就可以调用这个函数。这种指向函数的指针变量称为“函数指针”。

定义形式

类型名 ( 指针变量名)(参数类型1, 参数类型2,…);*
如:int (*pf)(int ,char);

pf是一个函数指针,它所指向的函数,返回值类型应是int,该函数应有两个参数,第一个是int 类型,第二个是char类型。

使用方法

可以用一个原型匹配的函数的名字给一个函数指针赋值。 要通过函数指针调用它所指向的函数,8写法为:
**函数指针名(实参表); **

例:

#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);
int x = 4, y = 5;
pf = PrintMin;
pf(x,y);
return 0;
}

main函数的输出结果为“4”

函数指针和qsort库函数

函数指针的作用举例:C语言中的快排库函数

void qsort(void *base, int nelem, unsighed int width, 
int (*pfCompare)(const void *, const void *));

表示可以对任意类型的数组进行排序,其中参数(*pfCompare)(const void *, const void *)为函数指针,其指向的是一个返回类型为int的函数,这个函数有两个参数,类型都为const void *
对数组排序,需要知道

a[0] a[1] a[j] a[n-1]
*base 1 j n-1
  • 数组的起始地址
  • 数组长度
  • 每个元素的值
  • 元素排序规则

调用qsort函数时

  • 第一个参数 base:待排序数组的起始地址 ;
  • 第二个参数nelem:待排序数组的元素个数;
  • 第三个参数width:待排序数组的每个元素的大小(以字节为单位);
  • 最后一个参数pfCompare:函数指针,指向比较函数的地址

比较函数的作用, 就是告诉qsort,如何去判定两个元素,到底哪一个应该排在前面, 哪一个应该排在后面,所以pfCompare指向比较函数的地址,需要程序猿自己编写。它必须遵循这样一种格式:返回值一定是int,必须有两个类型为const void*的参数。

排序的过程,实际上就是在不断地比较数组元素,并且交换它们的位置。qsort函数在执行期间要比较两个数组元素时, 会通过pfCompare指针,调用 程序员自己编写的”比较函数“。调用”比较函数“时候,会把待比较的两个元素的地址,作为参数,传递给”比较函数“。 然后qsort根据”比较函数“的执行结果,即返回值, 判断待比较的两个元素的前后顺序。

当比较a[0]和a[1]两个元素时,pfCompare(e1, e2);比较函数编写规则为:

  • 如果 * elem1应该排在 * elem2前面,则函数返回值是负整数
  • 如果 * elem1* elem2哪个排在前面都行,那么函数返回0
  • 如果 * elem1应该排在* elem2后面,则函数返回值是正整数

实例

下面的程序,功能是调用qsort库函数,将一个unsigned int数组按照个位数从小到大进行排序。比如 8,23,15三个数,按个位数从小到大排序,应该是 23,15,8 。

#include <stdio.h> 
#include <stdlib.h>
int MyCompare(const void *elem1, const void *elem2){
unsigned int *p1, *p2;
p1 = (unsigned int *) elem1; //
p2 = (unsigned int *) elem2;
return (*p1 % 10)-(*p2 % 10);
}

#define NUM 5
int main(){
unsigned int an[NUM] = { 8,123,11,10,4 };
qsort(an, NUM, sizeof(unsigned int), MyCompare);
for( int i = 0;i < NUM; i ++ )
printf("%d ",an[i]);
return 0;
}

输出结果为:

10 11 123 4 8

比较时需要对elem1elem2进行强制类型转换。因为,elem1void * 指针,如果只写*elem1,编译器不知道elem1指向的元素到底是多少个字节,因此直接写elem1是非法的。所以需要对elem1进行强制类型转换, 把它强制转换成一个unsigned int *的指针,再赋值给p1p1unsigned int *`类型变量。


命令行参数

基本概念

将用户在CMD窗口输入可执行文件名的方式启动程序时,跟在可执行文件名后面的那些字符串,称为“命令行参数”。命令行参数可以有多个,以空格分隔。
在C++中:

int main(int argc, char * argv[]) { 
......
}
  • argc:启动程序时,命令行参数的个数。

C/C++语言规定,可执行程序程序本身的文件名,也算一个命令行参数,因此,argc的值至少是1。

  • argv:指针数组,其中每个元素都是一个char*类型的指针。指针指向存放命令行参数的字符串。

例如,argv[0]指向的字符串就是第一个命令行参数,即可执行程序 的文件名,argv[1]指向第二个命令行参数,argv[2]指向第三个命令 行参数…

例子

#include <stdio.h> 
int main(int argc, char * argv[]){
for(int i = 0; i < argc; i++){
print("%s\n", argv[i]);
}
return 0;
}

上面的例子循环遍历了argv数组,该数组共有argc个元素,将每一个元素指向的字符打印出来。每一个元素指向的字符串都是一个命令行参数。当把程序编译为sample.exe,然后在控制台输入:

sample para1 para2 s.txt 9 "hello world"

其打印结果为:

sample 
para1
para2
s.txt
9
hello world

要打印空格时,使用字符串作为参数即可。

位运算

基本概念

用于对整数类型(int,char, long 等)变量中的某一位(bit),或者若干位进行操作。如:

  • 判断某位是否为1
  • 只改变其中某位,保持其他不变

c++中有六种位运算符:

& | ^ ~ << >>
按位与 按位或 按位异或 按位取反 左移 右移
双目 双目 双目 单目 双目 双目

按位与“&”

将参与运算的两操作数各对应的二进制位进行与操作,只有对应的两个二进位均为1时,结果的对应二进制位才为1,否则为0。

例如:表达式“21 & 18 ”的计算结果是16

21 0000 0000 0000 0000 0000 0000 0001 0101
18 0000 0000 0000 0000 0000 0000 0001 0010
21&18 0000 0000 0000 0000 0000 0000 0001 0000

作用

  • 将某变量中的某些位清0且同时保留其他位不变;
  • 获取某变量中的某一位
  1. 需要将int型变量n的低8位全置成0,而其余位不变,则可以执行:
n = n & 0xffffff00;   
n &= 0xffffff00;

若n为short型,执行

n &= 0xff00;
  1. 判断一个int型变量n的第七位(从右往左,从0开始数 )是否为1
(n & 0x80) == 0x80;

按位或“|“

将参与运算的两操作数各对应的二进制位进行或操作,只有对应的两个二进位都为0时,结果的对应二进制位才是0,否则为1。

例如:表达式“21 | 18 ”的计算结果是23

21 0000 0000 0000 0000 0000 0000 0001 0101
18 0000 0000 0000 0000 0000 0000 0001 0010
21|18 0000 0000 0000 0000 0000 0000 0001 0111

作用

  • 将某变量中的某些位,置1,且保留其他位不变

如:需要将int型变量n的低8位全置成1,而其余位不变,则可以执行:

n |= 0xff; 
// 0xff: 1111 1111

按位异或“^”

将参与运算的两操作数各对应的二进制位进行异或操作,即只有对应的两个二进位不相同时,结果的对应二进制位才是1,否则为0。

例如:表达式“21 ^ 18 ”的计算结果是7

21 0000 0000 0000 0000 0000 0000 0001 0101
18 0000 0000 0000 0000 0000 0000 0001 0010
21^18 0000 0000 0000 0000 0000 0000 0000 0111

作用

  • 将某变量中的某些位取反,且保留其他位不变

如:需要将int型变量n的低8位取反,而其余位不变,则可以执行:

n ^= 0xff; 
// 0xff: 1111 1111

特点

  • a^b=c,那么就有 c^b = a以及c^a= b

此规律可以用来进行简单的加密和解密。

  • 能实现不创建临时变量,就能交换两个变量的值。
int a = 5, b = 7;
a = a ^ b;
b = b ^ a;
a = a ^ b;

按位非“~”

按位非运算符“~”是单目运算符。其功能是将操作数中的二进制位0变成1,1变成0。

例如:表达式“~21”的值是整型数 0xffffffea

21 0000 0000 0000 0000 0000 0000 0001 0101
~21 1111 1111 1111 1111 1111 1111 1110 1010

左移运算符“<<”

表达式: a << b 的值是:将a各二进位全部左移b位后得到的值。左移时,高位丢弃,低位补0。a 的值不因运算而改变。

例如: 9 << 4 结果为144

9 0000 0000 0000 0000 0000 0000 0001 1001
9<<4 0000 0000 0000 0000 0000 0001 1001 0000

实际上,m左移1位,就等于 $m \times 2 $ ,左移n位,就相当于 m×2nm \times 2^{n}。而左移操作比乘法操作快得多。

右移运算符“>>”

表达式:a >> b 的值是:将a各二进位全部右移b位后得到的值。右移时,移出最右边的位就被丢弃。 a 的值不因运算而改变。

对于有符号数,如long,int,short,char类型变量,在右移时,符号位(即最高位)将一起移动,并且大多数C/C++编译器规定,如果原符号位为1,则右移时高位就补充1,原符号位为0,则右移时高位就补充0。

例如:-25 >> 4 = -2 、-2 >> 4 = -1 、18 >> 4 = 1

-25 1111 1111 1111 1111 1111 1111 1110 0111
-25 >> 4 1111 1111 1111 1111 1111 1111 1111 1110

例子

#include <stdio.h>
int main(){
int n1 = 15;
short n2 = -15;
unsigned short n3 = 0xffe0;
char c = 15;
n1 = n1>>2;
n2 >>= 3;
n3 >>= 4;
c >>= 3;
print("n1 = %d, n2 = %d, n3 = %d, c = %x", n1, n2, n3, c);
}

输出结果为:

1=3, n2=fffffffe, n3=ffe, c=1
n1 0000 0000 0000 0000 0000 0000 0000 1111
n1>>2 0000 0000 0000 0000 0000 0000 0000 0011
n2 1111 1111 1111 0001
n2>>3 fffffffe 1111 1111 1111 1110
n3 1111 1111 1110 0000
n3>>4 ffe 1111 1111 1111 1110
c 0000 1111
c>>3 1 0000 0001

编程题目

题目:有两个int型的变量a和n(0 <= n <= 31),要求写一个表达式,使该表达式的值和a的第n位相同。

答案一:

(a >> n) & 1

分析:

a b31b_{31} b30b_{30} …… bnb_{n} …… b0b_{0}
a>>n b31b_{31} b31b_{31} …… bib_{i} …… bnb_{n}
(a>>n) & 1 0 0 …… 0 …… bnb_{n}

答案二:

(a  &  (1 << n )) >> n

引用

基本概念

下面的写法定义了一个引用,并将其初始化为引用某个变量。

类型名 & 引用名 = 某变量名;

int n = 4; 
int & r = n; // r引用了 n, r的类型是 int &

某个变量的引用,等价于这个变量,相当于该变量的一个别名 。

int n = 7; 
int & r = n;
r = 4;
cout << r; //输出 4
cout << n; //输出 4
n = 5;
cout << r; //输出5
  • 定义引用时一定要将其初始化成引用某个变量;
  • 初始化后,它就一直引用该变量,不会再引用别的变量了;
  • 引用只能引用变量,不能引用常量和表达式。

应用

交换两个整型变量值的函数

在C语言中:

void swap(int * a, int * b) { 
int tmp;
tmp = * a;
* a = * b;
* b = tmp;
}
void main(){
int n1, n2;
swap(& n1, & n2)
}
// n1,n2的值被交换

在C++中使用引用:

void swap(int & a, int & b) { 
int tmp;
tmp = a;
a = b;
b = tmp;
}
void main(){
int n1, n2;
swap(n1, n2);
}
// n1,n2的值被交换

引用作为函数的返回值

int n = 4; 
int & SetValue(){ return n; } // 函数的返回值为引用
int main(){
SetValue() = 40;
cout << n; // 输出40
return 0;
}

此时可以对一个函数的返回结果赋值。

常引用

定义引用时,前面加const关键字,即为“常引用”

int n; 
const int & r = n;
// r的类型为 const int &
  • 不能通过常引用修改其引用的内容:
int n = 100; 
const int & r = n;
r = 200; //编译错误
n = 300; // 正常运行
  • 常引用和非常引用的转换
    • const T &T &是不同的类型
    • T &类型的引用或T类型的变量可以用来初始化const T &类型的引用
    • const T类型的常变量和const T &类型的引用则不能用来初始化T &类型的引用,除非进行强制类型 转换。

const关键字和常量

const用法

  1. 定义常量
const intMAX_VAL= 23
const string SCHOOL_NAME= “Peking University” ;
  1. 定义常量指针
  • 不能通过常量指针修改其指向的内容。
int n, m;
const int *p = & n;
*p = 5; // 编译出错
n = 4; // 正确
p = & m; // 常量指针的指向可以发生改变
  • 不能把常量指针赋值给非常量指针,反过来可以
const int * p1;
int * p2;
p1 = p2; // 编译正确
p2 = p1; // 编译错误
p2 = (int *)p1; // 编译正确,const强制类型转换
  • 函数参数为常量指针时,可以避免函数内部不小心改变参数所指地方的内容
void MyPrinf(const char *p){
strcpy(p, "this"); // 编译错误
printf("%s", p); // 编译正确
}

strcpy函数第一个参数类型为char*。参数p的类型是const char*。 不能用const char*类型的指针给char*类型的指针赋值。所以p的类型跟char*不匹配的,编译出错。 因此它就可以避免在函数内部不小心写出了能改变参数指针所指向的地方的内容。

  1. 定义常引用

不能通过常引用修改其引用的变量

int n;
const int & r = n;
r = 5; // 编译错误
n = 4; // 编译正确

动态内存分配

new运算符

new分配变量

**P = new T;**
`T`是任意类型名,`P`是类型为`T * `的指针。 动态分配出一片大小为` sizeof(T)`字节的内存空间,并且将该内存空间的起始地址赋值给P。
int *pn;
pn = new int;
*pn = 5;

new分配数组

**p = new T[N];**
`T`是任意类型名,`P`是类型为`T * `的指针,`N`为要分配的数组元素的个数,可以是整型表达式。 动态分配一片大小为`sizeof(T)`子节的内存空间,并将该内存空间的起始地址赋值给`P`。
int *pn;
int i = 5;
pn = new int[i * 20];
pn[0] = 20;
pn[100] = 30; // 编译正确,运行时数组越界

delete释放动态分配的内存

delete释放变量

new动态分配的内存空间,一定要用 delete运算符进行释放 。delete 指针必须指向new出来的空间。

int *p = new int;
*p = 5;
delete p;
delete p; // 导致异常,一片空间不能被多次delete

delete释放数组

delete释放动态分配的数组,要加[]delete []指针必须指向new出来的数组。

int *p = new int[20];
p[0] = 1;
delete [ ] p;

位运算测试

  1. printf("%d\n", 34 & 27);的输出结果是:2(按位与)
34 0000 0000 0000 0000 0000 0000 0010 0010
27 0000 0000 0000 0000 0000 0000 0001 1011
34&27 0000 0000 0000 0000 0000 0000 0000 0010
  1. printf("%x\n",-12 >> 2 );的输出结果是:fffffffd(右移)
-12 1111 1111 1111 1111 1111 1111 1111 0100
-12>>2 1111 1111 1111 1111 1111 1111 1111 1101
f f f f f f f d
  1. printf("%d\n",26 | 14);的输出结果是:30(按位或)
27 0000 0000 0000 0000 0000 0000 0001 1010
14 0000 0000 0000 0000 0000 0000 0000 1110
26|14 0000 0000 0000 0000 0000 0000 0001 1110
  1. printf("%d\n",18 ^ 22);的输出结果是:4(按位异或)
18 0000 0000 0000 0000 0000 0000 0001 0010
22 0000 0000 0000 0000 0000 0000 0001 0110
18^22 0000 0000 0000 0000 0000 0000 0000 0100
  1. int * p = new int[12];动态分配了多少字节的空间?

48; int为4字节,new了一个int数组,长度为12

  1. 下面程序段:
int main(){
int a = 3, b = 5;
int & r = a;
r = b; // a = b = 5
b = 7;
cout << a << endl;
return 0;
}

其输出结果是:5

  1. 以下说法正确的是:
  • [x] 类的成员函数之间可以互相调用
  • [ ] 每个对象内部都有成员函数的实现代码
  • [ ] 一个类的私有成员函数内部不能访问本类的私有成员变量
  • [ ] 编写一个类时,至少要写一个成员函数
  1. 以下对类A的定义,哪个是正确的?
  • [ ] A
class A {
int v;
A *next;
void Func(){}
};
  • [ ] B 缺少“;”
class A {
private:
int v;
public:
void Func(){}
}
  • [X] C
class A{
int v;
public:
void Func();
};
A::void Func(){}
  • [ ] D
class A{
int v;
public:
A next;
void Func(){}
};
  1. 假设有以下类A:
class A{
public:
int func(int a) {return a * a;}
};

以下程序片段,哪个是不正确的?

  • [x] A a,b; if( a != b) a.func(5);
  • [ ] A a; a.func(5);
  • [ ] A * p = new A; p->func(5);
  • [ ] A a; A & r = a; r.func(5);
Author: NYY
Link: http://yoursite.com/2018/08/01/c++/c-1-1/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.