一、概念
简单的说指针就是一个地址
指针是一个变量,其值为另一个变量的地址,即,内存位置的直接地址。就像其他变量或常量一样,在使用指针存储其他变量地址之前,对其进行声明。指针变量声明的一般形式为:
int *ip; /* 一个整型的指针 */ double *dp; /* 一个 double 型的指针 */ float *fp; /* 一个浮点型的指针 */ char *ch; /* 一个字符型的指针 */
//指针的入门 #include <stdio.h> void main(){ int num = 1; //定义一个指针变量,指针 //说明: //1.int*表示类型为 指针类型(执行一个int类型指针) //2.名称 ptr, ptr就是一个int*类型的变量地址 //ptr指向了一个int类型的变量的地址 int*ptr = # //num的地址是多少 //如果要输出一个变量的地址,使用格式是%p //&num表示取出这个num这个变量的对应地址 printf("num 的值=%d\nnum 的地址=%p",num,&num); //指针变量本身也有地址: printf("\nptr 的地址是%p",&ptr); printf("\nptr 存放的值是一个地址%p",ptr); printf("\nptr 指向的值=%d",*ptr); getchar(); }
练习:
#include <stdio.h> void main(){ //1) 写一个程序,获取一个int变量num的地址,并显示到终端 int num = 100; //2) 将num的地址赋给指针 ptr , 并通过ptr去修改num的值. //指针的类型和指向的变量类型对应(相等) int * ptr = # printf("num的值=%d\nnum的地址=%p",num,&num);//num =100 *ptr = 99;//修改num的值 printf("\nnum的新值=%d",num);//num = 99 //3) 并画出案例的内存布局图 getchar(); }
void main() { int a = 300; int *ptr = a; } 题1 错误: 把 int 赋给 int *
void main() { int a = 300; float *ptr = &a; } 题2 错误:把 int 的地址赋给 float *
void main() { int a = 300; // a = 300 int b = 400; // b = 400 int * ptr = &a; //ok ptr 指向 a *ptr = 100; // a = 100 ptr = &b; // ok ptr 指向 b *ptr = 200; // b = 200 printf("\n a=%d,b=%d,*ptr=%d", a, b, *ptr); getchar(); } //输出什么内容 a=100; b=200;&ptr=200;
指针细节说明
-
基本类型,都有对应的指针类型, 形式为 数据类型 *,比如 int的对应的指针就是 int *, float 对应的指针类型就是 float * , 依次类推。
-
此外还有指向数组的指针、指向结构体的指针,指向共用体的指针
二、值传递和地址传递
C语言传递参数(或者赋值)可以是值传递(pass by value),也可以传递指针(a pointer passed by value), 传递指针也叫地址传递。
-
默认传递值的类型:基本数据类型 (整型类型、小数类型,字符类型), 结构体,共用体。
-
默认传递地址的类似:指针、数组
练习
#include <stdio.h> void main(){ //试编写程序实现如下效果 //姓名 年龄 成绩 性别 爱好 //xx xx xx xx xx //要求: //a、用变量将姓名、年龄、成绩、性别、爱好存储 //b、添加适当的注释 //c、添加转义字符 char name[10] = "蒋容丞";//字符数组可以存放字符串 short age = 23; float score = 99.8; char gender = 'M'; char hobby[10] = "篮球"; printf("姓名\t年龄\t成绩\t性别\t爱好\n%s\t%d\t%.2f\t%c\t%s",name,age,score,gender,hobby); getchar(); }
//自己推结果 void main() { int number1; int number2; int number3; int number4 = 50; int number5; number1 = 10; number2 = 20; number3 = number1 + number2; printf("\nNumber3 = %d" , number3)//30 number5 = number4 - number3; printf("\nNumber5 = %d" , number5);//20 getchar(); }
传地址或指针给指针变量
#include <stdio.h> void test2(int *p);//函数声明,接收int * void main() { int num=90; int *p = #//将num 的地址赋给p test2(&num); //传地址(传num的地址) printf("\nmain() 中的num=%d", num); test2(p); //传指针(传p之指针也就是p里存放的指针) printf("\nmain() 中的num=%d", num); getchar(); } void test2(int *p) { *p += 1;//*p 就访问num的值 }
传数组给指针变量
#include <stdio.h> /* 函数声明 */ double getAverage(int *arr, int size); double getAverage2(int *arr, int size); int main () { /* 带有 5 个元素的整型数组 */ int balance[5] = {1000, 2, 3, 17, 50}; double avg; /* 传递一个指向数组的指针作为参数 */ avg = getAverage2( balance, 5 ); /* 输出返回值 */ printf("Average value is: %f\n", avg ); getchar(); return 0; } //说明arr是一个指针 double getAverage(int *arr, int size) { int i, sum = 0; double avg; for (i = 0; i < size; ++i) { sum += arr[i]; } avg = (double)sum / size; return avg; } double getAverage2(int *arr, int size) { int i, sum = 0; double avg; for (i = 0; i < size; ++i) { sum += *arr; arr++; } avg = (double)sum / size; return avg; }
三、指针的算术运算
介绍:
指针是一个用数值表示的地址。可以对指针执行算术运算。可以对指针进行四种算术运算:++、--、+、-。
指针递增操作(++)
指针递减操作(--)
#include <stdio.h> const int MAX = 3; int main () { int var[] = {10, 100, 200}; int i, *ptr; /* 指针中最后一个元素的地址 */ ptr = &var[MAX-1]; for ( i = MAX; i > 0; i--) { printf("ptr存放的地址=%p, var[%d] 的地址= %p\n", ptr, i-1,&var[i-1] ); printf("存储值:var[%d] = %d\n", i-1, *ptr ); ptr--; } getchar(); return 0; }
指针+、-操作
#include <stdio.h> int main () { int var[] = {10, 100, 200}; int i, *ptr; ptr = var;//将var的首地址赋给ptr ptr += 2; //ptr的存储地址+8个字节 printf("var[2]=%d var[2]的地址=%p ptr存储的位置(prt的地址)=%p ptr指向的值 =%d",var[2], &var[2], ptr, *ptr); getchar(); return 0; }
四、指针的比较
指针可以用关系运算符进行比较,如 ==、< <=(小于等于) 和 > >=(大于等于)。如果 p1 和 p2 指向两个变量,比如同一个数组中的不同元素,则可对 p1 和 p2 进行大小比较, 看下面代码,说明输出什么?
#include <stdio.h> int main () { int var[] = {10, 100, 200}; int *ptr; ptr = var;//ptr 指向var 首地址(第一个元素) if(ptr == var[0]) {//错误,类型不一样(int* 和 int不兼容) printf("ok1"); } if(ptr == &var[0]) { // 可以 printf("\nok2"); //输出 } if(ptr == var) { //可以 printf("\nok3"); //输出 } if(ptr >= &var[1]) { //可以比较,但是返回false printf("\nok4");//不会输出 } getchar(); }
#include <stdio.h> const int MAX = 3; int main () { int var[] = {10, 100, 200}; int i, *ptr; ptr = var; i = 0; while ( ptr <= &var[MAX - 2] )//&var[1] { printf("Address of var[%d] = %x\n", i, ptr ); printf("Value of var[%d] = %d\n", i, *ptr ); ptr++; i++; } //会输出 10 , 100 getchar(); return 0; }
五、指针数组
基本介绍
要让数组的元素 指向 int 或其他数据类型的地址(指针)。可以使用指针数组。
指针数组定义 数据类型 *指针数组名[大小]; 比如: int *ptr[3];
-
ptr 声明为一个指针数组
-
由 3 个整数指针组成。因此,ptr 中的每个元素,都是一个指向 int 值的指针。
#include <stdio.h> const int MAX = 3; int main () { int var[] = {10, 100, 200}; int i, *ptr[3]; for ( i = 0; i < MAX; i++) { ptr[i] = &var[i]; /* 赋值为整数的地址 */ } for ( i = 0; i < MAX; i++) { printf("Value of var[%d] = %d\n", i, *ptr[i] ); } getchar(); return 0; }
#include <stdio.h> //请编写程序,定义一个指向字符的指针数组来存储字符串列表(四大名著书名), 并 //通过遍历 该指针数组,显示字符串信息 , (即:定义一个指针数组,该数组的每个 //元素,指向的是一个字符串) const int Max = 3; void main(){ //定义一个指针数组,该数组的每个元素,指向的是一个字符串 char *books[] = { "三国演义", "水浒传", "西游记", "红楼梦" }; int i; for(i = 0;i<=Max;i++){ printf("\nbooks[%d]=%s",i,books[i]); //%s传递地址,所以books[i]不加* } getchar(); }
六、指向指针的指针(多重指针)
基本介绍 指向指针的指针是一种多级间接寻址的形式,或者说是一个指针链。通常,一个指针包含一个变量的地址。当我们定义一个指向指针的指针时,第一个指针包含了第二个指针的地址,第二个指针指向包含实际值的位置
多重指针(二级,三级)快速入门案例
-
一个向指针的指针变量必须如下声明,即在变量名前放置两个星号。例如,下面声明了一个指向 int 类型指针的指针:
int **ptr; // ptr 的类型是 int **
-
当一个目标值被一个指针间接指向到另一个指针时,访问这个值需要使用两个星号运算符, 比如 **ptr
#include <stdio.h> int main () { int var; int *ptr;//一级指针 int **pptr;//二级指针 var = 3000; ptr = &var; pptr = &ptr; printf("var的地址=%p var = %d \n", &var, var ); printf("ptr 的本身的地址=%p ptr存放的地址=%p*ptr = %d \n", &ptr, ptr, *ptr ); printf("pptr 本身地址 = %p pptr存放的地址=%p**pptr = %d\n", &pptr, pptr, **pptr); getchar(); return 0; } var的地址=006FFB94 var = 3000 ptr 的本身的地址=006FFB88 ptr存放的地址=006FFB94*ptr = 3000 pptr 本身地址 = 006FFB7C pptr存放的地址=006FFB88**pptr = 3000
七、返回指针的函数
C语言 允许函数的返回值是一个指针(地址),这样的函数称为指针函数
#include <stdio.h> #include <string.h> char *strlong(char *str1, char *str2){ //函数返回的char * (指针) printf("\nstr1的长度%d str2的长度%d", strlen(str1), strlen(str2)); if(strlen(str1) >= strlen(str2)){ return str1; }else{ return str2; } } int main(){ char str1[30], str2[30], *str;//str是一个指针类型,指向字符串 printf("\n请输入第1个字符串"); gets(str1); printf("\n请输入第2个字符串"); gets(str2); str = strlong(str1, str2); printf("\nLonger string: %s \n", str); getchar(); return 0; }
指针函数注意事项和细节
-
用指针作为函数返回值时需要注意,函数运行结束后会销毁在它内部定义的所有局部数据, 包括局部变量、局部数组和形式参数,函数返回的指针不能指向这些数据【案例演示】
#include <stdio.h> int *func(){ int n = 100;//局部变量 在func返回时,就会销毁 return &n; } /* 函数运行结束后会销毁该函数所有的局部数据, 这里所谓的销毁并不是将局部数据所占用的内 存全部清零,而是程序放弃对它的使用权限, 后面的代码可以使用这块内存 */ int main(){ int *p = func(), n;//返回得是指针 n = *p; printf("\nvalue = %d\n", n); getchar(); return 0; }
-
函数运行结束后会销毁该函数所有的局部数据,这里所谓的销毁并不是将局部数据所占用的内 存全部清零,而是程序放弃对它的使用权限,后面的代码可以使用这块内存 【案例演示】
-
C 语言不支持在调用函数时返回局部变量的地址,如果确实有这样的需求,需要定义局部变量为 static 变量 【案例演示】
#include <stdio.h> int *func(){ static int n = 100;//如果这个局部变量时static性质的,那么n存放数据的空间在静态数据取 return &n; } int main(){ int *p = func(), n;//func返回指针 printf("okookokko");//可能是使用到局部变量 int n = 100占用空间 n = *p; printf("\nvalue = %d\n",n); getchar(); return 0; }
练习:
#include <stdio.h> #include <stdlib.h> //编写一个函数,返回一个一维数组 int * f1(){ static int arr[10];//必须加上static,让arr的空间在静态数据区分配. int i= 0; for(i = 0;i<10;i++){ arr[i] = rand();//随机数 } return arr; } void main(){ int *p; int i; p = f1();// for(i = 0;i<10;i++){//p指向的是在f1 生成的数组的首地址(即第一个元素的地址) printf("\n%d",*(p+i)); } getchar(); }
八、函数指针
函数指针(指向函数的指针)
基本介绍
-
一个函数总是占用一段连续的内存区域,函数名在表达式中有时也会被转换为该函数所在内存区域的首地址,这和数组名非常类似。
-
把函数的这个首地址(或称入口地址)赋予一个指针变量,使指针变量指向函数所在的内存区域,然后通过指针变量就可以找到并调用该函数。这种指针就是函数指针。
#include <stdio.h> //max函数 接收两个int,返回较大数 int max(int a, int b){ return a>b ? a : b; } int main(){ //说明 函数指针 //函数指针的名字是pmax //int 表示 该函数指针指向的函数是返回int类型 //(int,int)表示该函数指针指向的函数形参是接收两个int //在定义函数指针时,也可以写上形参名 int x, y, maxVal; int (*pmax)(int, int) = max; // printf("Input two numbers:"); scanf("%d %d", &x, &y); //还可以用maxVal = pmax(x, y); maxVal = (*pmax)(x, y); printf("Max value: %d\n", maxVal); getchar(); getchar(); return 0; }
九、回调函数
基本介绍
-
函数指针变量可以作为某个函数的参数来使用的,回调函数就是一个通过函数指针调用的函数。
-
简单的讲:回调函数是由别人的函数执行时调用你传入的函数(通过函数指针完成)
#include <stdio.h> #include <stdlib.h> // 回调函数 //int(*f)(void) //f就是 函数指针,它可以接收的函数是 (返回int,没有形参的函数) //f在这里被intArray调用,充当了回调函数的角色 void initArray(int *array, int arraySize, int (*f)(void)) { int i ; for ( i=0; i<arraySize; i++) array[i] = f();//通过函数指针 调用了getNextRandomValue函数 } // 获取随机值 int getNextRandomValue(void) { return rand(); } int main(void) { int myarray[10],i; //调用initArray函数 //传入了一个函数名getNextRandomValue(地址),需要使用函数指针接收 initArray(myarray, 10, getNextRandomValue); for(i = 0; i < 10; i++) { printf("%d ", myarray[i]); } printf("\n"); getchar(); return 0; }
十、指针的注意事项和细节
-
指针变量存放的是地址,从这个角度看指针的本质就是地址。
-
变量声明的时候,如果没有确切的地址赋值,为指针变量赋一个 NULL 值是好的编程习惯。
-
赋为 NULL 值的指针被称为空指针,NULL 指针是一个定义在标准库 <stdio.h>中的值为零的常量 #define NULL 0 [案例]
-
指针使用一览 (见后)
#include <stdio.h> void main(){ int *p = NULL;//p空指针 int num = 34; p = # printf("p = %d",*p);//34 getchar(); }