C语言学习笔记6——指针

本文介绍了C语言中指针的概念,包括指针变量的定义、空指针与野指针的区别,以及数组与指针的关系。指针变量存储的是地址值,空指针为0地址,野指针未初始化。文章还讨论了不同类型的指针在内存中占用相同字节数,但使用时需匹配数据类型,避免数据错误。此外,详细阐述了数组名的特性,二维数组的表示方法,以及指针数组和数组指针的差异。最后提到了const关键字在指针中的应用,区分了指针常量和常量指针。
摘要由CSDN通过智能技术生成

1.int* p = &i; 不要理解为int (*p) = &i。(*p)不是合法的变量名。变量定义是:

[存储类型] 数据类型 变量名 = 值;

对等过来:(int*)  p = &i; int*是数据类型,p是变量名,&i是值。

2.机器字长确定后,所有类型的指针在内存中占的字节都是一样的,不管是 *基本类型、*构造体、*void、多级指针,32位的是占4B,64位占8B。

虽然不同类型指针所占内存大小一样,但是在定义指针变量时要和被指对象的类型一致,因为指针会根据指针地址(指针地址只是起始地址。在计算机中好像都是这种套路,一个完整的东西都是由起始位置+所占长度共同决定)+偏移量去取数据。如果指针类型和被指对象类型不一致,导致读出的数据值会有误。如果用char* p 指向int型数据,那么只会取到int数据的一个字节,而int由内存中的4个字节构成。

3.空指针和野指针

1)空指针:指向地址为0的指针

int* p = NULL;//NULL=0,此处编译器将其作为地址考虑即是0x0000000000000000(64位)这个地址,是内存的开始,这个地址没有分配给任何进程,因此在使用p的时候会报段错误。

2)野指针:没有初始化就使用的指针。

由于int*的存储类型是auto,因此p的值是个随机值,即指向任意地址,会报段错误

int* p; 
printf("%p --> %d\n", pc, *pc);

4.指针万变不离其宗的一点:指针(也叫做指针变量名,也是变量里的值)里放的是地址值(空指针放的0x0内存开始的地址),对任意指针进行&操作都是获取该指针的地址值。

当把某个地址赋给一个指针时,该指针就指向了该对象,但是也仅仅是知道了是哪个对象,具体要拿到对象里的值/东西,还要进行* 操作。 *就是取出该指针所指对象里存放的值。

有点类似快递点:取&操作就是把快递取件码报给你告诉你位置,*操作就是你根据取件码取件。一级指针即是二级指针的快递,又是直接访问对象的取件码。

i是直接访问对象,p为一级指针,q为二级指针,则有:

p = *q = &i

**q是直接访问i值

5.指针是变量有取地址&、取值*,赋值左值、关系运算(>  <  ≥  ≤)、自增自减,后两类都是针对占一段连续内存的某物(比如同一数组)来说的(两个不同东西单纯比较内存地址大小意义不大),数据结构里只有数组是在内存上连续存储的。

6.数组名a和指针p的区别,数组名是地址常量无自增自减和赋值左值但可以有a+x也可以有*(a+x),这些运算指针都支持。那么for循环中控制次数获取一维数组大小的方式可以也做sizeof(a)/sizeof(*a),a代表整个数组大小,*a = *(a+0) = a[0]。二维数组是sizeof(a)/sizeof(**a),但二维数组一般用两层for循环控制输出。

7.左值必须是个变量不可为常量。编译报错说左值应该就是把常量放等号左边了

8.如果把一维数组名赋给了指针,那么a[i]也可以表示为p[i]

9.直接把一个数组赋给指针:需要个强转,一维数组可以不指定元素个数,二维数组必须指定列数

 int* p = (int[][2]){1,2,3,4,5,6};
        int i = 0;
        for(i = 0; i<6; ++i)
        {
                printf("%p-->%d\n", p+i, *(p+i));
        }

0x7ffe204a5a30-->1
0x7ffe204a5a34-->2
0x7ffe204a5a38-->3
0x7ffe204a5a3c-->4
0x7ffe204a5a40-->5
0x7ffe204a5a44-->6

10.二维数组用数组名表示:

a[i][j] = *(*(a+i)+j);

*(*(a+i)+j)理解:a+i表行指针,*(a+i)+j表列指针

由于a为二维数组,所以数组名a(是a+0简写)也是第一行的行指针,a+i是第i+1行的行指针,

*(a+i)是将第i+1行的行指针转换为该列的第一个元素的指针,其实是*(a+i)+0的缩写(虽然+0可写可不写,+0表改行第一列元素的指针,所以最好不省略+0 以帮助理解),但因为每一行第一列的指针即是改行的行指针也是该该行第一列元素的指针,所以

