C语言指针,超详细,分章节


什么是指针:

        指针就是内存里的地址,也就是内存单元的编号。

        一个字节一个地址

        变量创建的本质是向内存申请一块空间。 

整形指针;

         a 为整形变量(一个整形变量占4个字节,一个字节一个地址,他们之间差1), 取地址a,拿到的就是a较小的那个地址,为什么里面是19呢?因为19是十六进制数,转成十进制就是25了。

 指针变量:

什么叫指针变量?用来存放指针(也就是地址)的变量叫指针变量。

int  *p ,*p表示指针变量,int 表示类型即这个指针变量的类型。

       

指针变量的类型

        p 是变量名,int * 是变量类型(也叫整形指针类型),扩展到数组,应该是除了变量名剩下的就是类型

 解引用操作符:

“ * ” 就是解引用操作符, 这里解引用指针变量 p 拿到的就是 p 里面存放的地址的使用权(也就是变量 a 的地址)所以 *p 就等价于变量a可以直接当作变量a使用。

相当于给 a 赋值为100,间接的修改了 a 的值。

指针变量的大小:

根据计算的的位数决定的,32位时一个指针变量的大小就是 4个字节,64位环境时就是8个字节,这个大小与指针变量类型无关。

为什么指针变量一样大

        因为指针变量里面放的是地址,而地址的长度是根据环境决定的,所以他们是一样大的。下面是x86方便一点,x64太长了。

 int 和 char 类型的指针变量大小都是8个字节。

最好使用%zd打印打印sizeof的返回值 。规定sizeof的返回类型是size_t,这个类型的打印就是%zd

既然指针变量大小都一样为什么还要区分呢?

        原因出在解引用上,指针的类型决定了解引用时能操作的大小,如下图,当int 类型的指针放在一个chat类型的指针变量里面时,放倒是能放的进,但在解引用重新赋值时却只能修改一个字节的大小。char 类型的指针只能访问一个字节。

指针变量的加减运算:

 由图可以看出,什么类型的指针加减运算时,就会跳过相应类型大小的地址。

pa + 1 , pa 为整形指针,加一,一次就跳过4个地址,加二就逃过8个地址 ,加一vs里面就往高地址跳,减一就往低地址跳

pc + 1,一次跳过一个地址,因为pc是字符类型指针(对应了类型所占的字节大小,一个字节一个地址嘛)。

任意类型指针(void类型指针):

        void* ,这个玩意儿就是任意类型指针,没有具体类型,也就是什么类型的地址都可以放。

对void类型的指针,不能进行指针运算。也不能解引用。

所以想用的时候(函数的传参调用),在用的时候还得强制类型转换。

        void * 类型的指针主要用于函数接收指针参数时,使其可以接收任意类型的指针当你需要的时候强转为你要的类型就行。

const修饰指针变量: 

        const 修饰变量时,变量不可被被修改(仅仅是不能被修改没有其他意思)但在c++中就是常量了。

     但可以用指针来修改。

理解int* p =&a;

     

int a = 0;
int* p = &a;

          1、 p 是指针变量,这个变量里面存放了其他变量的地址。

          2、*p 为解引用,使用存放的地址。

          3、p 是指针变量,说明 p 也会在内存里申请一块空间,也有自己的地址,也能放入另一个指针变量里面

const 放在 *p 左边

        const 放在 int *p左边,此时const 控制的是 *p*p 就等于 a 没有 a 地址的使用权)。也就是说不能修改 a 里面的值了。

 但是可以给 p(指针变量)里面重新放另外一个变量的地址

  在重新放了一个地址后,*p 照样不能使用。

 const 放在 * 号的右边                 

        此时 const  修饰的是指针变量  p ,也就是说指针变量  ( p 的使用权被锁定)里面不能重新放入另一个地址了。但是 * p可以使用,也就是咱们可以使用 a 的地址。 

指针运算:

        引言:数组在内存中是连续存放的,随着下标的增长,地址由低到高增长。说明了一件事(数组里面一个元素一个地址)。

 指针与数组之间的加运算。

        一种计算方式:其实就一种两种写法而已,

int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9 };
	int* p = &arr[0];
	int ret = sizeof(arr) / sizeof(arr[9]);
	int i = 0;
	for (i; i<ret;i++)
	{
		//printf("%d ", *p);
		printf("%d ", *(p + i));
		//p++;
	}


	return 0;
}

        第一张图,*(p+i) 就是,解引用p+0,p+1 ,p+2......  (加一跳一个,加2就跳俩)

        用自增来写的话,会改变p的起始位置。

指针减指针;

        只有指针减指针,没有加指针。

        相减的前提:两个指针得指向同一块空间。

     指针减指针得到的是两个指针之间的元素个数。下面的是高地址减去低地址,得到9,如果反过来,那么就是 -9.

