文档章节

C和指针---第十三章:高级指针话题

fzyz_sb
 fzyz_sb
发布于 2013/09/20 14:31
字数 2414
阅读 125
收藏 1
点赞 0
评论 0

13.1 进一步探讨指向指针的指针

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
	int i = 5;
	int *pi = &i;
	int **ppi;

	ppi = &pi;
	//如果没有ppi = &pi;则这是错误的赋值方法,因为ppi还未被初始化。如果ppi被初始化,则*ppi = pi毫无作用。
//	*ppi = pi;			


	printf("%d\n", **ppi );

	return 0;
}
13.2 高级声明
int f;    //一个整型变量
int *f;   //一个指向整型的指针
int *f();
f是一个函数,返回值类型是一个指向整型的指针。

备注:用于声明变量的表达式和普通的表达式在求值时所使用的规则相同

int (*f)();
第一对括号的作用:迫使间接访问在函数调用之前进行,使f成为一个函数指针(即我们必须传递函数的地址进去,进行*f后成为一个函数,然后调用这个函数),它所指向的函数返回一个整型值。

则下面这个表达式容易理解:

int *(*f)();
函数指针,返回一个指向整型的指针。
int *f[];
[]的优先级高于*,所以首先f是个数组,数组内部的元素为*,而类型为int,所以数组的元素类型是指向整型的指针。

以下两个表达式均非法:

1.

int f()[];

f是个函数,它返回一个整型数组---但是是非法的,函数只能返回标量值,不能返回数组。

2. 

int f[]();

f是个数组,返回值为整型的函数---但是是非法的,因为数组元素必须具有相同的长度,但不同的函数显然可能具有不同的长度。

所以下面的声明是合法的:

int (*f[])();
f是一个数组,数组元素的类型是函数指针,它所指向的函数的返回值是一个整型值。
int *(*f[])();
创建一个指针数组,指针所指向的类型是返回值为整型指针的函数。

13.3 函数指针

简单声明一个函数指针并不意味着它马上就可以使用。和其他指针一样,对函数执行间接访问之前必须把它初始化为指向某个函数。

int f( int );
int ( *pf )( int ) = &f;
初始化表达式中的&操作符是可选的,因为函数名被使用时总是由编译器把它转换为函数指针。
int ans;
ans = f( 25 );
ans = ( *pf )( 25 );
ans = pf( 25 );
第一条语句简单的使用名字调用函数f。但首先f被转换为函数指针,该指针指定函数在内存中的位置。然后,函数调用操作符调用该函数,执行开始于这个地址的代码。

第二条语句对pf执行间接访问操作,它把函数指针转换为一个函数名。但接着又被转换为函数指针来执行函数。

第三条语句直接调用函数指针来执行函数。

13.3.1 回调函数

这里有个简单的函数,它用于在一个单链表中查找一个值。它的参数是一个指向链表第一个节点的指针以及那个需要查找的值:

Node *search_list( Node *node, int const value )
{
	while ( NULL != node ){
		if ( node->value == value ){
			break;
		}
		node = node->link;
	}

	return node;
}
这个函数的一大缺点是:只能查找整型值,所以我们可以通过回调函数的技巧来达到查询任何类型的值。

备注:参数类型声明为void*, 表示“一个指向未知类型的指针”

#include <stdio.h>

node *search_list( node *node, void const *value, int ( *compare )( void const *, void const * ) )
{
	while ( NULL != node ){
		if ( compare( &node->value, value ) == 0 ){
			break;
		}
		node = node->link;
	}

	return node;
}
所以,当我们想比较整型值的时候,我们可以这样编写比较函数:
int compare_int( void const *a, void const *b )
{
	if ( *( int * )a == *( int * )b ){
		return 0;
	}

	return 1;
}
然后,我们可以这样调用:
desired_node = search_list( root, &desired_value, compare_int );
13.3.2 转移表

假设有个计算器程序:

switch ( oper ){
	case ADD:
		result = add( op1, op2 );
		break;
	case SUB:
		result = sub( op1, op2 );
		break;
	case MUL:
		result = mul( op1, op2 );
		break;
	case DIV:
		result = div( op1, op2 );
		break;
......
}
如果操作符很多,则switch则很长很长。

我们可以用函数指针来转换:

