数组和指针学习记录

一、概念

. 数组:数组是用于储存多个相同类型数据的集合。
. 指针:指针相当于一个变量,存放的是其它变量在内存中的地址。

二、指针数组和数组指针

2.1数组指针

int (*p)[n];

也叫行指针,()优先级高,首先说明p是一个指针,指向一个整型的一维数组,这个一维数组的长度是n,也可以说是p的步长。也就是说执行p+1时,p要跨过n个整型数据的长度。
如要将二维数组赋给一指针,应这样赋值:

int a[3][4];
int (*p)[4]; //该语句是定义一个数组指针,指向含4个元素的一维数组。
 p=a;        //将该二维数组的首地址赋给p,也就是a[0]或&a[0][0]
 p++;       //该语句执行过后,也就是p=p+1;p跨过行a[0][]指向了行a[1][]

所以数组指针也称指向一维数组的指针,亦称行指针。
2.2指针数组

int *p[n];

[]优先级高,先与p结合成为一个数组,再由int*说明这是一个整型指针数组,它有n个指针类型的数组元素。这里执行p+1时,则p指向下一个数组元素,这样赋值是错误的:p=a;因为p是个不可知的表示,只存在p[0]、p[1]、p[2]…p[n-1],而且它们分别是指针变量可以用来存放变量地址。但可以这样 p=a; 这里p表示指针数组第一个元素的值,a的首地址的值。
如要将二维数组赋给一指针数组:

int *p[3];
int a[3][4];
p++; //该语句表示p数组指向下一个数组元素。注:此数组每一个元素都是一个指针
for(i=0;i<3;i++)
p[i]=a[i]

这里int *p[3] 表示一个一维数组内存放着三个指针变量,分别是p[0]、p[1]、p[2]
所以要分别赋值。

这样两者的区别就豁然开朗了,数组指针只是一个指针变量,似乎是C语言里专门用来指向二维数组的,它占有内存中一个指针的存储空间。指针数组是多个指针变量,以数组形式存在内存当中,占有多个指针的存储空间。
还需要说明的一点就是,同时用来指向二维数组时,其引用和用数组名引用都是一样的。
比如要表示数组中i行j列一个元素:

*(p[i]+j)*(*(p+i)+j)(*(p+i))[j]、p[i][j]

优先级:()>[]>*

三、对指针的操作

解引用:用*来获取指针指向的数据,这个不用多说。
指针的运算:加减运算,需要注意的是,指针的加减运算的粒度是基于指针类型的长度,在下例中:

int *p = (int*)0x1000;
char *str = (char*)0x1000;
p++;
str++;
print("p=%d,str=%d\r\n",p,str);

输出结果:
p=0x1004,str=0x1001
可以看到,p指向int型数据,p++就相当于p+sizeof(int),而str++就相当于str+sizeof(char).

四、例题

4.1

int a[5] = { 1, 2, 3, 4, 5 };
int *p = (int *)(&a + 1);
printf("%d,%d", *(a + 1), *(p - 1));

题目分析:&a+1是先取出数组a的地址,然后加1,那么指针变量p则指向数组最后一个元素后边的那块空间,然后对p-1进行解引用操作,就是对p向左偏移4个字节的空间解引用,得到的结果自然是5。对a+1进行解引用操作,这里a是数组名,代表数组首元素的地址,a+1则是数组第二个元素的地址,解引用自然就是2了。
输出结果:2,5

4.2

 struct test
    {
        int num;
        char *pcname;
        short date;
        char cha[2];
        short ba[4];
    }*p;
    p + (0x1) = 0x___
    (unsigned long)p+0x1 = 0x___
    (unsigned int*)p + 0x1 = 0x___

题目分析:结构体指针大小为20个字节,p是一个指向结构体的指针,p加1跳过二十个字节,所以 p + (0x1) =0x10014 。第二个是把结构体指针强制类型转换为(unsigned long)的数,对它加1,就直接给这个数加1,结果为0x100001。第三个是把结构体指针强制类型转换为(unsigned int*)类型,对它加1,偏移4个字节,结果为0x100004

