十年网站开发经验 + 多家企业客户 + 靠谱的建站团队
量身定制 + 运营维护+专业推广+无忧售后,网站问题一站解决
在内存中,每一个自己单元,都要一个编号,称为地址。在虚拟内存中,也同样如此。
专注于为中小企业提供成都网站设计、成都网站建设服务,电脑端+手机端+微信端的三站合一,更高效的管理,为中小企业北戴河免费做网站提供优质的服务。我们立足成都,凝聚了一批互联网行业人才,有力地推动了上千多家企业的稳健成长,帮助中小企业通过网站建设实现规模扩充和转变。专门用来存放地址的变量称为指针变量,简称指针。
不管是什么类型的指针变量,在32位机上,占4个字节;在64位机上,占8个字节
2、指针运算(1)p是一个 指针变量;p + n 或者 p - n ,实际得到的地址量是
p + / - sizeof(* p) *n
(2)不同数据类型的两个指针进行加减运算是无意义的
p 、q 是两个相同类型的指针变量;
p - q 得到的是 (p的地址在 - q的地址值)/sizeof(*p)
(3)指针是一个地址值,可以进行关系运算。同理,不同类型的指针进行该运算没有意义。
3、指针和数组(1)本质的区别是指针是指针是一个变量,用来储存地址,数组名是地址常量;
数组名是地址常量,数组名的类型是数组元素的指针类型。
在把数组名当成一个地址使用的时候,和&数组名[0]是等价的。
更进一步的讲,比如 int arr[5]={0};
arr+ i 和 &arr[i]是等价的,因此 *(arr + i) 和 arr[i]是等价的。对数组的下标操作本质是地址操作,因此,i[arr] 和 arr[i] 其实是一样的。
在把数组名当一个数组类型使用的时候,才更符合我们常规意义上的数组概念。
比如 sizeof(arr) 和 &arr。
#include#includeint main(void)
{
int arr[5] = {0,1,2,3,4};
int *p1 = arr; //arr作为地址使用,类型是 int *型 和 &arr[0] 等价
int *p2 = &arr[0];
printf("%p,%p\n",p1,p2);
printf("%d\n",sizeof(a));//arr作为数组类型使用
int (*p3)[5] = &arr;//arr作为数组类型使用,对arr取地址,得到的是一个指向数组的指针
printf("%p\n",p3);//值和&arr[0]相同,但类型是不一样的
return 0;
}
(2)在说明多维数组和指针之前,得先区分一下数组指针和数组指针。
顾名思义,数组指针是一个指向数组的指针,形式如 int (*p)[5],
而数组指针是一个数组元素为指针的数组,形式如 int int *p[5].
#includeint main(void)
{
int arr[3][3]={{1,2,3},{4,5,6},{7,8,9}};
int (*p1)[3]=arr;//数组指针
printf("%p\n",p1);
for(int (*row)[3] = arr;row< arr+3; ++row){
printf("%p:",row);
for(int *line = *row;line< *row + 3; ++line){
printf("%d\t",*line);
}
printf("\n");
}
p1 = &arr[0];//arr作为地址,类型和值都和&arr[0]相同
printf("%p\n",p1);
for(int (*row)[3] = &arr[0];row< &arr[3]; ++row){
printf("%p:",row);
for(int *line = &(*row)[0];line< &(*row)[3]; ++line){
printf("%d\t",*line);
}
printf("\n");
}
//以上两种方式直接通过指针变量的方式访问多维数组元素
for(int i = 0; i< 3; i++){
printf("%p:",&arr[i]);
for(int j = 0; j< 3; j++){
printf("%d%d%d\t",arr[i][j],*(arr[i]+j),*(*(arr+i)+j));
}
printf("\n");
}
//所以,arr+i 和 &arr[i],*(arr + i)和 arr[i] 是等价的
//*(arr + i) + j、arr[i] + j、 &arr[i][j]是等价的
//*(*(arr+i)+j) 、 *(arr[i] + j) 、 arr[i][j]是等价的
//或者说,arr[i][j]的下标写法只是对某个具体数数组元素的访问更方便,
//本质上编译器其实对数组下标的写法会自动变成地址操作
int arr1[3] = {1,2,3};
int arr2[3] = {4,5,6};
int arr3[3] = {7,8,9};
int *p2[3] = {arr1,arr2,arr3};//指针数组
printf("%p\n%p\n%p\n",p2[0],p2[1],p2[2]);
for(int i = 0; i<3 ;i++)
{
printf("%p:",*(p2 + i));
for(int *line = *(p2 + i); line< *(p2 + i) +3; ++line)
printf("%d\t",*line);
printf("\n");
}
return 0;
}
(3)多维数组的存储方式
c语言本质上其实只有一维数组,数组元素的访问本质都是数组元素地址的解引用
#includetypedef int (*ptr)[4];
int main(void)
{
int arr1[12] = {1,2,3,4,5,6,7,8,9,10,11,12};
int (*p1)[4] = NULL;
p1 = (ptr)arr1;//把 arr1类型强转,但其实不强转程序运行结果也是一样的,就是warning类型不一致
for(int i = 0;i< 3;i++){
for(int j = 0;j< 4;j++)
{
printf("%d,%d\t",*(*(p1+i)+j),p1[i][j]);
}
printf("\n");
}
//一、arr并不是二维数组,但是使用数组指针也能访问数组元素
//二、p1并不是二维数组,但是使用p[i][j]也能访问对应元素
int arr2[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}};
int *p2 = (int *)arr2;
for(int i = 0; i< 12; i++)
{
printf("%d,%d\t",p2[i],*(p2+i));
}
printf("\n");
return 0;
}
2、指针和函数(1)函数指针
函数的地址:如果在程序中定义了一个函数,那么在编译时,系统会为该函数代码分配存储空间,这段空间的首地址被称为函数的地址,而且函数名表示的就是该地址。因此我们可以定义一个指针变量来存放函数地址,这个指针变量就叫函数指针变量,简称函数指针。
#includeint max(int ,int );
int main(void)
{
int (*fp)(int,int) = &max;
int a = (*fp)(1,2);
printf("%d\n",a);
int b = fp(1,2);
printf("%d\n",b);
int (*fp2)(int,int) = max;
int c = (*fp2)(1,2);
printf("%d\n",c);
printf("%p,%p\n",max,&max);
printf("%ld,%ld\n",sizeof(max),sizeof(&max));
return 0;
}
int max(int x,int y){
return x>y?x:y;
}
在程序中定义了 int (*fp)(int,int),
含义是:定义了一个指针变量fp,该指针变量可以指向返回值为int型,且有两个整型参数的函数,
fp的类型为 int(*)(int,int).
对函数指针变量赋值后,就可以通过解引用的方式调用fp所指向的函数了。ANSI C标准运行我们将将(*fp)(1,2)简写成fp(1,2),但要明白这种写法只是一种简写形式。
另外,对指针变量进行赋值,可以直接使用函数名,也可以&函数名,因为地址值是相同的,怎么解释这个地址只看函数指针变量的类型。就像在上个部分数组与指针,把不同类型的地址赋值给数组指针,程序运行结果并不会有不同,只是编译会有警告提示。而在函数指针这里,并不会有警告。尽管max函数名是int(int,int)类型,而&max才是int(*)(int,int)类型。
(3)指针函数
指针函数就比较简单了,就是返回值是一个地址类型的函数
#includeint *query(int *arr,int n);
int main(void)
{
static int arr_score[10] = {87,89,85,76,65,70,72,85,97,99};
for(int i = 0 ;i< 10; i++)
printf("%d\t", *query(arr_score,i));
printf("\n");
int *(*fp)(int *arr, int n) = &query;
for(int i = 0 ;i< 10; i++)
printf("%d\t", *(*fp)(arr_score,i));
printf("\n");
return 0;
}
int *query(int *arr,int n){
return arr + n;
}
这里对 int *(*fp)(int *arr, int n) = &query 指针函数的指针的定义
和*(*fp)(arr_score,i) 该指针的调用和解引用做一下解释。
首先,()的优先级是最高的,所以(*fp)说明 fp是一个指针变量,然后前面的int *说明表示这个指针变量可以指向返回值为int *的函数;后面括号中的参数就应该不需要说了。
然后 *fp,首先fp是函数指针, *fp就是该指针所指向的函数,由于函数运算符()的优先级高于单目运算符*,所以先给函数传递函数参数,然后对结果进行* 解引用。
3、指针和字符串在c语言中,并没有字符串这个数据类型。通常借助于字符数组来存储字符串。而字符指针可以存储字符串的起始地址,并且和数组一样,字符数组名就代表了字符串的起始地址。这样,我们就可以用指针来处理字符串。
字符串或者说字符数组和数组在操作上有很多共同之处,但也自己的特殊性。
#includeint main(void)
{
char str[] = "hello,world!";
char *strp = "hello,world";
printf("%d\n",sizeof(str));
printf("%d\n",strlen(str));
int arr[] = {1,2,3,4,5};
printf("%d\n",sizeof(arr)/4);
return 0;
}
1、字符串数组默认以‘\0’作为字符串结束标志,所以会自动添加一个‘\0’字符在字符串末尾。
2、注意str 和 strp初始化的区别。详细可以参考(25条消息) C 内存管理(代码区、数据区、堆区、栈区)_熹微seesea的博客-博客
3、不能像对数组名取地址操作一样对字符数组名取地址。
#includevoid tocapital(char *str);
int main(void)
{
char str[] = "hello,world!";
printf("%s\n",str);
tocapital(str);
printf("%s\n",str);
return 0;
}
void tocapital(char *str){
while(*str != '\0'){
if(*str >= 'a' && *str<= 'z')
*str -= 32;
str ++;
}
}
4、数组作为函数参数在c语言中,我们无法将一个数组(包括字符数组)作为函数参数直接传递。如果我们使用数组名作为参数,那么数组名会被转换为指向该数组第一个元素的指针。
例如,在3中的程序使用printf("%s\n",str)和使用printf(”%s\n“,&str[0])完全等效。
在写自定义函数时,将数组作为函数参数毫无意义c语言中会自动地将作为参数的数组声明转换为相应的指针声明,比如使用void tocapital(char str[])和使用void tocapital(char *str)是相同的。
一个常见的例子就是函数main的第二个参数:
int main(int argc,char * argv[]){.....}和
int main(int argc,char **argv){......}是等价的
参考《c陷阱与缺陷》
5、野指针(25条消息) 野指针和常见的内存错误_熹微seesea的博客-博客
6、常量指针,指向常量的指针和指向常量的常量指针(1)int * const p;
p是一个常量类型的指针,一旦初始化就不能修改指针的值,但是这个指针所指向的地址上存储的值可以改变
(2)const int *p;
p是一个指向常量的指针,常量自然不能修改,但是可以改变指针变量的值
(3)const int *const p;
同时满足(1)和(2)的内容
你是否还在寻找稳定的海外服务器提供商?创新互联www.cdcxhl.cn海外机房具备T级流量清洗系统配攻击溯源,准确流量调度确保服务器高可用性,企业级服务器适合批量采购,新人活动首月15元起,快前往官网查看详情吧