0
点赞
收藏
分享

微信扫一扫

ANSI C (6) —— 指针、断言、信号、跳转


指针

指针的声明

下面的三条语句是等价的,但是我们常常使用的是第一种形式int *p,同时我们注意,他们是在初始化p而不是*p。

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

int main()
{
int a=12;
int *p1=&a;
int* p2=&a;
int * p3=&a;
printf("p1: %p %d\n",p1,*p1);
printf("p2: %p %d\n",p2,*p2);
printf("p3: %p %d\n",p3,*p3);
return 0;
}
/*
p1: 0028FF10 12
p2: 0028FF10 12
p3: 0028FF10 12
*/

指向void 类型的指针可以指向其他的任何的数据类型变量的地址。任何类型的指针也可以指向void.

ANSI C (6) —— 指针、断言、信号、跳转_assert

int main()
{
int a=12;
double b=12.99;
void *p=&a;
printf("%d\n",*((int *)p));
p=&b;
printf("%lf\n",*((double *)p));
return 0;
}
/*
12
12.990000
*/

我们可以在变量value前使用&但是不能在value ‘计算符’ ‘常量或变量’前加&,因为value ‘计算符’ ‘常量或变量’得到的是常量

int a=12,b=10;
int *p=&(a+b); // error: lvalue required as unary '&'

数组和指针

数组名是一个指针常量,数组定义时设置成数组第一个单元的地址,从此以后就不能修改,​​int a[3]; a=...​​​是错误的。
但是修改数组的单元是合法的,​​​a[1]=...​​​。
指针变量可以进行赋值操作。

#include <stdio.h>
int main(){
char *p=NULL;
char s[10]="123456789";
p=s;
printf("%s\n",p);
p=s+2; /* 等价于 p=&s[2]; [2]表示和数组首地址的偏移量是2 */
printf("%s\n",p);
  p=s+4; /* 等价于p=&s[4]; [4]表示和数组首地址的偏移量是4 */
  printf("%s\n",p);
  return 0;
}
/*123456789345678956789*/

利用数组首地址输出字符串:

#include <stdio.h>
#include <stdlib.h>
int main(){
   char s[5]="1234";
   printf("%s\n",s);
   printf("%s\n",&s[0]);
   return 0;
}
/*12341234*/

多级间接访问

指向指针的指针,比如 ​​int ** p​​​ (指向int型变量的指针变量的指针变量)
一次类推还有,​​​int *** p​​​ (p是类型为​​int***​​​的变量,三个星号,代表着三次间接访问)
如下列代码:

int a = 12;
int* p1 = &a;
int** p2 = &p1;
int*** p3 = &p2;
printf("%d\n",***p3);
***p3 = 14;
printf("%d\n",a);

常量指针与指针常量

const修饰的是数据类型,则为常量指针。(const char* p)
const修饰的是指针本身,那么则为指针常量。(char* const p)

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

char s[] = "I love you";
void try1(){
const char* p = s; /* const decorate "char*" */
//*p='H'; /* error: assignment of read-only location ‘*p’ */
}
void try2(){
char* const p = s; /* const decorate "p" */
*p='H'; /* p can't point to another addr */
}

int main(){
char* p = s;
*p='H';
try1();
try2();
printf("%s\n",s);
return 0;
}
/*
output:
H love you
*/

指向函数的指针

指向函数的指针的值是对应函数代码所在的地址。
注意声明的方式,对比下列代码:

int (*p)(char )    /* p是一个指向参数为char,返回值是int的函数的指针 */
int *p(char ) /* p是函数名,该函数的参数为char,返回值类型是int * */

调用相关的方式:
​​​(*p)(ch)​​​
或者
​​​p(ch)​​​
比如:

int get(char ch){
return ch;
}
int main()
{
int (*p)(char);
p = &get;
printf("%d\n",p('A'));
printf("%d\n",(*p)('A'));
return 0;
}
/*
65
65
*/

函数指针应用:
在程序中fork一个子进程,在子进程中弹出选择框,询问用户喜欢哪一类操作系统,然后回到父进程正常退出。

#include <stdio.h>
#include <stdlib.h>
void fun1(){
puts("windows OS ");
}
void fun2(){
puts("linux OS ");
}
void fun3(){
puts("Mac OS ");
}
int main(){
int ret = fork();
if(ret == -1){
perror("fork ");
return -1;
}
else if(ret == 0){
flag:
printf("please tell me which OS do you like most:\n1.windows\n2.linux/Unix\n3.Mac OS\nenter a number: ");
int ret;
scanf("%d",&ret);
void (*func[3])(); /* function pointer array. */
func[0] = &fun1;
func[1] = &fun2;
func[2] = &fun3;
if(ret>=1 && ret<=3){
(*func[ret-1])();
}
else goto flag;
}
else {
int status;
wait(&status);
puts("end....");
}
return 0;
}

