C语言的数组 指针 sizeof 运算符的关系

地址只是一个点,没有长度的(默认长度是一个字节)

数组中几个关键符号(a  a[0]  &a  &a[0])的理解

(1)a 就是数组名。a做左值时表示整个数组的所有空间,又因为C语言规定数组操作时要独立单个操作,不能整体操作数组, 所以a不能做左值;

                                 a做右值表示数组首元素(数组的第一个元素,也就是a[0]的首地址)(首地址就是起始地址,就是4个字节中最开始第一个字节的地址).a做右值等同于&a[0];

(2)a[0]表示数组的首元素,也就是数组的第0个元素。做左值时表示数组第0个元素对应的内存空间连续(连续4字节);做右值时表示数组第0个元素的值(也就是数组第0个元素对应的内存空间中存储的那个数)

(3)&a就是数组名a取地址,字面意思来看就是数组的地址。&a不能做左值(&a实质是一个常量)

         &a做右值时表示整个数组的首地址

         总结

              1.&a和a做右值时区别:&a是整个数组的首地址,a表示首元素首地址。这2个在数组上是相等的,但是意义不相同,导致他们在参与运算时候有不同的表现。

              2.a和&a[0]做右值时意义和数值相同可以互相替代。

              3.&a是常量,不能做左值

              4.a做左值代表真个数组所有空间,不能做左值

数组和指针的天生姻缘

以指针的方式来访问数组元素

#include<stdio.h>

int main(void)

{
    int a[5] = {1,2,3,4,5};

    printf("a[3] = %d.\n,a[3]");
    
    printf("*(a+3) = %d.\n,*(a+3)");         

    return 0;

}

#include<stdio.h>

 int main(void)
 {
   int a[5] = {1,2,3,4,5};
   int *p;
   p = &a[2];
  
   printf("*(a+1) = %d.\n,*(a+1)");  
   
   return 0;          
}

(1)数组元素使用时不能整体访问,只能单个访问。访问的方式有2种:数组形式和指针形式。

(2)数组格式访问数组元素是:数组名[下标];(注意下标从0开始)

(3)指针格式访问数组元素是:*(指针+偏移量);

如果指针是数组首元素地址(a或者&a[0]),那么偏移量就是下标;指针也可以不是首元素地址,而是其他那个元素地址,这时候偏移量就要考虑叠加了。

(4)数组下标方式和指针方式均可以访问数组元素,两者的实质是一样的,编译器内部都是用指针方式来访问数组元素的,用指针方式访问数组才是本质

从内存角度理解指针访问数组的实质

指针和数组类型的匹配问题

(1)int *p; int a[5];  p = a;       //类型匹配

(2)int *p; int a[5];  p = &a;    //类型不匹配,p是int

* , &a是整个数组的指针,也就是一个数组指针类型,不是int指针类型,所以不匹配

(3)a和&a[0]是元素的指针,也就是int *类型;而&a是数组指针,是int(*)[5];类型。

总结:指针类型决定了指针如何参与运算

(1)指针参与运算时,因为指针变量本身存储的数值是表示地址的,所以运算也是地址运算。

(2)指针参与运算的特点是,指针变量+1,并不是真的加1,而是加1*sizeof(指针类型);如果是int *指针,则+1就实际表示地址+4,如果是char * 指针,则+1就表示地址+1;如果是double* 指针,则+1就表示地址+8.

(3)指针变量+1时实际不是加1而是加1*sizeof(指针类型),主要是因为是希望指针+1后刚好指向下一个元素(而不希望错位)。

指针与强制类型转换

变量的数据类型的含义

(1)所有的类型的数据存储在内存中,都是按照二进制格式存储的。所以内存中只知道有0和1,不知道是int的,还是float的还是其他的类型数据

(2)int  char  short等属于整型,他们的存储方式(数转换成二进制往内存中放的方式)是相同的,只是内存格子大小不同(所以这几种整型就彼此叫二进制兼容格式);而float和double的存储方式彼此不同,和整型更不同。