野指针:

        未被初始化的指针变量,叫野指针,就会出错。

 越界访问也会导致野指针的产生

 函数被销毁时也会导致野指针

空指针:

        下图就是一个空指针,本质是赋值了一个为0的地址。没有指向任何地方,也就是说不能解引用

        避免返回局部变量地址,因为局部变量在出了作用域以外就会被销毁,特别是函数里面的局部变量。

assert断言 

使用时请包含头文件,assert.h

        作用就是判断后面的表达式,为真:程序正常运行,为假:则终止运行并给出报错信息提示。

        此处的目的是,在使用完指针变量后判断,如果为空指针则停止运行

 当不需要断言时,可在assert.h头文件之前定义一个宏 NDEBUG

#difine  NDEBUG,如下图

在VS环境中,release版中会自动优化掉assert.

什么情况必须使用指针:

看一下下图为何出错

         首先,在传参时,形参和实参的地址不一样,x 和 y (都有自己的空间)其实相当于复制了 a 和 b 里面的值,然后借助 z 交换了 x y 的值,完了之后函数销毁 x y 的空间也回收了,所以并没有修改 a b 里面的值,a b 根本就都没有动过。

        如果下图不懂请看目录里的的对应部分(解引用操作符)。

指针和数组

数组名

        数组名的地址,和数组首元素的地址相同

取地址数组名的两个例外:

  1. sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小,单位是字节。        
  2. &(数组名),也表示的是整个数组,取出的是整个数组的地址,然后你就会发现也是一样的

        差别在,&arr+1,跳过的是 40 个字节,那两个跳过的是 4 个字节也即是一个元素,因为数组里面有 40 个元素,一个占 4 个字节,为什么是68而不是80呢?因为这是十六进制显示的,所以要转一下。

除此之外,都表示数组首元素的地址

一维数组引用的几个相等 

下列四个作用相等,主要是 p[i] == arr[i]

 上图的一些等式成立的前提是 p 里边放的arr的地址。不对的话肯定不对啦,听君一席话如听君一席话

解析上面的六个等式

        1、arr [ i ] 访问下标为i的元素,*(arr+i)得到 arr + i 地址里面的值,前者的本质就是后者

        2、*(p+i)指向 p 里面的地址,随着 i 的变化得到对应地址里面的值,

        3、p [ i ]就等于 arr [ i ] , *( i + arr ) 本质是地址的变化然后解引用,得到地址里面放的东西,

        4、i [arr] 本质是*(i+arr)(不要纠结基本不用)

一维数组传参的本质

        int arr[ ] 本质还是指针,写成数组方便理解。

        arr没有给出具体大小的话传递就是首元素地址

        传递一维数组首元素地址就相当于传递了一个 int 的地址,所以接收的时候用整指针接收就行

        如果传递的是二维数组的的数组名,就相当于传递了一个一维数组的地址,接收类型应该是 数组指针,int (*p) [10]

二级指针 

        首先先了解一下一级指针,一级指针就是存放了一个地址的指针变量,既然是变量,说明他也会想内存申请一块空间,他也有自己地址(用来存放某个地址),说到这儿想必大家都已经明白了二级指针,二级指针就是存放一级指针的地址的。

        二级指针写法就是 int * * pp,int只是类型可以变的,pp是变量名也可以变的。只是这里叫这个名字而已。

两个 * * 的理解 

       *p 解引用指针变量pp得到的是 p变量使用权可以修改p里面的地址

        *pp 解引用 p 里面的地址,得到使用 a 的权利,拿到的是 a 的地址,解引用就能使用,

指针数组:

概念:存放指针的数组

        类型是int*   [5],arr是数组名

 指针模拟实现二维数组:

        创建几个一维数组,然后把数组名放入指针数组中

         然后借用下标引用符实现调用

 也可以解引用使用

int arr1[] = { 1,2,3,4,5 };
int arr2[] = {6,7,8,9,10};
int arr3[] = { 11,12,13,14,15};
int* arr[] = { arr1,arr2,arr3 };//arr123实际为地址
printf("%d\n", **(arr+1) + 2);//*(arr+1)为arr2的地址,然后再次解引用 *(arr2+2)==arr2[2]为8,

 指针变量

字符指针变量:

 char*p = "hello world" , ”hello world “相当于一个字符数组。

        而 p 里面放的就是首元素地址,printf ( "%c \n" , *p); 打印出来的就是 h

 但是*p 不能重新修改(赋值),因为*p指向的对象直接就是一个常量,而常量并不能被修改。

        字符串常量不能被修改,地址指向的并不是变量所以不能被修改。

番外;在字符指针中,strlen可以直接访问整个数组。遇见\0停止

           %S 打印字符串遇见 \0 停止。

 数组指针变量;

数组指针变量——指向的对象是数组,存放的是数组的地址