a+i 、*(a+i) +0、  *(a+i)三者%p的地址值相同


         int a[2][3] = {1,2,3,4,5,6};
        int i= 0;
        for(i = 0; i < sizeof(a)/sizeof(**a); ++i)
                printf("a[%d][%d]%p->%d\n", i/3, i%3, &a[0][0]+i,*(&a[0][0]+i));
        printf("a+1=%p\n        \
                *(a+1)=%p\n     \
                *(a+1)+1=%p\n   \
                *((a+1)+1)=%d\n \
                *(*(a+1)+1)=%d\n"
                , a+1, *(a+1), *(a+1)+1, *((a+1)+1), *(*(a+1)+1));



a[0][0]0x7ffe8939d350->1
a[0][1]0x7ffe8939d354->2
a[0][2]0x7ffe8939d358->3
a[1][0]0x7ffe8939d35c->4
a[1][1]0x7ffe8939d360->5
a[1][2]0x7ffe8939d364->6

a+1=0x7ffe8939d35c    //第二行行地址
*(a+1)=0x7ffe8939d35c    //第二行第一列元素地址
*(a+1)+1=0x7ffe8939d360    //第二行第二列元素地址
*((a+1)+1)=-1992699032    //*(a+2)第三行行地址,越界
*(*(a+1)+1)=5            //第二行第二列元素值

a[0]=0x7ffe8939d350
a[0]+1=0x7ffe8939d354
a[1]=(nil)

*note:

 1)数组名一个符号两种含义,是没有+0导致的

               一维数组数组名是整个一维数组的指针/地址,也是第一个数组元素的指针;

                二维数组名即是整个二维数组的指针也是第一行行指针(a+0简写为a),但不能理解

                为第一行第一列的元素的指针( 需要*写为*(a+0)+0 )

2)二维数组,没有*的是行地址,1个*是列地址,2个**是元素值

3)*和[ ]可以转换,p+i是以int在内存上移动:

int* p = &a[0][0];
*(p+i)= p[i];

4)换行符 \ 是在字符串中使用的,在printf的“”中换行使用到的 

11.数组指针和指针数组:

1)数组指针:

int (*q)[3] 按照  数据类型 变量名 = 值;等价于  int[3] *q;数据类型是3个int构造体,q每次移动都是3个int的构造体(int *p,p每次移动是一个int基本类型数据)。很容易想到q和二维数组的行指针很相似(区别是一个是指针变量,一个地址常量)。由于二维数组名不带*的是行指针,因此可以将a赋值给q;p移动的单位是1个int类似于列指针,而数组名带一个*表示列指针,因此可以将*a赋予p. 

int a[2][3] = {1,2,3,4,5,6};
int* p = *a;//列地址赋给p, 此外还可以 int* p = *(a+1);
int (*q)[3] = a;

2)指针数组:

int* p[3];

char* name[5] = {"the", "wonderful", "amazing", "great", "world!"};

eg。指针数组与对字符串而非整数进行选择排序

        //数组指针和指针数组、选择排序
        #if 1
        char* name[5] = {"the", "wonderful", "amazing", "great", "world!"};
        int i=0, j=0, k= 0;
        char* temp = NULL;

        for(i=0; i < 5-1; ++i)
        {
                k = i;
                for(j=i+1; j<5;j++)
                {
                        if(strcmp(name[j], name[k]) < 0)
                        {
                                temp = name[k];  //该交换只是交换记录值,而不是元素位置交换
                                name[k] = name[j];
                                name[j] = temp;
                        }
                        if(k != i)//前面只是笔记找到本次的最小值,本步骤是放置到指定位置
                        {
                                name[i] = name[k];
                        }
                }
        }
        for(i = 0; i<5; ++i)
        {
                printf("name[%d]:%s\n", i, name[i]);
        }

*Note:数组指针是1个指针指向一个行数组,指针数组是多个同类型的列指针组成一个数组

12.字符指针与字符数组

char *str = "hello";
char strArr[] = "hello";

###strArr是地址常量:

1)不能用 strArr = “world”直接赋值方式改变其值,(地址)常量不可放到等号左边;

2)可以用strcpy(strArr, "world");实现:strArr是一段连续内存地址,可以用world覆盖。

3)sizeof(strArr)=包含尾零和字符个数,strlen=字符个数

4)字符数组还有一种字符初始化方式,char strArr[ ] = {'h', 'e', 'y'};这种方式中没有尾零的情况 

###str是个指针变量:

1)可以用 str = “world”直接赋值方式改变其值,即str指向world的地址,因为赋值world是把world首地址赋值过去;

2)不可以用strcpy(str, "world");实现(会出现段错误),因为str指针没有变,依然指向hello常量,试图用world覆盖字符串常量,常量区的数据使用上不可修改或者覆盖,因此会报段错误。

