C和指针与相关应用
指针的基础知识
- 指针是C语言的一个重要的概念,也是C语言的一个重要特色
- 从地址可以找到内存中存储的相关数据,这个地址对应的就是C语言中的指针
- 指针指向的是数据在内存中的地址,而不是指向内存中内容
&是取内容运算符
就是可以通过变量名,找到这个变量名对应的地址
比如:int a=0,printf("%p",&a)
此时&a就对应于变量a在内存中储存的地址
*是指针运算符
可以通过地址,找到这个地址里面的变量
比如:int a=0,*p;p=&a;
此时p就等同于变量a*,也是是指针p指向的对象,而p就等同于变量a的地址
对于int a=0,*p;p=&a;
来说,*p(指针p指向的对象a)等价于a(原本的对象a)
对于int a=0;int *p;//此时需要对*p进行初始化 *p=a;
来说,*p等于a的内容,此时a的地址和p指向的地址不是一样的 而且必须对*p进行初始化
- 1、*p=a的意思是:将a的值赋给p指针指向的地址的值;
- 2、p=&a的意思是:将a的地址赋给指针p;
#include <stdio.h>
//指针变量 和 指针的区别
int main()
{
int anum = 10;
int bnum = 20;
int *a,*b;
//a,b 为 指针变量 此时指向空地址
//&取地址符号 将后者地址赋值给前者指针变量
a = &anum;
b = &bnum;
printf("anum = %d,bnum = %d\n",anum,bnum);
//%p 输出地址 将指针输出 指针本质是十六进制的地址
printf("a's addr = %p,b's addr = %p\n",a,b);
//*取内容符号 输出地址对应存储的内容
printf("a's num = %d,b's num = %d\n",*a,*b);
return 0;
}
此时指针a,b的地址是不一样的 是两个不同的指针变量
但是a和b指向的地址和内容与anum/bnum是一致的
指针变量的基础应用
- 1.使用指针变量 交换两个数的值
#include <stdio.h>
//用指针交换两个数
//输入两个数 按大小输出
int main()
{
int a,b,temp;
scanf("%d%d",&a,&b);
//a1,b1 为 int型指针变量 此时指向空地址
int *a1,*b1;
//把a的地址赋值给a1 把b的地址赋值给b1
a1 = &a;
b1 = &b;
if(a<b){
//用temp交换a1和b1指向的内容
//实际上是使用temp中间变量交换了a1和b1指向的a和b的内容
temp = *a1;
*a1 = *b1;
*b1 = temp;
}
printf("max = %d,min = %d\n",a,b);
return 0;
}
- 2.也可以使用指针变量作为函数的参数进行传递
#include <stdio.h>
//用指针交换两个数 函数版
//输入两个数 按大小输出
void swaps(int *a1,int *b1){
// 此时a1,b1为指针变量
//*a1就是a,*b1就是b
//交换a,b两变量的值
int temp;
temp = *a1;
*a1 = *b1;
*b1 =temp;
}
int main()
{
int a,b;
scanf("%d%d",&a,&b);
//a1,b1 为 指针变量 此时指向空地址
int *a1,*b1;
//把a的地址赋值给a1 把b的地址赋值给b1
a1 = &a;
b1 = &b;
if(a<b){
//向swaps方法传入指针变量参数 并交换指针变量a1和指针变量b1指向的地址存储的值
swaps(a1,b1);
}
printf("max = %d,min = %d\n",a,b);
return 0;
}
- 3.还可以在嵌套调用中使用指针的办法
#include <stdio.h>
//用指针排序三个数 嵌套调用的方法
//输入三个数 按由大到小输出
void exchange(int *a1,int *b1,int *c1){
//*a1<*b1 交换a,b两数的值
if(*a1<*b1){
swaps(a1,b1);
}
//*a1<*c1 交换a,c两数的值
if(*a1<*c1){
swaps(a1,c1);
}
//*b1<*c1 交换a,b两数的值
if(*b1<*c1){
swaps(b1,c1);
}
}
void swaps(int *a1,int *b1){
// 此时a1,b1为指针变量
//*a1就是第一个参数,*b1就是第二个参数
//交换a,b存储的值
int temp;
temp = *a1;
*a1 = *b1;
*b1 =temp;
}
int main()
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
//a,b,c 为 指针变量 此时指向空地址
int *a1,*b1,*c1;
//把a的地址赋值给a1 把b的地址赋值给b1 把c的地址赋值给c1
a1 = &a;
b1 = &b;
c1 = &c;
//向swaps方法传入指针变量参数 并按情况交换指针变量a1,指针变量b1和指针变量c1 指向的地址存储的值
exchange(a1,b1,c1);
printf("%d %d %d\n",a,b,c);
return 0;
}
通过指针引用数组
- 1.通过指针引用数组的方法
int arr[7] = {1,9,19,8,1,0,0};
//arr是数组的首地址 arr可以作为指针变量
int *p = arr;
//&arr[0]是该数组的首个元素的地址
int *p1 = &arr[0];
//*arr是取数组的首地址存储的值
int arr1 = *arr;
//arr[0]是取数组的首个元素的值
int arr2 = arr[0];
注:
数组名就是一个指向数组第一个元素的指针变量
p指向的是arr的第一个元素的地址 也是数组的首地址
&arr[0]是该数组的首个元素的地址 p=p1=&arr[0]=arr=某个十六进制的地址
*arr是取数组的首地址存储的值
arr[0]是取数组的首个元素的值 arr2=arr1=arr[0]=*arr=1
- 2.通过指针引用数组的应用
#include <stdio.h>
//指针和数组的相关应用
void outputarr(int arr[]){
//接受传入的数组首地址
//顺序打印数组
for(int *a=arr;a<arr+7;a++){
printf("%d ",*a);
}
}
int main()
{
int arr[7] = {1,9,19,8,1,0,0};
//arr是数组的首地址 arr可以作为指针变量
int *p = arr;
//&arr[0]是该数组的首个元素的地址
int *p1 = &arr[0];
printf("CASE 1\n%p %p\n",p,p1);
//*arr是取数组的首地址存储的值
int arr1 = *arr;
//arr[0]是取数组的首个元素的值
int arr2 = arr[0];
printf("%d %d\n",arr1,arr2);
//p+4 是取数组的首地址的p+16 即(4*sizeof(arr)) 指向arr第五个元素的首地址
p = p+4;
//&arr[4] 是取数组的第五个元素地址
p1 = &arr[4];
printf("CASE 2\n%p %p\n",p,p1);
//*(arr+4)是取数组的首地址的p+16的地址值
arr1 = *(arr+4);
//arr[4]取数组的第五个元素的值
arr2 = arr[4];
printf("%d %d\nCASE 3\n",arr1,arr2);
//用数组指针遍历arr 方法1
for(int *a=arr;a<arr+7;a++){
printf("%d ",*a);
}
printf("\nCASE 4\n");
//用数组指针遍历arr 方法2
for(int *a=&arr[0];a<arr+7;a++){
printf("%d ",*a);
}
printf("\nCASE 5\n");
//用数组指针遍历arr 用数组名(一个指向数组第一个元素的指针变量)做参数 方法3
outputarr(arr);
return 0;
}
- 3.通过指针引用多维数组
多维数组的指针使用方法基本和一维数组类似
#include <stdio.h>
//指针和二维数组
void outputarr(int arr[][2]){
printf("CASE 5\n");
//接受传入的二维数组首地址 并循环输出
for(int *a=&arr[0][0];a<arr+2;a+=1){
printf("%d ",*a);
if((a-&arr[0][0])%2!=0){
printf("\n");
}
}
}
int main()
{
int arr[2][2] = {{1,9},{8,1}};
//arr是二维数组的第一行首地址
int *p = arr;
//&arr[0]是该二维数组的第一行的首个元素的地址
int *p1 = &arr[0];
printf("CASE 1\n %p %p\n",p,p1);
//*arr[0]是取二维数组的第1行第1列存储的值
int arr1 = *arr[0];
//arr[0][0]是取二维数组的第1行第1列存储的值
int arr2 = arr[0][0];
printf("%d %d\n",arr1,arr2);
//p+1 是取二维数组的第一行首地址的p+16 即(2*sizeof(arr)) 指向arr第2行的首地址
p = p+1;
//&arr[1] 是取二维数组的第二行第一个元素的地址
p1 = &arr[1];
printf("CASE 2\n%p %p\n",p,p1);
//*(arr+1)是取二维数组的第二行的(第一个元素)首地址 *(arr+1)+1 为第二行第二个元素的地址 *(*(arr+1)+1)为取数组第二行第二个元素的值
arr1 = *(*(arr+1)+1);
//arr[1][1]取二维数组第二行第二个元素的值
arr2 = arr[1][1];
printf("%d %d\n",arr1,arr2);
printf("CASE 3\n");
//用数组指针遍历arr 方法1
for(int *a=&arr[0][0];a<arr+2;a+=1){
printf("%d ",*a);
if((a-&arr[0][0])%2!=0){
printf("\n");
}
}
printf("CASE 4\n");
//用数组指针遍历arr 方法2
for(int *a=arr;a<arr+2;a+=1){
printf("%d ",*a);
if((a-arr[0])%2!=0){
printf("\n");
}
}
//用数组指针遍历arr 用二维数组首地址做参数 方法3
outputarr(arr);
return 0;
}
通过指针引用字符串
通过指针引用字符串的原理和通过指针引用数组是类似的
#include <stdio.h>
//引入memset函数
#include <string.h>
//指针和字符串和相关操作 定义strcopy方法
void strcopy(char str[],char str1 []){
for(char *c = str,i=0 ; *c!='\0';c++,i++){
str1[i] = *c;
}
}
int main()
{
char str[]="HELLO WORLD!!!";
char str1[15];
//str是字符数组的起始地址
//%s输出str串
printf("%s\n",str);
//*str输出str第一个元素
printf("%c\n",*str);
//a串赋值到b串 方法1 老办法
for(int i = 0 ; i < sizeof(str);i++){
str1[i] = str [i];
}
printf("CASE 1 \n%s\n%s\n",str,str1);
//清空字符串str1
memset(str1,'\0',sizeof(str1));
//a串赋值到b串 方法2 将str首地址赋值到字符指针上
for(char *c = str,i=0 ; c < str + sizeof(str)&&*c!='\0';c++,i++){
//把c指向的字符内容赋值到相对应的str1的位置上
str1[i] = *c;
}
printf("CASE 2 \n%s\n%s\n",str,str1);
//清空字符串str1
memset(str1,'\0',sizeof(str1));
//a串赋值到b串 方法3 数组作参数
strcopy(str,str1);
printf("CASE 3 \n%s\n%s\n",str,str1);
return 0;
}
字符指针变量和字符数组的区别
- 两者的声明不一样
字符数组char str[20];
字符指针变量char str[20];char *p=str;
- 字符数组由若干个元素组成,每个元素都是一个字符 而 字符指针变量是存放的指向一个字符类型的变量的地址
- 赋值方式不同
可以对字符指针赋值 不能对数组名赋值
char *a; //声明一个字符指针变量
a = "I love China!"; //把字符串的首地址赋值给字符指针变量 赋值给a的是首地址 并不是内容
char str[14]; //声明一个字符数组
str[0] = 'I'; //合法 对数组元素赋值
str = "I love China!"; //不合法 不能给指针对应的地址赋值
- 编译时 为字符数组分配足够多的存储单元 而字符指针变量只分配一个存储单元
- 指针变量的值可以改变 但是数据名对应的值不能改变
#include <stdio.h>
int main(){
char *a = {"I love China!"};
a = a+7; //正确 a是指针变量 可以改变值
printf("%s\n",a);
return 0;
}
#include <stdio.h>
int main(){
char str[] = "I love China!";
str= str+7; //错误 数组名不能改变
printf("%s\n",str);
return 0;
}
- 字符数组的各个元素可以改变 , 字符指针变量指向的字符串是个常量不能改变
char str[] = "I love China!";
str[2] = 'r'; //合法 可以对字符数组进行重新赋值
char *a = "I love China!";
a[2] = 'r'; //不合法 不可以对字符串常量进行重新赋值
return 0;
指向函数的指针
定义一个指向函数的函数指针变量
比如
int max(int a,int b){ return a>b?a:b;}
int (*p)(int,int) = max;
使用(*p)(a,b) 来代替 max(a,b)的调用
#include <stdio.h>
//函数指针
//输入三个数 输出最大和最小的数
//定义函数max2num
int max2num(int a,int b)
{
//返回两数最大值
return a>b?a:b;
}
//定义函数max3num
int max3num(int a,int b,int c)
{
int maxnum;
//定义三个两个参数的函数指针p,p1,p2 指向max2num
int (*p)(int,int) = max2num;
int (*p1)(int,int) = max2num;
int (*p2)(int,int) = max2num;
//调用三次max2num函数 获得最大值
return (*p2)((*p)(a,b),(*p1)(b,c));
}
//定义函数min2num
int min2num(int a,int b)
{
//返回两数最小值
return a<b?a:b;
}
//定义函数min3num
int min3num(int a,int b,int c)
{
int minnum;
//定义三个两个参数的函数指针p,p1,p2 都指向min2num函数
int (*p)(int,int) = min2num;
int (*p1)(int,int) = min2num;
int (*p2)(int,int) = min2num;
//调用三次min2num函数 获得最小值
return (*p2)((*p)(a,b),(*p1)(b,c));
}
//定义指针型函数output
void *output(int a,int b,int c)
{
//定义一个三个参数的函数指针p 指向max3num函数
int (*p)(int,int,int) = max3num;
//定义一个三个参数的函数指针p1 指向min3num函数
int (*p1)(int,int,int) = min3num;
//通过指针调用函数 (*p)(a,b,c)返回最大值 (*p1)(a,b,c)返回最小值
printf("maxnum is %d minnum is %d",(*p)(a,b,c),(*p1)(a,b,c));
}
int main()
{
int a,b,c;
scanf("%d %d %d",&a,&b,&c);
//声明一个函数指针
int (*p)(int,int,int);
//指向指针型函数
p = &output;
//调用指针型函数
p(a,b,c);
return 0;
}
返回指针值的函数
返回指针值的函数的声明
表示声明一个叫maxn的函数 该函数的返回值类型是指向int型变量的一个指针值
int *maxn(int a,int b){
}
#include <stdio.h>
//返回指针值的函数maxn 返回的是指向一个int型变量指针值
int *maxn(int a,int b){
int *temp ;
temp = a>b?&a:&b;
return temp;
}
int main()
{
int a,b;
scanf("%d%d",&a,&b);
//接收该指针值
int *temp =maxn(a,b);
//输出该指针指向的数据
printf("the max number is %d",*temp);
return 0;
}
指针数组
指针数组的声明
数据类型名 * 数组名[数组长];
注意不能写成
数据类型名 (* 数组名)[数组长];
前者是一个指针数组 数组的每一个元素都是一个指针 而后者定义一个指向一维数组的指针
#include <stdio.h>
#include <string.h>
//指针数组
//使用指针数组排序多个字符数组 使用指针数组作为参数传入函数内部
void sortstr(char *str[])
{
char *temp;
int i,j,k;
//冒泡排序的相关代码
for( i = 0 ; i < 4; i ++)
{
k = i;
for(j=i+1; j<5; j++)
{
if(strcmp(str[k],str[j])>0)
{
k = j;
}
}
if(k != i)
{
//交换不同指针指向的字符串内容
temp = str[i];
str[i] = str[k];
str[k] = temp;
}
}
}
//输出指针数组
void printstr(char *str[])
{
for(int i=0; i<5; i++)
{
printf("%s\n",str[i]);
}
}
int main()
{
//定义一个指向五个字符数组的指针数组str
char *str[]= {"banana","cheer","cheems","apple","microsoft"};
//使用指针数组的首地址作为参数 传入sortstr方法进行排序
sortstr(str);
//使用指针数组的首地址作为参数 传入printstr方法进行顺序输出
printstr(str);
return 0;
}
内存的动态分配
常用的内存分配函数有malloc,free,realloc,calloc
这些源文件都包含在stdlib.h头文件内
malloc
原型定义 void *malloc(size_t size)
参数是需要划分的内存的大小 单位是字节数
返回的是无类型的指针 需要手动转换为需要的数据类型
比如
char *p;
//用malloc动态分配内存区域给p
p = (char *) malloc(10*sizeof(char));//强制转换为需要的数据类型
free
原型定义 void free(void *ptr)
释放之前调用 calloc、malloc 或 realloc 所分配的内存空间。
比如
char *p;
//用malloc动态分配内存区域给p
p = (char *) malloc(10*sizeof(char));//强制转换为需要的数据类型
//释放分配的内存空间
free(p);
realloc
原型定义 void *realloc(void *ptr, size_t size)
尝试重新调整之前调用 malloc 或 calloc 所分配的 ptr 所指向的内存块的大小。
参数是之前分配好内存的指针变量和需要重新划分的内存的大小 单位是字节数
返回的是无类型的指针 需要手动转换为需要的数据类型 如果请求失败,则返回为 NULL。
比如
char *p;
//用malloc动态分配内存区域给p
p = (char *) malloc(10*sizeof(char));//强制转换为需要的数据类型
//调整之前分配的内存空间
realloc(p,20*sizeof(char));
calloc
原型定义 void *calloc(size_t nitems, size_t size)
分配所需的内存空间,并返回一个指向它的指针。
主要是为了开辟动态的内存区
参数是需要划分的区域的个数nitems 和每个区域的字节数size
返回的是无类型的指针 需要手动转换为需要的数据类型
char *p;
//用calloc动态分配内存区域给p
p = (char *) calloc(50,sizeof(char));//强制转换为需要的数据类型
#include <stdio.h>
#include <stdlib.h>
int main()
{
//定义指针数组
char *p;
//用malloc动态分配内存
p = (char *) malloc(10*sizeof(char));
//p = (char *) calloc(50,sizeof(char));
scanf("%s",p);
//三种输出方式
puts(p);
printf("%s\n",p);
char *temp = p;
for(int i=0;i<10&&*(temp)!='\0';i++){
printf("%c",*(temp++));
}
printf("\n");
//realloc不太容易成功 有可能返回NULL 导致程序闪退
realloc(p,20*sizeof(char));
free(p);
//free之后再printf会报错或闪退
//printf("%d\n",p[0]);
//用calloc重新动态分配内存
p = (char *) calloc(50,sizeof(char));
//使p[0]为'c'
p[0]='1';
printf("%c\n",p[0]);
return 0;
}
c和指针的相关小结
- 使用指针时要注意不要越过数组的上界与下界 如果数组越界可能会导致程序闪退或报错
- 使用指针变量之前一定要初始化,否则可能出现野指针的问题 使用这个指针有可能导致程序闪退或报错
- 两个指针变量可以相减。两个指针相减则表示两个指针相差的元素个数 至少需要保证这两个指针都是指向一个数组的不同元素的合法指针
- 常见的指针的应用如下
定义 | 含义 |
---|---|
int *p; | 定义指向一个整形数据的指针p |
int **p; | 定义指向一个整形数据的指针变量的指针变量p |
int *p[n]; | 定义指针数组p,该指针数组的每个元素都是一个单独的指针变量 |
int (*p)[n]; | 定义指向拥有n个元素的一维数组的指针变量p |
int *p(); | 定义返回值类型为指向一个整形变量的函数p() |
int (*p)(); | 定义一个函数指针p,指向返回值为整形变量的一个方法比如int func(),可以用 (*p)()来代替func()的调用 |