(3)存进去时是按照这个变量本身的数据类型存储的,但是取出时是按照printf中%d之类的格式化字符串的格式来提取的。此时虽然a所代表的内存空间中的10101序列并没有变(内存是没被修改的)但是怎么理解(怎么把这些1010转成数字就变了)

总结:C语言中的数据类型的本质,就是决定了这个数在内存中怎么存储的问题,也就是决定了怎么解析。所以要求我们平时数据类型不能胡乱瞎搞。

分析几个题目:

* 按照int类型存却按照float类型取     一定会出错

* 按照int类型存却按照char类型取     可能会出错

* 按照short类型存却按照int类型取     可能会出错

* 按照float类型存却按照double类型取     一定会出错

指针的数据类型的含义

(1)指针的本质是变量

(2)一个指针涉及2个变量:一个指针变量自己本身,一个是指针变量指向的变量

(3) int *p:定义指针变量时,p(指针变量本身)是int *类型,*p(指针指向的变量)int类型

(4)int * 类型就是指针类型,只要是指针类型就是占4个字节,解析方式按照地址方式解析的(不管是int * 还是char*还是double*)的解析方式是相同的,都是地址。

(5)对于指针所指向的那个变量,指针的类型就很重要,指针所指向的那个变量的类型(他所对应的内存空间的解析方法)要取决于指针类型。譬如指针是int *的,那么指针指向的变量就是int 类型。

指针数据类型转换实例分析1

(1)int 和 char 类型是整型,类型兼容的。所以互转的时候有错有对。

(2)int 和 char 的不同在于chai只有一个字节而int有4个字节,所以int的范围比char大。在char所表示的范围之内int和char是可以转换不会出错;但是超过了char的范围后char转成int不会错,而int到char会错(大到小会错)

指针数据类型转换实例分析2

sizeof运算符

(1)sizeof是C语言的一个运算符(主要sizeof不是函数,虽然用法很像函数),sizeof的作用是用来返回()里面的变量或者数据类型占用的内存字节数。

(2)sizeof存在的加值?主要是因为在不同平台下各种数据类型所占的内存字节数不尽相同,所以程序中需要使用sizeof判断当前变量在当前环境下占几个字节。

#include <stdio.h>                                     
#include <string.h>
int main(void)
{

    char str[] = "hello";
    printf("sizeof(str) = %d.\n",sizeof(str));                  //字符串会以\0做结束标志
    printf("sizeof(str[0]) = %d.\n",sizeof(str[0]));
    printf("strlen(str) = %d.\n",strlen(str));                  //srtlen 用来得到一个字符     
                                                                  串的长度但是不算\0           
    return 0;
}


#include <stdio.h>                                     
#include <string.h>            //32位系统中所有类型指针的长度都是4
int main(void)
{

    char str[] = "hello";
    char *p = str;
    printf("sizeof(p) = %d.\n",sizeof(p));                   //4相当于sizeof(char *)          
    printf("sizeof(*p) = %d.\n",sizeof(*p));                 //1相当于sizeof(char)
    printf("strlen(p) = %d.\n",strlen(p));                   //5相当于strlen(str) 
                                                                          
    return 0;
}


#include <stdio.h>           

int main(void)
{

    int b[100] = 10;
    printf("sizeof(b) = %d.\n",sizeof(b));
    
    return 0;
}
//size(数组名)的时候,数组名不做左值也不做右值,纯粹就是数组名的含义。那么sizeof(数组名)实际返回的是整个数组所占用内存空间(以字节为单位的)


(1)函数传参,形参是可以用数组的
(2)函数形参是数组时,实际传递不是整个数组,而是数组的首元素首地址。也就是数函数传参用数组来传,实际相当于传递的是指针(指针指向数组首元素首地址)。


#include <stdio.h>                                     
#include <string.h>

#define dpChar char *
typedef char * tpChar;     //重命名类型 换名字
int main(void)
{

    dpChar p1, p2;           //char *p1,    char  p2
    tpChar p3, p4;           //char *p3,    char *p4
    printf("sizeof(p1) = %d.\n",sizeof(p1));
    printf("sizeof(p2) = %d.\n",sizeof(p2));
    printf("sizeof(p3) = %d.\n",sizeof(p3));
    printf("sizeof(p4) = %d.\n",sizeof(p4));
}