double add( double, double );
double sub( double, double );
double mul( double, double );
double div( double, double );
........
double ( *oper_func[] )( double, double ) = {
	add, sub, mul, div,....
};

result = oper_func[ oper ]( op1, op2 );
请参考《代码大全2》.

13.4 命令行参数

假设我们输入:

prog -a -b -c name1 name2 name3
则下面代码执行这个命令:
#include <stdio.h>
#define TRUE 1

void process_standard_input( void );
void process_file( char *file_name );

int option_a, option_b;

void main( int argc, char **argv )
{
	while ( *++argv != NULL && **argv == '-' ){
		switch( *++*argv ){
			case 'a':
				option_a = TRUE;
				break;
			case 'b':
				option_b = TRUE;
				break;
		}
	}

	if ( NULL == *argv ){
		process_standard_input();
	}
	else{
		do{
			process_file( *argv );
		}while( *++argv != NULL );
	}
}
但如果命令行是:
prog -abc name1 name2 name3
则我们只需要修改while循环即可:
while ( ( opt = *++*argv ) != '\0' ){
	switch( opt ){
		case 'a':
			option_a = TRUE;
			break;
.....
	}
}

13.5 字符串常量

#include <stdio.h>

int main(void)
{
	printf("%c\n", *( "xyz" + 1 ) );

	return 0;
}
程序输出字符:‘y’

当一个字符串常量出现于表达式中时,它的值是个指针常量。编译器把这些指定字符的一份拷贝存储在内存的某个位置,并存储一个指向第一个字符的指针。但是,当数组名用于表达式时,它们的值也是指针常量。我们可以对它们进行下标引用,间接访问以及指针运算。

一个神秘的函数:

#include <stdio.h>

void mystery( int n )
{
	n /= 10;
	printf("%s\n", "**********" + 10 - n );
}

int main(void)
{
	int i = 0;
	while ( i++ <= 100 ){
		mystery( i );
	}

	return 0;
}
如果参数为0,则打印0个星号,如果为100,则打印10个星号。但是当n等于0的时候,星号就10个,不是存在越界情况吗?实际上我们打印的是字符'\0',故个人推荐,在星号后面增加一个(‘ ’)空格。

打印十六进制也可以简化成下面的代码:

putchar( "0123456789ABCDEF"[ value % 16 ] );
知道python的人应该对这个不陌生,其实python的源码是用C写的时候,你就懂得了。

习题:

1.

#include <stdio.h>
#include <ctype.h>


int main(void)
{
	int (*func[])(int value) = {
		iscntrl, isspace, isdigit, islower, isupper, ispunct, isprint
	};
	int	n_count[8] = {0, 0, 0, 0, 0, 0, 0, 0};
	char	ch;
	int	i = 0;
	while ( ( ch = getchar() ) != EOF ){
		n_count[7]++;
		for ( i = 0; i < 7; i++ ){
			if ( func[i]( ch ) ){
				n_count[i]++;
				continue;
			}
		}
	}
	printf("cntrl:%.2f%%\n", n_count[0] * 100.0 / n_count[7] );
	printf("space:%.2f%%\n", n_count[1] * 100.0 / n_count[7] );
	printf("digit:%.2f%%\n", n_count[2] * 100.0 / n_count[7] );
	printf("lower:%.2f%%\n", n_count[3] * 100.0 / n_count[7] );
	printf("upper:%.2f%%\n", n_count[4] * 100.0 / n_count[7] );
	printf("punct:%.2f%%\n", n_count[5] * 100.0 / n_count[7] );
	printf("no print:%.2f%%\n", ( n_count[7] - n_count[6] ) * 100.0 / n_count[7] );

	return 0;
}

程序输出:

2.

#include <stdio.h>
#include <stdlib.h>

typedef struct Node{
	struct Node *next;
	char		data;
} Node;

void func( Node *node, void (*commonFunc)( Node* node ) )
{
	Node *current = node;
	while ( current ){
		commonFunc( current );
		current = current->next;
	}
}

void print_list( Node *node )
{
	printf("%c-->", node->data );
}

int main(void)
{
	Node d = { 0, 'd' };
	Node c = { &d, 'c' };
	Node b = { &c, 'b' };
	Node a = { &b, 'a' };

	func( &a, print_list );
	printf("NULL\n");

	return 0;
}

程序输出:

