指针
指针的声明
下面的三条语句是等价的,但是我们常常使用的是第一种形式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.
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
*/