要了解指针必须先明白一个概念间接引用:通过变量的内存地址改变变量的值
指针:用来存放变量地址的变量就是指针变量,指针一般指向一个函数或一个变量。在使用一个指针时,通过存储地址间接改变变量的值,一个程序既可以直接使用这个指针所储存的内存地址,又可以使用这个地址里储存的函数的值。
指针占用的存储空间和指针变量的类型无关和编译器有关,使用sizeof查看占用字节数
| 16位编译器 | 32位编译器 | 64位编译器 |
Void*(指针变量) | 2 | 4 | 8 |
如果指针变量没有指向和指针类型相同的变量 指针会从变量开始取相应的字节,所以指针必须分类型
char a1; int *s=&a1; //会认为a1是int类型所以会取2位(在十六位编译器环境下) |
char a; char *b=&a;//此处并不是赋值操作而是定义了一个指针变量b,然后给b赋值等同于下面 //char *b; //*b声明指针变量 // b=&a; printf("%d",*b); //*b 取出变量a存储的值,也就是访问a的地址 |
一个没有任何指向的指针不能使用(错误语法)
char *t; *t='1'; //没有指向任何变量的时候,是随机指向,是错误做法 t=100; //指针变量是用来存储变量地址的,不能随便存储一个常数,除非这个常数刚好等于某个变量的地址 |
利用指针交换两个变量的值
void toplay(char *p1,char *p2){
char temp=*p1; //第二步
*p1=*p2; //第三步
*p2=temp; //第四步
}
int main(int argc, const char * argv[])
{
//第一步
char a=10;
char b=20;
toplay(&a,&b);
printf("%i",a);
return 0;
}
利用指针一个函数返回多个值
int sumAndMin(int a,int b,int *p){ *p=a-b; return a+b; } int main(int argc, const char * argv[]) { int a=10; int b=20; int min; sumAndMin(a, b, &min); printf("%i",min); return 0; } |
指针指向数组
//指针指向数组元素 int array[3]={9,2,3}; int *p=&array[0]; //int *p=array; 因为array就是array[0]的地址 //改变首个元素的值 *p=10;
//利用指针遍历数组 int *k=&array; //指向的并不是数组,而是数组的第一个元素 for (int i=0; i<3; i++) { printf("array[%d]=%d\n",i,*(p+i)); // p+i取的是第i个元素的地址 p的值一直都没有改变过 }
for (int i=0; i<3; i++) { printf("array[%d]=%d\n",i,*(p++)); // p的值一直在改变,每次遍历都会改变 }
for (int i=0; i<3; i++) { printf("array[%d]=%d\n",i,*(array+i)); //因为array是个常量所以不能写成array++ }
for (int i=0; p<array+3; i++,p++) { printf("array[%d]=%d\n",i,*(p)); // } |
指针和数组函数的关系
当一个方法的参数是数组的时候,我们可以传入数组也可以传入一个和参数类型相同的指针
void arrayMethod(char s[]){ s[0]=12; } 程序调用 char a[]={1,2}; char *p=a; arrayMethod(p); arrayMethod(a); |
而当一个方法的参数是指针的时候,我们同样可以传入一个指针或者一个同类型的数组
void pointMethod(char *p){ p[0]=12; p[1]=14;//修改数组第一个元素的值 p[i]代表第i个元素 } 程序调用: char a[]={1,2};
char *p=a; //arrayMethod(p); //arrayMethod(a); pointMethod(p); pointMethod(a); |
从以上可以看出数组和指针的关系密切,本质原因是当我们一个方法需要传入数组的时候,传入的数组名代表了数组的地址也就是数组第一个元素的地址,而我们传入一个指向数组的指针也就是指向了数组的地址;而当我们一个方法需要传入指针的时候,这个指针需要传入的是一个地址,而这个地址同样可以是同类型的数组的地址,所以可以传入一个数组。
字符串与指针
字符串遍历两种方式
1.传统遍历方法
//遍历一个字符串 void forString(){ char str[]="upsbus.com"; for (int i=0; str[i]!='\0'; i++) { printf("%c\n",str[i]); } } |
指针遍历方法
//通过指针遍历一个字符串 void forStringByPonit(){ //定义的是一个字符串变量 char str[]="upsbus.com"; char *p=str; //*p=&str 因为数组名代表的是数组地址,而数组的地址又是数组第一个元素的地址,所以&可以省略 for (; *p!='\0'; p++) { printf("%c\n",*p); } } |
虽然这个方法看起来有点怪怪的样子,不过我们不得不承认指针的便捷性。
void forStringByPonit2(){ //定义的是一个字符串常量 char *p="upsbus.com"; //这里*p并不是指向整个字符串而是指向第一个字符,因为这是一个指向字符的指针 //char *p; // p="upsbus.com"; 拆分过程 printf("%c\n",*p);//打印出来的是第一个元素的内容 for (; *p!='\0'; p++) { printf("%c\n",*p); } } |
从这个方法可以看出C语言给我们带来便捷性的同时也给我们埋下了很多的陷阱
字符串常量和字符串变量
//字符串常量 char *p1="upsbus.com"; //字符串变量 char p2[]="upsbus.com"; |
而当我们在C语言中定义字符串的时候要考虑清楚,到底是需要一个常量的字符串还是需要一个变量的字符串就像我们在Java中有时候会定义一些final的变量一样。
C语言字符串陷阱,以下都是错误代码
//错误原因是temp是一个常量,代表数组地址即第一个元素的地址,我们不能对一个常量进行赋值 char temp[20]; temp="upsbus.com"; 错误提示:Array type 'char [20]' is not assignable
char *temp="upsbus.com"; temp=“baidu.com”;//因为temp是一个常量,所以不能对temp进行赋值 temp='a';//此处虽然temp代表的是第一个元素u,看上去可以赋值一个字符,但是temp是一个常量所以不能进行赋值 |
指针和函数
返回指针的函数
//返回函数的指针 char * returnPoint(){ return "coolJune"; } |
定义一个指向函数的指针
int sumNum(int a,int b){ return a+b; } 程序调用: int (*p)(int ,int ); //int(*p)(int a,int b);
p=sumNum; //方法的地址直接用方法名取
//程序调用 int i=p(2,3); |
因为方法在内存中也有一块地址来存储,所以才可以定义一个指向方法的指针。
传入一个函数指针
//传入一个函数指针,由传入函数的指针决定做什么运算 int calc(int a,int b,int (*p)(int,int)){ return p(a,b); } int (*p)(int ,int );
p=sumNum; int (*s)(int,int); s=minNum; int i=calc(5, 15, s); |
以上这种做法可以保证calc这段程序的完整性,如果需求变更,不用改这段代码