这就是一个数组指针,&arr 取出来的是整个数组的地址,

(*p)说明这是一个指针变量,他的类型是 int [10]  说明这个是一个数组指针,数组有10个元素每个元素是int类型(所以即使是接收一维数组,他的类型也和一个变量的类型一样)

感觉到有点像把数组里面的每一个地址放进一个变量里,而这个变量的类型由数组的类型和数的大小(或者元素的个数决定的)

如何访问一维数组指针变量里的元素?

  如下图:进行分析

        首先 p里边放的是整个数组的地址,所以我们直接解引用p得到的还是整个数组的地址;并不是首元素地址

\

然后我们再次解引用就会发现可以得到首元素地址,而对首元素的+i就能访问数组里的元素了

       

一维数组访问的几种写法,

      

          

二维数组

二维数组的传参本质

        什么是二维数组,把一维数组当作数组元素的就是二维数组,所以二维数组名在传参时传的就是第一行的地址(整个第一行)。

所以在函数接收时,应该使用数组指针变量来接受,在创建二维数组指针变量时应该如下图创建

         在使用时得理解一个东西就是,一定得先解引用再下标访问,先解引用才能使用里面的一维数组,所以就直接是 *p 就是第一行(首元素地址,首元素就是一个一维数组),如果想第二行那么应该 (*p+1)[ ? ] 用括号括起来(那个1可以用变量代替),然后下标访问

写成这样也可以,相信各位看到这儿的都应该看得懂,我就不过多解释了。

二维数组的解引用理解 

第二个这个图应该是被我不小心删了。看不懂直接跳过。

        p+1对应第二行地址,对其解引用拿到第二行的地址,差不多就是这么个意思arr[ 1 ][ 4 ],但其实是地址哦,这里其实就相当于一个一维数组了,然后加+1能得到一维数组的第二个地址,对其进行解引用就能得到这个地址里面的值。

理解了第二个第三个就简单了多了把吧

        解引用得到第二行地址,然后下标访问,前面目录里有个一维数组的几个相等可以参考。arr[ i ] ==*(arr+i)

二维数组的访问写法

 看到这儿了,建议各位去试一试一定要动手实操一下,有什么想法也可以评论区交流。不想的也可以自己在自己的环境里面测试。

补充:二维数组地址的连续存放

我也是看见下面这个代码才学到, 咱们同样访问 8还可以怎么写,主要是我在想他怎么去第二行的。

接下来就和大家分析一下,一个关键就是就算是二维数组的地址他也是连续存放的,

        下面如有不懂,请看目录中,sizeof与数组之间的爱恨情仇。

        分析:*(*arr+5)*arr 的到的就是第一行的使用权,同时也代表了第一行第一个元素的地址,所以才能实现访问。

这里我就是从第二行的第一个元素开始的。

咱们可以发现,二维数组通过指针的调用,和一维数组一样,本身也是一堆地址。还是连续存放的当然可以直接通过首元素地址的加法访问。

函数指针变量

取地址函数名,和函数直接拿来用的地址是一样的,void 类型的函数也有地址。

函数指针的写法

 类比数组的写法

        分析:首先确定是一个指针变量——(*pf).函数——用()表示,然后看函数里面的形参写上对应的类型(int,int ), 返回的参数类型是 int 就在前面添上 Int 就行。

最后的样子就是,int (*pf) (int,int).

函数指针的类型

 int (* ) (形参类型) ,这个 int 是跟据函数的返回类型决定的。

如果是 void 前面就写 void

 下面是二维数组的类型作为参数的类型,就和之前上面说的一样,写一个一维数组类型就行。

 函数指针变量的使用

        *pf1 解引用变量名得到函数(不解引用直接使用指针变量也可以),()函数的调用操作符,里面给了两个参数,代码是欧克的结果就是10,(*pf1)(实参)不一定要传参,即可实现函数的调用。

        (*pf1)这个  *  可以不写,外面的括号也可以不写(同时存在,写*就要括号,不写就不用)

写*

void test(int* p[3])
{
	printf("ok\n");
}
int main()
{
	int arr[][3] = {1,2,3,4,5,};
	
	test(arr);
	void (*p)(int* p[3]) = test;
	(*p)(arr);


	return 0;
}

不写,

括号与*共存亡

但后面一定要跟括号与形参(可以为空),ni'dei

void test(int* p[3])
{
	printf("ok\n");
}
int main()
{
	int arr[][3] = {1,2,3,4,5,};
	
	test(arr);
	void (*p)(int* p[3]) = test;
	p(arr);


	return 0;
}

 函数指针变量,可以不需要解引用,直接变量名加括号(),括号里面给上参数就能实现调用

 typedef关键字

        用途:重命名复杂类型(简单的也可以只要你不嫌麻烦)。主要用于结构体重命名,结构体类型加变量名字很长,所以可以简化一下

 对指针类型的重命名;