输出结果:0x10014,0x100001,0x100004

4.3

        int a[4] = { 1, 2, 3, 4 };
        int *p1 = (int *)(&a + 1);
        int *p2 = (int *)((int)a + 1);
        printf("%x,%x", p1[-1], *p2);

题目分析:&a取出来的是数组a的地址,然后加1跳过整个数组,在强制类型装换为整型指针,则p1则指向4后边的那块空间。p1[-1]相当于*(p1-1),p1是一个整型指针,-1向后偏移4个字节,在解引用以十六进制打印得到4。在vs编译器里,数据是以小端存储的。a代表数组首元素的地址,将1的地址强制转换为一个整型数字,在加1,就是把1的地址当成一个数加1,在强制类型装换从其实地址跳过1个字节。然后p2进行解引用可以访问后边的四个字节,又因为它是以小端存储的,变过来为02 00 00 00,以十六进制打印结果为2000000。
输出结果:4,2000000

4.4

 int a[5][5];
 int(*p)[4];
 p = a;
 printf("%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);

编译器执行会报错。
题目分析:p是一个数组指针,指向一个长度为4的数组,首地址是a的首地址。对指针进行加法(减法)运算时,它前进(后退)的步长与它指向的数据类型有关,p 指向的数据类型是int [4],那么p+1就前进 4×4 = 16 个字节,p-1就后退 16 个字节。
p[i][j]也就是*(p[i]+j),即*((p+i)+j)。a为5行5列,a[4][2]对应从0开始的第22个元素,p[4][2]对应第(44+2)
输出结果:FFFFFFFC,-4

4.5

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

题目分析:数组赋值里边嵌套的是小括号,而不是花括号,小括号的里边是逗号表达式,所以数组的赋值相当于a[3][2]={1,3,5},这样结果就是1了。
输出结果:1

4.6

       int a[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
        int *p1 = (int *)(&a + 1);
        int *p2 = (int *)(*(a + 1));
        printf("%d,%d\n", *(p1 - 1), *(p2 - 1));

&a是取出整个数组的地址,对它加1跳过整个数组,然后将它强制类型装换为整型指针类型,对(p1 - 1)解引用得到的是p1向后偏移4个字节的那块空间的内容,既10。a代表数组第一行的地址,然后加1代表第二行的地址,对它解引用得到第二行的数组名a[1],既第二行首元素地址,然后将它强制类型装换为整型指针类型,对(p2 - 1)解引用得到第二行首元素地址向后偏移4个字节那块空间的内容,即5。
输出结果:10,5

4.7

       char *a[] = { "work", "at", "alibaba" };
        char **pa = a;
        pa++;
        printf("%s\n", *pa);

a是一个指针数组,里边存放了work,at,alibaba首元素的地址,pa又存放了数组a首元素(w的地址)的地址,pa++跳过一个char *的大小,现在pa指向数组第二个元素,在解引用得到at的地址,以%s打印结果就是at.
输出结果:at

4.8

        char *c[] = { "ENTER", "NEW", "POINT", "EIRST" };
        char**cp[] = { c + 3, c + 2, c + 1, c };
        char***cpp = cp;
        printf("%s\n", **++cpp);
        printf("%s\n", *--*++cpp+3);
        printf("%s\n", *cpp[-2]+3);
        printf("%s\n", cpp[-1][-1]+1);

输出结果:POINT,ER,ST,EW

char **cp[]={c+3,c+2,c+1,c};
这是二重指针。因为前边定义的c是数组类型,也就是一种常量指针(地址),所以c+3,c+2,c+1,c都是指向数组c每个元素的地址。现在要将数组元素的地址放在数组中,那么该用什么数据类型呢?存放一个变量(也可以是字符串)地址时,需要的是指针,那么存放一个地址的地址,就需要一个二重指针(因为只有指针可以存放地址,这里是存放一个地址的地址,所以要用二重指针)。

char *** cpp=cp;
cp本来就是一个常量指针,再加上他的类型是char**,所以它就是一个三级指针。这里cpp的类型是char***,也是三级指针,所以将cp直接赋值给cpp也就没有问题啦(类型相同的嘛(都是三级指针))。

printf("%s#",** ++cpp);
++的优先级高,所以先执行++cpp,也就是得到数组cp的第二个元素地址。
两个 ** 我们分开看,先看* ++cpp,这是取得数组cp第二个元素的值c+2。
在看** ++cpp,相当于*(c + 2),所以这里得到的是数组c第三个元素的地址,也就是"POINT"字符串的地址,所以这里输出POINT#

printf("%s#",* – *++cpp);
由优先级和执行顺序的关系得知,这里会先执行++cpp,因为前一个输出语句已经导致cpp加1了,所以现在cpp再加1,那么会得到数组cp的第三个元素地址。再执行 * ++cpp,这是取得数组cp第三个元素的值c+1。
然后再执行-- * ++cpp,相当于–(c+1),也就是c,然后再执行 * c,所以取得是数组c的第一个元素的地址,所以输出结果为ENTER#

printf("%s#",* cpp[-2]+3);
数组下标优先,所以会先执行cpp[-2],也就是*(cpp-2);因为前两输出语句导致cpp加了2,所以现在-2后,又使cpp-2的地址为数组cp的首地址,所以得到数组cp第一个元素c+3。再执行 * (cpp[-2]),也就是 * (c+3),这就得到了数组c的第四个元素的地址,也就是字符串"FIRST"的地址,然后这个地址再加3,所以得到字符串ST的地址,因此输出结果为ST

printf("%s#",cpp[-1][-1]+1);
先执行cpp[-1],相当于 * (cpp-1)因为前边两个输出语句导致cpp被加了2,它目前指向数组cp的第三个元素,所以cpp-1相当于数组cp第二个元素c+2,然后再执行cpp[-1][-1],相当于(c+2)[-1],也就是*(c+2-1) = *(c+1),所以得到数组c的第二个元素的地址,也就是字符串"NEW"的地址,然后在这个地址上加1,就得到字符串EW的地址,所以输出结果为EW

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
SQLAlchemy 是一个 SQL 工具包和对象关系映射(ORM)库,用于 Python 编程语言。它提供了一个高级的 SQL 工具和对象关系映射工具,允许开发者以 Python 类和对象的形式操作数据库,而无需编写大量的 SQL 语句。SQLAlchemy 建立在 DBAPI 之上,支持多种数据库后端,如 SQLite, MySQL, PostgreSQL 等。 SQLAlchemy 的核心功能: 对象关系映射(ORM): SQLAlchemy 允许开发者使用 Python 类来表示数据库表,使用类的实例表示表中的行。 开发者可以定义类之间的关系(如一对多、多对多),SQLAlchemy 会自动处理这些关系在数据库中的映射。 通过 ORM,开发者可以像操作 Python 对象一样操作数据库,这大大简化了数据库操作的复杂性。 表达式语言: SQLAlchemy 提供了一个丰富的 SQL 表达式语言,允许开发者以 Python 表达式的方式编写复杂的 SQL 查询。 表达式语言提供了对 SQL 语句的灵活控制,同时保持了代码的可读性和可维护性。 数据库引擎和连接池: SQLAlchemy 支持多种数据库后端,并且为每种后端提供了对应的数据库引擎。 它还提供了连接池管理功能,以优化数据库连接的创建、使用和释放。 会话管理: SQLAlchemy 使用会话(Session)来管理对象的持久化状态。 会话提供了一个工作单元(unit of work)和身份映射(identity map)的概念,使得对象的状态管理和查询更加高效。 事件系统: SQLAlchemy 提供了一个事件系统,允许开发者在 ORM 的各个生命周期阶段插入自定义的钩子函数。 这使得开发者可以在对象加载、修改、删除等操作时执行额外的逻辑。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值