3)sizeof(str)=8(别掉坑里了,这是获取指针的大小,64位所有指针大小都是8B),strlen(str)=字符个数

4)str是字符指针,但是给str赋值时不能用

        *str = "hello"; 而是使用 str = “hello”;这是因为字符串常量赋值时是负的他在静态区里的地址。

5)同样判断一个字符指针的字符串是否为空是用

        if(str == NULL) 而非 if(*str == NULL)

因为NULL虽然是个空字符串,比较的也是空字符串所在地址(空串放置在0x0地址上?),既然是地址就只能指针变量,不能使用指针取值了。

6)注意str = "world";和strcat/strcpy(str,"world")的区别,前者是指针指向的地址改变,后者是往字符串常量区(静态区)覆盖写入,所以前者可以,后者err

*Note:字符串常量只以地址(在静态区)示人。

*Note:出现段错误相对来说是比较好的,因为故障立马就显示出来了,而不是后面留坑。

*Note:在被调函数里返回字符串常量相关时注意,被调函数里要用字符指针不要用字符数组,因为函数返回值是个地址,但在主调函数接收到该返回值地址时,字符数组是局部变量已经被释放了再去访问是有问题的;而字符指针因为得到的是静态区的地址,因此主调函数拿到时该地址时数据依然存在;如果被调函数里一定要用字符数组,一定要用静态的static存储,这样也能保证被调函数执行结束内存没有回收。举个例子:

#include <stdio.h>
//返回的是局部变量的地址,该地址位于动态数据区,栈里
char *s1()
{
char* p1 = "qqq";//为了测试‘char p[]="Hello world!"’中的字符串在静态存储区是否也有一份拷贝
char p[]="Hello world!";
char* p2 = "w";//为了测试‘char p[]="Hello world!"’中的字符串在静态存储区是否也有一份拷贝
printf("in s1 p=%p\n", p);
printf("in s1 p1=%p\n", p1);
printf("in s1: string's address: %p\n", &("Hello world!"));
printf("in s1 p2=%p\n", p2);
return p;
}
 
//返回的是字符串常量的地址,该地址位于静态数据区
char *s2()
{
char *q="Hello world!";
printf("in s2 q=%p\n", q);
printf("in s2: string's address: %p\n", &("Hello world!"));
return q;
}
 
//返回的是静态局部变量的地址,该地址位于静态数据区
char *s3()
{
static char r[]="Hello world!";
printf("in s3 r=%p\n", r);
printf("in s3: string's address: %p\n", &("Hello world!"));
return r;
}
 
int main()
{
char *t1, *t2, *t3;
t1=s1();
t2=s2();
t3=s3();
 
printf("in main:");
printf("p=%p, q=%p, r=%p\n", t1, t2, t3);
 
printf("%s\n", t1);
printf("%s\n", t2);
printf("%s\n", t3);
 
return 0;
}

结果:
in s1 p=0013FF0C
in s1 p1=00431084
in s1: string's address: 00431074
in s1 p2=00431070
in s2 q=00431074
in s2: string's address: 00431074
in s3 r=00434DC0
in s3: string's address: 00431074
in main:p=0013FF0C, q=00431074, r=00434DC0
$
Hello world!
Hello world!
 

13.const和指针:const只是通过变量名来指定常量

1)const和宏:

生效时间不同,一个预处理,一个编译运行;可靠性:宏不检查定义合法性,变量定义会检查;宏不分配内存,const分配内存;

2)const和*位置而非和int决定是指针常量(const在*后)还是常量指针(const在*前),*前和*后都有const,则指针和指针对象都不可改变 const int * const p = &i;

对于常量指针 const int* p 可以认为是const 修饰 (int *p),那么所指向对象是常量,可通过改变指针的指向来改变p的对象,但不能通过*p赋值来改变,那是不是通过 const int * const p = &pi这种方式就把p和pi完全绑定了?;


        const float pi = 3.14;    //想让pi是常量
        const float* p = &pi;    //指向pi的指针也要是const,这样防止通过指针赋值改变pi值
//      *p = 3.1415;//语法错误
        printf("*p=%f\n", *p);
        float j =2;
        p = &j;                   //通过这种方式只能改指针对象,但改不了pi的值

        printf("*p=%f\n", *p);

运行结果:
*p=3.140000
*p=2.000000

int* const p 可认为是const修饰p,那么指针是个常量,只能固定指向同一个地址。

        int i = 1;
        int j = 100;
        int* const p = &i;
        printf("*p=%d\n", *p);

//      p = &j;    //p为cosnt不可变

        printf("*p=%d\n", *p);

运行结果:*p=1

*note:

cosnt常量用常量化的指针来指向;

const变量定义时必须初始化;

封装接口时,如果对方调用接口希望传入的值不被修改,则该传参要用const修饰

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值