操作时 typedef 空格 被重命名类型 空格 重命名

数组指针类型重命名

如下图举例;根据逻辑上来讲应该写成,typedef int ( * )[ 5 ] parr_t ,但是语法要求写在括号里面。

函数指针的重命名和数组指针一样

如果用# define来定义类型,也可以,但是在使用时如果一次性定义两个变量,那么后面那个变量就不会被完整定义了(限于定义指针变量)

函数指针数组:

从名字的意义就大概能知道是,存放函数指针的一个数组。

 在函数指针变量名的后边给上一个[ 数组元素个数 ](也可以不写会自动匹配就是函数指针数组了。

函数指针数组的类型

去掉函数名就是它的类型,在这儿依然能用,如上图去掉函数名。

就是:int ( *  [4])(int,int),   [4] 说明这是一个数组。

 函数指针数组的调用

pfArr[ i ]得到几个函数的地址,相当于 Add ( ) Sbu ( ),再给括号里面给上参数即可实现调用,i 是多少就调用下标相同的地址。

函数数组指针变量

意思就是存放,函数指针数组的一个指针变量

 这个 pfArr,是一个数组,存放  函 数 指 针 的 数 组

那么取地址 &pfArr ,取到得就是函数指针数组的地址,那么如何创建相应的类型的呢?

首先写出他的类型:char*(*  [4])(int,char*),这就是类型,我们知道 * 可以用来确定他是一个指针变量,所以我们在类型的里面添上一个指针变量就能的到这个类型的指针变量了

        char *(*(*p)[4])(int,char*),这就是完美的创建了一个函数指针数组变量。

        把若干函数的指针放入一个数组中,再创建一个指针变量存放这个数组,这个指针比变量就叫做   函数数组指针变量

逐个分析

char*;为函数的返回类型。

(* ( )[4]) ; 表示指向的其实是一个数组。

(*p) ; 确认为指针变量。

(int , char*) ; 为函数的返回类型。

最终可得,一个变量,这个变量里面放的是一个指针(地址),而这个地址是一个数组的地址,这个数组里面存放的是函数的指针(也叫地址)

一个存放着函数指针数组的地址的变量。

回调函数

意义:通过一个指针变量实现调用的函数。

把add函数传给了Calc函数 ,下图为主函数内的代码。

 

 再在Calc函数中调用add函数,add就是回调函数。

qsort

        qsort是一个库函数(头文件是    stdlib.h),可以直接用来排序数组,底层使用的是快速排序。

        可以排序任意类型数据。只不过需要一点操作。

 qsort 函数的使用需要4个参数

        1、需要被排序的数组指针

        2、该数组的元素个数

        3、每一个元素的大小

        4、比较函数的指针。(这个函数要自己写,除了数组名,其他的照抄就行)

       

         p1和p2(都是变量名而已)为需要比较的那两个数据的地址(不用自己给它传参。)。

        由于void 类型的指针不能直接使用,所以需要在判断的时候强制类型转换(强制类型转换是根据元素的类型转换的。)。

        里面的判断语句就是 qsort 的使用规则

       1、 如果大于返回一个正数

       2、 小于返回负数

        3、等于返回 0

这样写也行,关键是能做到返回这三个数字(正数、负数、0)。

        下面为完整的代码,运行代码即可实现排序

        分析: qsort ( arr , sz , sizeof(arr [0]) , cmp_int)

1、传上需要被排序的数组名。

2、数组的元素个数

3、每一个元素的大小

4、比较函数的地址(这也算的上是一个回调函数了)

模拟实现qsort函数

问题一、

        在看是视频时我发现里面的交换函数是写在外面的然后我就在想这个比大小的函数就不能写在外面吗?

        然后我就发现了,我怎么给他传参呢?就算能够在main()里面传第一次第二次怎么搞?所以还是得通过test函数传过去。他不能够与里面的循环进行匹配。所以还是用回调函数(顺便复习一下)

回调函数:就是通过函数指针调用的函数

        如果你把一个函数的指针作为参数传给另一个函数,当这个指针被用来调用所指向的函数时,这个被调用的函数就是回调函数,也就是说下面如果把 panduan函数的指针传给 test函数,当for 循环里面需要比大小时此时调用 puanduan函数,这个panduan就是回调函数。

        回调函数不是由该函数直接调用的,而是在特定的事件或条件时由另一放调用的,用于实现该事件或条件的响应。

        还有就是我这里就只考虑了比较Int类型,有点写不下去了,说白了就是错误的想法,这样写实现不了任意类型的比较,行也很麻烦(得自己一个一个的改)。

 下面就是整个qosrt函数的实现,我只考虑了int 类型的情况

如有需要更换判断函数就行,对test函数这里修改为另一个函数就行,或者你也可以自行优化写个siwtch语句

然后下面这种比较函数再写俩出来就行

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



int panduan(void* q, void* p)
{
	return *(int*)q - *(int*)p;
}
void swap(char* q1, char* p1,size_t dx)
{	
	for ( int i = 0; i < dx; i++)
	{
		//因为在传参时传过来的是需要交换的的元素地址,直接每个字节交换就行。
		char a = *(q1+i);
		*(q1+i) = *(p1+i);
		*(p1+i) = a;
	}
}
void print(int arr[],size_t sz)
{
	int i = 0;
	for (i;i<sz;i++)
	{
		printf("%d ", arr[i]);
	}
}
void test(void* base, size_t sz, size_t dx,int(*cmp)(const void*q,const void *p))
{	
	int i = 0;
	for (i; i < sz - 1; i++)
	{
		int j = 0;
		for (j; j < sz-i; j++)
		{
			if (panduan((char*)base + j * dx, (char*)base + (j + 1) * dx) < 0)//使它接受任意类型时都是可以使用的
			{
				swap((char*)base + j * dx, (char*)base + (j + 1) * dx,dx);//因为是一个字节一个字节的交换所以还需要传宽度
			}
		}
	}
	

}


int main()
{
	int arr[] = { 1,2,4,5,6,7,8,9,0 };
	size_t sz = sizeof(arr) / sizeof(arr[0]);
	size_t dx = sizeof(arr[0]);
	test(arr,sz,dx,panduan);
	print(arr,sz);
	return 0;
}

sizeof与数组之间的爱恨情仇        

sizeof与整形数组

计算大小:sizeof 计算大小时 ,x86和x64算出来的一样大,但是算指针就不一样了。

其实看懂这个的关键就是区分,里面是元素还是地址

前提是 int 类型的数组 ,与x64的环境,你别不一样,然后怎么不一样呢?

        如果是元素那么就是4个字节

        如果是地址就是8个字节(指针变量的大小参考目录

分析:

        1、不用讲

        2、sizeof (a+0),它不属于sizeof(数组名)管了,所以是首元素地址,欧克只要知道是地址了那么不用看了直接就是8.

        3、sizeof( *a ),归它管,所以是一个地址,但这里要注意有 “ 解引用 ” 符号,所以他这样就是个元素了,就等于 sizeof( 1 ),一个 int 类型的元素就是4个字节。

        4、sizeof ( a+1 ), a+1怎么搞他也是个地址,直接就是8。还是说一下,a不为那两种情况所以是首元素地址,只要是地址就是8.其实只要不是sizeof(a)这一种情况就行,后面马上讲到

        5、sizeof( a[1] ),里面是个元素,4。

        6、sizeof( &a ),这不就来了,取地址取地址,即使他是一个数组的地址只要是 int 类型的他就是 8,这个数组指针的类型就是,int (* )[4]这就是类型。

        7、sizeof (*&a ),有两理解方式但结果都一样,我将我直观理解的,&a 得到整个数组的地址,再解引用得到整个数组,就等于第一种情况。16

        8、sizeof( &a+1 ),数组地址加一,跳过一个数组,他还是一个地址,8。

        9、sizeof( &a[0] ),得到那个元素再取地址他还是一个地址,8。

        10、sizeof( &a[0]+

1 ) ,一个地址无需多言了。

int a[] = {1,23,4,2};
	1、printf("%zd\n", sizeof(a));     16 -- sizeof(数组名)的场景
	2、printf("%zd\n", sizeof(a + 0));    a是首元素的地址-类型是int*, a+0 还是首元素的地址,是地址大小就是4/8
	3、printf("%zd\n", sizeof(*a));    a是首元素的地址,*a就是首元素,大小就是4个字节
	//*a == a[0] == *(a+0)
	4、printf("%zd\n", sizeof(a + 1));    a是首元素的地址,类型是int*,a+1跳过1个整型,a+1就是第二个元素的地址,4/8
	5、printf("%zd\n", sizeof(a[1]));    a[1]就是第二个元素,大小4个字节
	6、printf("%zd\n", sizeof(&a));    &a是数组的地址,数组的地址也是地址,是地址大小就是4/8个字节
	7、printf("%zd\n", sizeof(*&a));    1. *& 互相抵消了,sizeof(*&a) = sizeof(a) -16
	2. &a 是数组的地址,类型是int(*)[4],对数组指针解引用访问的是数组, 计算的是数组的大小 -16
	
	8、printf("%zd\n", sizeof(&a + 1));    &a+1是跳过整个数组后的那个位置的地址,是地址就是4/8个字节
	9、printf("%zd\n", sizeof(&a[0]));     首元素的地址,大小4/8个字节
	10、printf("%zd\n", sizeof(&a[0] + 1));    &a[0] + 1 -- 数组第二个元素的地址,大小是4/8个字节

 sizeof与字符串数组

        都差不多,只是字符串的末尾有个斜杠0,在数组中会占一个空间。

解析:
        1、略

        2、sizeof(arr+0) , arr为首元素地址,类型是char *,既然是地址无需多言。

        3、sizeof(*arr) , arr同样为首元素地址,然后解引用得到字符 a ,大小就是一个字节。

        4、sizeof(arr[1]) , 略。

        5、sizeof(&arr) , 地址无需多言。

        6、sizeof(&arr+1) , 地址无需多言。

        7、sizeof(&arr[0]+1) , 地址无需多言。

char arr[] = "abcdef";
	1、printf("%d\n", sizeof(arr));    arr是数组名,单独放在sizeof内部,计算的是数组总大小,是7个字节
	2、printf("%d\n", sizeof(arr + 0));    arr表示数组首元素的地址,arr+0还是首元素的地址,是地址就是4/8
	3、printf("%d\n", sizeof(*arr));    arr表示数组首元素的地址,*arr就是首元素,大小是1字节
	4、printf("%d\n", sizeof(arr[1]));    arr[1]是第二个元素,大小1个字节
	5、printf("%d\n", sizeof(&arr));    &arr是数组的地址,是地址就是4/8
	6、printf("%d\n", sizeof(&arr + 1));    &arr是数组的地址,+1跳过整个数组,还是地址,是地址就是4/8个字节
	7、printf("%d\n", sizeof(&arr[0] + 1));    &arr[0] + 1是第二个元素的地址,大小是4/8个字节

sizeof与存放字符串的char*类型指针变量

 解析、

        1、sizeof( p ) , 指针变量的大小上面有说过。

        2、sizeof(p+1) , p为首元素地址(参考字符指针变量章节),p+1第二元素的地址,大小无需多言。

        3、siezof(*p) , 相当于解引用首元素地址,得到首元素,大小是1.

        4、sizeof(p[0]) , 等于*(p+0),和上面这个一样

        5、sizeof(&p),我们知道p是一个指针变量所以,&p也是一个地址,类型是char**,大小无需多言

        6、sizoef(&p+1) ,也是一个地址。

        7、sizeof(&p[0]+1) , p[0]得到首元素,再取地址,其实就抵消了,和第二个一样、


	const char * p = "abcdef";

	1、printf("%d\n", sizeof(p));    p是指针变量,我们计算的是指针变量的大小,4/8个字节
	2、printf("%d\n", sizeof(p + 1));    p + 1是b的地址,是地址大小就是4/8个字节
	3、printf("%d\n", sizeof(*p));    p的类型是const char*, *p就是char类型了,1个字节
	4、printf("%d\n", sizeof(p[0]));    1. p[0] --> *(p+0)--> *p --> 'a',大小是1字节
	2. 把常量字符串想象成数组,p可以理解为数组名,p[0], 就是首元素

	5、printf("%d\n", sizeof(&p));    取出的是p的地址,地址的大小是4/8个字节
	6、printf("%d\n", sizeof(&p + 1));    &p + 1是跳过p指针变量后的地址,是地址就是4/8个字节
	7、printf("%d\n", sizeof(&p[0] + 1));    4/8 &p[0]-取出字符串首字符的地址,+1是第二个字符的地址,大小是4/8个字节

sizeof与二维数组 

 解析、

        1、sizeof(a) , 求整个数组的大小,48个字节。

        2、sizeof(a[0][0]) , 求第一行第一个元素的大小,4。

        3、sizeof(arr[0]) , arr[0]为第一行的数组,数组名穿给sizeof计算整个数组的大小,16.

        4、sizeof(arr[0]+1),此时的arr[0]可不是整数组了,而是首元素的地址,确实地说是第一行首元素的地址,如果是arr[1]+1那么这个就是第二行首元素的地址了。加一个跳过首元素地址来到第一行第二个元素的地址,地址无需多言。

        5、sieozf(*(a[0]+1)),a[0]+1为第一行第二个元素的地址,对其解引用得到第二个元素,大小是 4.

        6、sizeof(a+1) , 此处的a应为首元素的地址,a是个二维数组,所以这里应该是第一行的地址然后加1,是第行的的地址,地址都懂。

        7、sizeof(*(a+1)) , 对第二行的地址解引用得到第行的的数组,大小是第二行数组的大小,16

        8、sizeof(&a[0]+1) , arr[0]为第一行的数组名,&数组名,得到整个数组的地址,加一交过这个数组,还是一个地址。

        9、sizoef(*(&arr[0]+1)) , 对第二行的地址进行解引用得到第二行,大小是第二行数组的大小,16.

        10、sizeof(*a) , 解引用首元素地址,得到第一行数组,大小16.

        11、sizeof(a[3]) , 不用看他越界没有,只管他的类型就行,a[3]为第四行的数组名,他的类型应该就是 int () [4],大小是16。

int a[3][4] = { 0 };
	1、printf("%d\n", sizeof(a));    a是数组名,单独放在sizeof内部,计算的是数组的大小,单位是字节 - 48 = 3*4*sizeof(int)
	2、printf("%d\n", sizeof(a[0][0]));    a[0][0] 是第一行第一个元素,大小4个字节 
	3、printf("%d\n", sizeof(a[0]));    a[0]第一行的数组名,数组名单独放在sizeof内部了,计算的是数组的总大小 16 个字节
	4、printf("%d\n", sizeof(a[0] + 1));    a[0]第一行的数组名,但是a[0]并没有单独放在sizeof内部,所以这里的数组名a[0]就是
	数组首元素的地址,就是&a[0][0],+1后是a[0][1]的地址,大小是4/8个字节

	5、printf("%d\n", sizeof(*(a[0] + 1)));    *(a[0] + 1)表示第一行第二个元素,大小就是4
	6、printf("%d\n", sizeof(a + 1));    a作为数组名并没有单独放在sizeof内部,a表示数组首元素的地址,是二维数组首元素的地址,也就是
	第一行的地址,a+1,跳过一行,指向了第二行,a+1是第二行的地址,a+1是数组指针,是地址大小就是4/8个字节

	7、printf("%d\n", sizeof(*(a + 1)));   1.a+1是第二行的地址,*(a+1)就是第二行,计算的是第二行的大小 - 16
	2. *(a + 1) == a[1], a[1]是第二行的数组名,sizeof(*(a + 1))就相当于sizeof(a[1]),意思是把第二行的数组名单独放在
	sizeof内部,计算的是第二行的大小
	8、printf("%d\n", sizeof(&a[0] + 1));    a[0]是第一行的数组名,&a[0]取出的就是数组的地址,就是第一行的地址
	&a[0]+1 就是第二行的地址,是地址大小就是4/8个字节
	9、printf("%d\n", sizeof(*(&a[0] + 1)));    *(&a[0] + 1)意思是对第二行的地址解引用,访问的就是第二行,大小是16字节
	10、printf("%d\n", sizeof(*a));    a作为数组名并没有单独放在sizeof内部,a表示数组首元素的地址,是二维数组首元素的地址,也就是
	第一行的地址,*a就是第一行,计算的就是第一行的大小,16字节
	*a == *(a+0) == a[0]
	11、printf("%d\n", sizeof(a[3]));a[3]无需真实存在,仅仅通过类型的推断就能算出长度
	a[3]是第四行的数组名,单独放在sizeof内部,计算的是第四行的大小,16个字节


strlen与数组之间的爱恨情仇

strlen与字符数组

        我主要讲一下那两个被注释掉的代码,其他的里面都是地址,strlen 会从起始地址一直往后面查找,找到 \0 为止,由于数组里面没有\0所是随机值

        strlen( *arr ),咱们可以知道,*arr就是字符 a ,而strlen的后面应该是地址(这里你可能就想说了不是字符串也可以嘛?因为字符串其实就相当于一个字符数组也是有地址的),而字符 a对应的十进制数就是97,这里编译器会以为给的是一个地址,strlen就会去找这个地址,但这个地址是个野指针不能访问,然后就出错了。下面那个也是一样。

	char arr[] = { 'a','b','c','d','e','f' };
	1、printf("%d\n", strlen(arr));    arr是首元素的地址,数组中没有\0,就会导致越界访问,结果就是随机的
	2、printf("%d\n", strlen(arr + 0));    arr+0是数组首元素的地址,数组中没有\0,就会导致越界访问,结果就是随机的
	3、printf("%d\n", strlen(*arr));    arr是首元素的地址,*arr是首元素,就是'a','a'的ascii码值是97
	就相当于把97作为地址传递给了strlen,strlen得到的就是野指针, 代码是有问题的
	4、printf("%d\n", strlen(arr[1]));    arr[1]--'b'--98,传给strlen函数也是错误的
	5、printf("%d\n", strlen(&arr));    &arr是数组的地址,起始位置是数组的第一个元素的位置,随机值 x
	6、printf("%d\n", strlen(&arr + 1));     随机值 x-6
	7、printf("%d\n", strlen(&arr[0] + 1));    从第2个元素开始向后统计的,得到的也是随机值 x-1

strlen 与字符串数组 

最后一个,是从第二地址开始算的所以会少一个。

解析:
        1、略

        2、 strlen(arr+0) , 和第一个一样,首元素地址开始往后统计。

        3、strlen(*arr) , 解引用第一个地址,得到一个字符,然后出错,后面得为地址才行。

        4、strlen(arr[1]),和上面这个一样。

        5、strlen(&arr)在这里等同于第一个。

        6、strlen(&arr+1) ,  跳过arr数组的地址然后找斜杠0,随机值

        7、strlen(&arr[0]+1) ,从第二个元素开始统计。

char arr[] = "abcdef";
	1、printf("%d\n", strlen(arr));    6
	2、printf("%d\n", strlen(arr + 0));    arr首元素的地址,arr+0还是首元素的地址,向后在\0之前有6个字符
	3、printf("%d\n", strlen(*arr));    'a'-97, 出错
	4、printf("%d\n", strlen(arr[1]));    'b'-98, 出错
	5、printf("%d\n", strlen(&arr));    &arr是数组的地址,也是从数组第一个元素开始向后找,6
	&arr -- char (*)[7]
	size_t strlen(const char* s);

	6、printf("%d\n", strlen(&arr + 1));    随机值
	7、printf("%d\n", strlen(&arr[0] + 1));    5

 strlen与存放字符串的char*类型指针变量

解析: 

        1、略

        2、strlen(p+1),首元素地址加一,跳过首元素统计。

        3、strlen(*p),解引用得到字符,出错。

        4、strlen(p[0]),和上面的一样。

        5、strlen(&p) , 得到的是这个变量p的地址,p的地址后面不知道哪儿有斜杠0,随机值。

        6、strlen(&p+1) , &p都是随机值何况加一。

        7、strlen(&p[0]+1),首元素地址加一。

	char* p = "abcdef";
	1、printf("%d\n", strlen(p));   6
	2、printf("%d\n", strlen(p + 1));   5
	3、printf("%d\n", strlen(*p));   *p就是'a'-97,err
	4、printf("%d\n", strlen(p[0]));   p[0]--> *(p+0)--> *p    err
	5、printf("%d\n", strlen(&p));   &p是指针变量p的地址,和字符串"abcdef"关系就不大了
	从p这个指针变量的起始位置开始向后数的,p变量存放的地址是什么,不知道,所以答案是随机值
	6、printf("%d\n", strlen(&p + 1));   随机值

	7、printf("%d\n", strlen(&p[0] + 1));   &p[0]-取出字符串首字符的地址,+1是第二个字符的地址, 5

题目讲解:

分析:

0x的意思是后面的数字都是十六进制数。

ox1到15的十进制和十六进制相等

        1、(p+0x1), 由*p=(struct Test*)0x100000, 可知 p是一个存放着结构体指针的指针变量,地址为0x100000,所以p+1(十六进制的1和十进制一样都是1),等于跳过一个结构体大小的地址,结果为 0x100020,你以为完了嘛?没有哦,得转成十六进制,0x100014这才是p+0x1的地址。

        2、(unsigned long)p+0x1,将p里面的地址强制转为一串无符号长整形数字,再加一,就等于这个数字加一。为100001,打印会有问题,因为是整形对不上。

        3、(unsigned int*)p+0x1,把结构体类型指针转为无符号整形指针,再加一跳过4个地址,为0x100004.

这题的关键在于,p 这个指针变量一行只能有四个元素,而 这个数组一行有个五个元素,

说明在a数组一行内只能有四个元素的地址放在p里面,第五个元素变成第二行第一个地址。

所以 

        &p[4][2],就是原a数组的a[3][3]的地址

        %p,打印整数打印的是该整数的二进制补码,转为十六进制数答应出来。

for example

        随便带大家回忆下正数的补码是他本身所以我这里还搞错了(和我预想的搞错了)

        这里就欧克了,转成十六进制的。

aa +1得到第二行的地址,解引用得到第二行数组。但也是一个地址。

由此证明*(aa+1)等于第二行数组名,数组名也表示首元素地址,所以解引用就会得到第一个元素,首元素地址加一得到第二个元素地址。

*(++cpp),在上面已经加过一次,指向的是cp的第二个元素,再加一次指向cp的第三个元素,解引用得到的是cp+2这个地址里的东西,就是c+1,*((c+1)-1)就是解引用首元素的的地址了,得到首元素地址里的东西。

解析 cpp[-2]

cpp[-2] 等于  *(cpp-2);

补充

指针转整形后的加减

        一个整形指针被转成实数后加一,再转回整形指针,等于由原来的起始地址跳过一个字节的地址,再转成  int*(整形指针)的话,类似于这个四个地址范围整体向后挪动一位第一个地址加一位,如果转成char 类型那就是第一个地址,

如果是short类型,他就要管俩

番外 

结构体

下面这是在创建一个结构体类型

这只是一个类型,说明了什么,说明了在使用时我们还需要创建一个——结构体变量,或者是数组。

结构体的指向 

想要具体的找到结构体的某个参数。(—>结构体成员访问操作符)

strcmp

        这个库函数用来比较字符串大小的(按照对应的字符的ASCLL码值),返回值和上面的qsort一样

 

二维数组中,得到第几行的数组名,那么也表示这行的第一个元素的地址,除非看见&(数组名)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值