4.以下程序有问题:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int compare_int( void const *a, void const *b )
{
	return *( int * )a >= *( int * )b ? 1 : 0;
}

int compare_char( void const *a, void const *b )
{
	return *( char * )a >= *( char * )b ? 1 : 0;
}

int compare_string( void const *a, void const *b )
{
	return *( char ** )a >= *( char ** )b ? 1 : 0;
}

void my_sort( void *arr[], int len, int elementLen, int ( *compare )( void const *, void const * ) )
{
	int i = 0;
	int j = 0;
	
	for ( i = 0; i < len - 1; i++ ){
		for ( j = i + 1; j < len; j++ ){
			if ( compare( ( arr + i ), ( arr + j ) ) ){
				if ( 1 == elementLen ){
					char temp = arr[i];
					arr[i] = arr[j];
					arr[j] = temp;
				}
				else if ( 4 == elementLen ){
					int temp = arr[i];
					arr[i] = arr[j];
					arr[j] = temp;
				}else{
					char *temp = arr[i];
					arr[i] = arr[j];
					arr[j] = temp;
				}
			}
		}
	}
}

void print_intarr( int arr[], int len )
{
	int i = 0;
	for ( i = 0; i < len; i++ ){
		printf("%d ", arr[i] );
	}
	printf("\n");
}

void print_chararr( char arr[], int len )
{
	int i = 0; 
	for ( i = 0; i < len; i++ ){
		printf("%c ", arr[i] );
	}
	printf("\n");
}

void print_stringarr( char *arr[], int len )
{
	int i = 0;
	for ( i = 0; i < len; i++ ){
		printf("%s ", arr[i] );
	}
	printf("\n");
}

int main(void)
{
	int		numArr[10] = {1, 3, 5, 7, 9, 2, 4, 6, 8, 0};
	char	chArr[] = "helloworld";
	char	*strArr[] = {"hello", "world", "i", "love", "C"};


	my_sort( numArr, 10, 4, compare_int );
	print_intarr( numArr, 10 );

	my_sort( chArr, 11, 1, compare_char );
	print_chararr( chArr, 11 );

	my_sort( strArr, 5, 5, compare_string );
	print_stringarr( strArr, 5 );

	return 0;
}

但是不知道如何处理啊。想了一下,问题的根源就在于处理不同的位,于是上网搜答案去了:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int compare_int( void const *a, void const *b )
{
	return *( int * )a >= *( int * )b ? 1 : 0;
}

int compare_char( void const *a, void const *b )
{
	return *( char * )a >= *( char * )b ? 1 : 0;
}

int compare_string( void const *a, void const *b )
{
	return *( char ** )a >= *( char ** )b ? 1 : 0;
}

void swap( char *i, char *j, int recsize )    //每个字符进行交换
{
	char x;
	while ( recsize-- > 0 ){
		x = *i;
		*i++ = *j;
		*j++ = x;
	}
}

void sort( char *base, int nel, int recsize, int ( *comp )( char *, char * ) )
{
	char	*i;
	char	*j;
	char	*last;
	
	last = base + ( nel - 1 ) * recsize;

	for ( i = base; i < last; i += recsize ){
		for ( j = i + recsize; j <= last; j += recsize ){
			if ( comp( i, j ) > 0 ){
				swap( i, j, recsize );
			}
		}
	}
}

void print_intarr( int arr[], int len )
{
	int i = 0;
	for ( i = 0; i < len; i++ ){
		printf("%d ", arr[i] );
	}
	printf("\n");
}

void print_chararr( char arr[], int len )
{
	int i = 0; 
	for ( i = 0; i < len; i++ ){
		printf("%c ", arr[i] );
	}
	printf("\n");
}

void print_stringarr( char *arr[], int len )
{
	int i = 0;
	for ( i = 0; i < len; i++ ){
		printf("%s ", arr[i] );
	}
	printf("\n");
}

int main(void)
{
	int		numArr[10] = {1, 3, 5, 7, 9, 2, 4, 6, 8, 0};
	char	chArr[] = "helloworld";
	char	*strArr[] = {"hello", "world", "i", "love", "C"};


	sort( numArr, 10, 4, compare_int );
	print_intarr( numArr, 10 );

	sort( chArr, 11, 1, compare_char );
	print_chararr( chArr, 11 );

	sort( strArr, 5, 4, compare_string );
	print_stringarr( strArr, 5 );

	return 0;
}
运行了一下,答案居然正确。我坦白以我现在的基础,写一天都写不出来这个答案。