执行:

$ ./func 
please tell me which OS do you like most:
1.windows
2.linux/Unix
3.Mac OS
enter a number: 0
please tell me which OS do you like most:
1.windows
2.linux/Unix
3.Mac OS
enter a number: 2
linux OS
end....

restrict指针的疑惑

restrict是c99标准引入的,用于限定和约束指针,它告诉编译器,所有修改该指针所指向内存中内容的操作都必须通过该指针来修改, 任何同样指向这个内存单元的其他指针都是无效指针。
但是下面的例子不是在说“无效指针是有效的”吗?

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

int main(){
int a = 12;
int *restrict p = &a;
int * p2 = &a;
*p = 23;
printf("%d %d\n",*p,*p2);
*p2 = 24;
printf("%d %d\n",*p,*p2);
return 0;
}

执行:

[edemon@CentOS workspace]$ gcc -std=c99 restrict.c 
[edemon@CentOS workspace]$ ./a.out
23 23
24 24

[edemon@CentOS workspace]$ gcc -std=gnu99 restrict.c
[edemon@CentOS workspace]$ ./a.out
23 23
24 24

断言

断言assertion是一个程序特定执行点上必须满足的条件,如果断言不满足,系统将打印错误。
例如:

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

int main()
{
int i;
for(i=0;i<10;i++){
assert(i<9);
}
return 0;
}
/*
Assertion failed: i<9, file E:\code\assert\main.c, line 9

This application has requested the Runtime to terminate it in an unusual way.
Please contact the application's support team for

使用断言需要一定的系统开销,可以在assert.h前增加宏NDEBUG来使得断言设定失效。

#define NDEBUG
#include <assert.h>

信号

程序执行的过程中如果出现错误,计算机系统将产生一个信号来表示发生了异常,C可以调用自己的函数来处理这些信号。 也即是异常处理。
相关的函数是signal()
​​​sighandler_t signal(int signum, sighandler_t handler);​​​
signal()返回上一个信号处理函数的handler的返回值。
三种方式处理异常信号:

function

effect

SIG_DFL

终止程序

SIG_IGN

忽略

自定义函数

自定义处理动作

比如程序:

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

int main(){
signal(SIGINT,SIG_IGN);
while(1);
return 0;
}

即使按下ctrl + c,系统也不会终止程序。
我们也能自定义信号处理函数,比如​​​javascript:void(0)​​ 的《11.自定义中断信号SIGINT的处理函数》。

跳转

这里说的跳转不是goto,而是setjmp(), longjmp().前者设置了跳转的终点,longjmp()则是跳转的入口。

void longjmp(jmp_buf env, int val);
If longjmp() is invoked with a second argument of 0, 1 will be returned instead.
void int

例子:

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

jmp_buf env;
void jmper(){
longjmp(env,0);
puts("hello");
}
void get(){
setjmp(env);
puts("end");
return ;
}
int main(){
get();
jmper();
return 0;
}

执行:

[edemon@CentOS workspace]$ gcc jmp.c 
[edemon@CentOS workspace]$ ./a.out
end
end

goto语句是无法实现不同函数间的跳转的。如上的例子,我们修改成:

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

void get(){
tag:
puts("end");
return ;
}
void jmper(){
goto tag;
puts("hello");
}
int main(){
get();
jmper();
return 0;
}

不出现意外的话,编译的时候会有类似这样的信息:

error: label ‘tag’ used but not defined
goto

其他

块和局部变量

C可以使用块{}来影响变量的作用范围。

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

int main(){
int a=10;
printf("%d\n",a);

{
int a=20;
printf("%d\n",a);
}
return 0;
}
/*
output:
10
20
*/

关于char的负数问题

关于char的负数问题:
ASCII码只有0~127,但是中文是用多字节来表示的,随着字符编码的不同,每一个汉字所占的字节数也是不同的,例如GBK一个汉字两个字节,utf-8一个汉字三个字节。
比如:

/*
Linux CentOS.com 2.6.32-642.6.2.el6.i686 #1 SMP Wed Oct 26 06:14:53 UTC 2016 i686 i686 i386 GNU/Linux
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main()
{
char *str = (char *)malloc(4);
strcpy(str,"好");
printf("%d %d %d %d\n",str[0],str[1],str[2],str[3]);
free(str);
return 0;
}
/*
-27 -91 -67 0
*/


举报

相关推荐

0 条评论