指针和函数传参

普通变量作为函数传参

#include<stdio.h>

//&a和&b不同,说明a和b不是同一个变量(在内存中a和b是独立的2个内存空间)
//但是a和b是有关联的,实际上b是a赋值得到的。

void func1(int b)
{

    //在函数内部,形参b的值等于实参a
    printf("b = %d.\n",b);
    printf("in func1,&b = %p.\n",&b);

}


int main(void)
{

    int a = 4;
    printf("&a = %p.\n",&a);
    func1(a);

    return 0;
}

(1)函数传参时,普通变量作为参数时,形参和实参名字可以相同也可以不同,实际上都是用实参来代替对应的形参)

(2)在子函数内部,形参的值等于实参。原因是函数调用时把实参的值赋值给了形参。

(3)这就是很多书上写到“传值调用”。

数组作为函数形参

(1)函数名作为函数形参时,实际传递不是整个数组,而是数组的首元素的首地址(也就是整个数组的首地址。因为传参时是传值,所以这2个没区别)。所以在子函数内部,传进来的数组名就等于是一个指向数组首元素首地址的指针。所以sizeof得到的是4。

(2)在子函数内传参得到的数组首元素首地址,和外面得到的数组首元素首地址的值是相同的。很多人把这种特性叫做“传址调用”(所谓的传址调用就是调用子函数时传了地址(也就是指针),此时可以通过传进去地址来访问实参。)

(3)数组作为函数形参时,【】里的数字是可有可无的。为什么?因为数组名做形参传递的实际只是个指针,根本没有数组长度信息。

指针作为函数形参

(1)只有一句话:和数组作为函数形参是一样的。

结构体变量作为形参

#include<stdio.h>

struct A
{
    char a;
    int  b;
};

void fun4(struct A a1)
{
    printf("sizeof(a1) = %d.\n",sizeof(a1));
    printf("&a1 = %p.\n", &a1);
    printf("a1.b = %d.\n",a1);

}

int main(void)
{

    struct A a = 
    {   .a = 4,            //结构体赋值 加, (逗号不是分号)
        .b = 5555,   
    };    
    printf("sizeof(a) = %d.\n",sizeof(a));
    printf("&a = %p.\n", &a);
    printf("a.b = %d.\n",a);
    func4(a);

    return 0;
}

(1)结构体变量作为函数形参的时候,实际上和我们普通变量一模一样,所以结构体变量其实也是普通变量而已

(2)因为结构体一般都很大,所以如果直接用结构体变量进行传参,那么函数调用效率就会降低,因此可以通关改传变量的指针进去。

传址调用与传值调用

#include<stdio.h>

void swap1(int a,int b)
{
    int tmp;
    tmp = a;
    a = b;
    b = tmp;
    printf("a = %d,b = %d.\n",a,b);
   
}

void swap2(int *a,int *b)
{
    int tmp;
    tmp = *a;
    *a = *b;
    *b = tmp;
    printf("*a = %d,*b = %d.\n",*a,*b);
   
}

int main(void)
{
    int x = 3,y = 5;
    swap1(x,y);
    printf("x = %d,y = %d",x,y);        //x = 5,y = 3  交换成功


    swap2(&x,&y);
    printf("x = %d,y = %d",x,y);        //x = 3,y = 5  没有交换

}

(1)传值调用描述的是这样一种现象:x和y作为实参,自己并没有真身进入wsap1函数内部,而只是拷贝了一份自己的副本进入子函数swap1,然后我们在子函数swap1中交换的实际是副本而不是x,y真生。所以在swap1内部确实是交换了,但外部的x和y根本没有受到影响。

(2)在swap2中x和y真的被改变了。但是x,y真身没有进入,单swap2我们把x和y的地址传到子函数了,于是在子函数可以通过指针解引方式从函数内部访问到外部的x和y真身,从而改变x和y。

发布了14 篇原创文章 · 获赞 0 · 访问量 417

猜你喜欢

转载自blog.csdn.net/LIGUOZHENLX/article/details/104161007