真心佩服!!

程序运行如下:

5.

这是从答案中摘录出来的:

#define TRUE 1
#define FALSE 0
#define NULL 0
#define NUL ’\0’
enum { NONE, FLAG, ARG };
/*
** Determine whether the argument is a flag or one that requires a value.
*/
argtype( register int ch, register int control )
{
	while( *control != NUL )
		if( ch == *control++ )
			return *control == ’+’ ? ARG : FLAG;
	return NONE;
}
/*
** Process the arguments.
*/
char **
do_args( int ac, reg char **av, char *control,
		void (*do_arg)( int, char * ), void (*illegal_arg)( int ) )
{
	register char *argp;
	register int ch;
	register int skip_arg;
	while( (argp = *++av) != NULL && *argp == ’–’ ){
		skip_arg = FALSE;
		while( !skip_arg && ( ch = *++argp ) != NUL ){
			switch( argtype( ch, control ) ){
				case FLAG:
					(*do_arg)( ch, NULL );
					break;
				case ARG:
					if( *++argp != NUL || (argp = *++av) != NULL ){
						(*do_arg)( ch, argp );
						skip_arg = TRUE;
						break;
					}
					(*illegal_arg)( ch );
					return av;
				case NONE:
					(*illegal_arg)( ch );
					break;
			}
		}
	}
	return av;
}


© 著作权归作者所有

共有 人打赏支持
fzyz_sb
粉丝 404
博文 209
码字总数 447144
作品 0
武汉
程序员
C++快速入门

只学过Java的我最近笔试接触到各种C++的题目,于是稍微恶补了一下C++的基础部分,以下内容是基于《21天学通C++》的部分读书笔记,按照章节写的。 第二章 C++程序的组成部分 ①#include 预处理...

waffle930 ⋅ 2016/10/02 ⋅ 0

Effective C++ (6) 函数的参数传递

1.非引用形参 普通的非引用类型的参数通过复制对应的实参实现初始化。当用实参副本初始化形参时,函数并没有访问调用所传递的实参本身,因此不会修改实参的值。 2.指针形参 函数的形参可以是...

文艺小青年 ⋅ 2017/06/09 ⋅ 0

Go1.6 和 Go语言圣经中文版 正式发布!

Go1.6 和 Go语言圣经中文版 正式发布! Go1.6正式版本发布。在 https://golang.org/doc/go1.6 可以预览Go1.6的发布信息。自Go1.5发布以来的重大变化有以下几个: 《Go语言圣经》 面世标志着G...

chai2010 ⋅ 2016/01/27 ⋅ 18

c++ primer 第十三章 智能指针

#include <iostream> using namespace std; class HasPtr; class U_Ptr{friend class HasPtr; U_Ptr(int * p):ip(p), use(1){} ~U_Ptr() {delete ip; } int * ip;size_t use;};class HasPtr{......

抢地主 ⋅ 2016/01/20 ⋅ 0

《程序员面试宝典》精华 编程语言部分

《程序员面试宝典》精华 编程语言部分 正所谓取其精华,去其糟粕。本文谨记录下《程序员面试宝典》一些关键的知识点、易错点,对于一些虽然重要但书中没有解释清楚的地方不做记录。当然这里的...

modernizr ⋅ 2014/08/11 ⋅ 2

Oracle数据库学习的福利来啦——最佳入门经验分享

适用人群:从事数据库技术实施或售后工程师,准备参加OCA,OCP认证考试 通过学习对Oracle数据库的相关基础知识进行梳理,最终共同提炼出必须最先掌握的那部分知识,无论你是数据库开发、管理、...

让往事随风 ⋅ 2016/04/13 ⋅ 0

Head First C学习日志 第七章 高级函数 创建函数指针

在c语言中,函数名也是指针变量 函数名是指向函数的指针,当你创建一个叫test的函数的同时,也会创建一个test指针变量,变量中保存了函数的地址。该变量位于常量段。 如何创建函数指针? 假设...

AlexTuan ⋅ 2016/02/29 ⋅ 0

Modern Fortran Explained学习笔记4

第三章.表达式和赋值语句 今天继续从第三章开始学习,第三章主要讲了表达式和赋值语句,其中最主要的是数组的表达和指针等概念的使用。 需要特别注意的点其实不多,主要的东西都是自动实现的...

粗衣大布裹天涯 ⋅ 2017/10/30 ⋅ 0

《Beginning Linux Programming》读书笔记(四)

1, 读写空指针 先看第一种情况,printf试图访问空指针,以打印出字符串,而sprintf试图向空指针写入字符串,这时,linux会在GNU C函数库的帮助下,允许读空指针,但不允许写空指针。 复制代...

嗯哼9925 ⋅ 01/08 ⋅ 0

业余爱好者的C程序设计学习之路

我学习和工作的方向都是化工,和 IT 专业一点边都不搭,属于程序设计爱好者一类。坚持了很多年了,谈谈我的认识。 一、为什么是C 汇编太难,直接下手会吓死宝宝的。 basic 不能考虑,因为“对...

四彩 ⋅ 2016/02/04 ⋅ 2

没有更多内容

加载失败,请刷新页面

加载更多

下一页

浅谈springboot Web模式下的线程安全问题

我们在@RestController下,一般都是@AutoWired一些Service,由于这些Service都是单例,所以并不存在线程安全问题。 由于Controller本身是单例模式 (非线程安全的), 这意味着每个request过来,...

算法之名 ⋅ 今天 ⋅ 0

知乎Java数据结构

作者:匿名用户 链接:https://www.zhihu.com/question/35947829/answer/66113038 来源:知乎 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 感觉知乎上嘲讽题主简...

颖伙虫 ⋅ 今天 ⋅ 0

Confluence 6 恢复一个站点有关使用站点导出为备份的说明

推荐使用生产备份策略。我们推荐你针对你的生产环境中使用的 Confluence 参考 Production Backup Strategy 页面中的内容进行备份和恢复(这个需要你备份你的数据库和 home 目录)。XML 导出备...

honeymose ⋅ 今天 ⋅ 0

JavaScript零基础入门——(九)JavaScript的函数

JavaScript零基础入门——(九)JavaScript的函数 欢迎回到我们的JavaScript零基础入门,上一节课我们了解了有关JS中数组的相关知识点,不知道大家有没有自己去敲一敲,消化一下?这一节课,...

JandenMa ⋅ 今天 ⋅ 0

火狐浏览器各版本下载及插件httprequest

各版本下载地址:http://ftp.mozilla.org/pub/mozilla.org//firefox/releases/ httprequest插件截至57版本可用

xiaoge2016 ⋅ 今天 ⋅ 0

Docker系列教程28-实战:使用Docker Compose运行ELK

原文:http://www.itmuch.com/docker/28-docker-compose-in-action-elk/,转载请说明出处。 ElasticSearch【存储】 Logtash【日志聚合器】 Kibana【界面】 答案: version: '2'services: ...

周立_ITMuch ⋅ 今天 ⋅ 0

使用快嘉sdkg极速搭建接口模拟系统

在具体项目研发过程中,一旦前后端双方约定好接口,前端和app同事就会希望后台同事可以尽快提供可供对接的接口方便调试,而对后台同事来说定好接口还仅是个开始、设计流程,实现业务逻辑,编...

fastjrun ⋅ 今天 ⋅ 0

PXE/KickStart 无人值守安装

导言 作为中小公司的运维,经常会遇到一些机械式的重复工作,例如:有时公司同时上线几十甚至上百台服务器,而且需要我们在短时间内完成系统安装。 常规的办法有什么? 光盘安装系统 ===> 一...

kangvcar ⋅ 昨天 ⋅ 0

使用Puppeteer撸一个爬虫

Puppeteer是什么 puppeteer是谷歌chrome团队官方开发的一个无界面(Headless)chrome工具。Chrome Headless将成为web应用自动化测试的行业标杆。所以我们很有必要来了解一下它。所谓的无头浏...

小草先森 ⋅ 昨天 ⋅ 0

Java Done Right

* 表示难度较大或理论性较强。 ** 表示难度更大或理论性更强。 【Java语言本身】 基础语法,面向对象,顺序编程,并发编程,网络编程,泛型,注解,lambda(Java8),module(Java9),var(...

风华神使 ⋅ 昨天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部