这一周主要学习了C语言中的最灵活的技巧 --指针。指针也是一个变量,它有自己的地址也有指向的地址,一般来说我们更关注的是指针指向的地址。指针的指向可以是整数、浮点数、字符、数组,同样的也可以是一个函数。
指针的基本操作有赋值、求值、取指针地址、将一个整数指针加给指针,增加指针的值、减小指针的值和求差值。如下程序所示:
1 /*ptr_ops.c - 指针操作*/ 2 #include <stdio.h> 3 int main() 4 { 5 int urn[5] = {100, 200, 300, 400, 500}; 6 int * ptr1, * ptr2, * ptr3; 7 ptr1 = urn; /*把一个地址赋给指针*/ 8 ptr2 = &urn[2]; /*把一个地址赋给指针,取得指针指向的值,并且取得指针的地址*/ 9 printf("pointer value , dereferenced pointer , pointer address : \n"); 10 printf("ptr1 = 0x%x, *ptr1 = %d, &ptr1 = 0x%x",ptr1, *ptr1, &ptr1); 11 printf("\nurn = 0x%x\n",urn); 12 printf("\nurn[0] = 0x%x\n",urn[0]); 13 /*指针加法*/ 14 ptr3 = ptr1 + 4; 15 printf("\nadding an int to pointer:\n"); 16 printf("ptr1 +4 = 0x%x,*(ptr1 + 3)= %d\n",ptr1 + 4,*(ptr1 + 3)); 17 ptr1++; /*递增指针*/ 18 printf("\nvalues after ptr1++\n"); 19 printf("ptr1 = 0x%x,*ptr1 = %d,&ptr1 = 0x%x\n",ptr1,*ptr1,&ptr1); 20 ptr2--; /*递减指针*/ 21 printf("\nvaluse after --ptr2\n"); 22 printf("ptr2 = 0x%x,*ptr2 = %d,&ptr2 = 0x%x\n",ptr2,*ptr2,&ptr2); 23 --ptr1; /*恢复为初始值*/ 24 ++ptr2; /*恢复为初始值*/ 25 printf("\nPointers reset to original values :\n"); 26 printf("ptr1 = 0x%x,ptr2 = 0x%x\n",ptr1,ptr2); 27 /*一个指针减去另一个指针*/ 28 printf("ubtracting one pointer from another :\n"); 29 printf("ptr2 = 0x%x,ptr1 = 0x%x,ptr2 - ptr1 = %d\n",ptr2,ptr1,ptr2 -ptr1); 30 /*一个指针减去一个整数*/ 31 printf("\nsubtracting an int from a pointer : \n"); 32 printf("ptr3 = 0x%x,ptr3 - 2 = 0x%x\n",ptr3,ptr3 - 2); 33 return 0; 34 35 }
赋值:可以把一个地址赋值被指针,如果是数组的话可以直接进行赋值,如果是整数、浮点数或者字符等要用地址运算符&来进行赋值。在赋值的时候一定要注意变量的类型要与指针的类型匹配,比如int类型的地址要赋给一个指向int的指针。
取指针地址:上边程序中&ptr1指的就是ptr1这个指针变量的地址,而不是指针变量指向的地址。
将一个整数加给指针:这个整数将会和这个指针所指类型的字节相乘,。然后所得的结果会加到初始地址上。例如:ptr1 + 4的结果就等同于&urn[4].
增加指针的值:与我们的自加运算类似,对指向某数组元素的指针做累加运算,让指针指向该数组的下一个元素。例如ptr1++使ptr1指向urn[1],但是ptr1这个指针自己的地址没有变,改变的是指向的地址。
从指针中减去一个整数:与上边将一个整数加到指针上类似,比如ptr3-2的结果就等同于&urn[2],因为ptr3是指向&urn[4]的。
减小指针的值:与增加指针的值类似。
求差值:求差值通过计算出同一数组中两个指针的差来计算两个元素之间的距离,比如:ptr2-ptr1的值为2,代表两个指针所指元素之间距离为两个int数值大小。
比较:可以用与关系运算符比较两个指针的值,但是两个指针类型必须一致。
这些基础的东西一定要熟练才能更好的运用。在指针知识刚开始学习的时候搞不懂指针的地址和指针指向的地址,还有就是指针的值为问题,后来经过编程调试发现这些基本的东西就是定义,要记牢、会运用就可以了。
在后来一章C语言的高级技术中讲到了结构体的知识,这一块在举例的时候举到了链表操作,删除结点,查找结点,插入结点等。看了程序之后了解了链表的流程之后,我觉得链表就是指针和结构体的综合运用。链表中每个结点都是一个结构体,里边包括的就是这个结点的值和一个指针,这个指针指向的是下一个结点的地址。我觉得了解到这一点之后对于链表的操作就会好理解一点。
1 /*插入结点操作 2 *先判断链是否是空链表。 3 *新建一个结点,将结点结构中结点值的指针指向要插入的结点的值。 4 *如果不是找到链表中的空结点。空结点的前一个结点中指向下一个结点的指针指向新结点。 5 *如果是空链表,直接把新结点当作链表的链表头 6 */ 7 8 int insert(int val) 9 { 10 Node p,q; /*定义两个结点p和q*/ 11 p = head; 12 if(p != NULL){ /*寻找适当的插入位置*/ 13 while(p->next != NULL){ /*循环结束时p的下一个结点恰好是一个空结点*/ 14 p = p->next; 15 } 16 } 17 q = (Node)malloc(sizeof(struct node)); /*创建一个新结点*/ 18 if(q == NULL) 19 return -1; 20 q->next = NULL; /* 对新结点进行赋值*/ 21 q->val = val; 22 if(p == NULL){ //如果链表是一个空链表的话,将要插入的链表作为链表头 23 head = q; 24 return 1; 25 } 26 p->next = q; 27 return 1; 28 } 29 //删除结点操作 30 int del(int val, Node * res) 31 { 32 Node p, q; 33 p = head; 34 if(p == NULL); 35 return -1; 36 if(p->val == val) 37 { 38 *res = p; 39 head = p->next;//将要删除结点中指向下一个结点的指针赋给链表头,删除这个结点 40 return 1; 41 }else if(p->next == NULL)//链表中只有一个结点的,并且不是我们要删除的结点,返回-1 42 return -1; 43 q = p; 44 p = p->next; 45 while(p !=NULL) 46 { 47 if(p->val != val){ //找到要删除的结点,将要删除的结点赋值给p,将要删除的结点的前一个结点赋值给q 48 q = p; 49 p = p->next; 50 }else{ 51 q->next = p->next; //删除结点,将要删除的结点的前一个结点直接指向要删除结点的后一个结点,等于删除了这个结点 52 p->next = NULL; //删除的结点依旧存在,但是不在这个链表中,作为一个单独的结点,指向空结点 53 *res = p; 54 return 1; 55 } 56 } 57 return -1; 58 } 59 //遍历链表,打印每个结点的值 60 void print() 61 { 62 Node p = head; 63 while(p != NULL){ 64 printf("%d\n",p->val); 65 p = p->next; 66 } 67 } 68 //遍历链表,释放每一个结点 69 void destory() 70 { 71 Node p = head; 72 while(p != NULL){ 73 Node q; 74 q = p; 75 p = p->next; 76 free(q); 77 } 78 head = NULL; 79 }
上边分别是插入结点,删除结点,打印链表和释放每一个结点。之前我理解了链表的本质就是一个结构体,其中存放链表的值和指向下一个结点的指针。因此要想插入一个结点的思想应该是找到要插入的位置,比如是a,b两个结点之间。首先要将a中指向下一个结点的指针赋给要插入的结点中的指针,让要插入的指针指向b,然后修改a中指针的值,指向要插入的结点的地址。上边举例的程序中写的是插入指针在空白的地方,其实要想插入到某一个数值的前边和后边思想都是一致的,先找到要插入的位置,然后去修改指针就可以。同样的要想删除一个结点只需要把这个结点前一个结点的指针指向这个结点后一个结点,然后将删除的结点的指针指向空地址就可以了。
看完链表操作之后,我对于链表有了具体的了解,之前只是知道有链表这个东西,但是链表究竟是什么样的格式,怎么样操作并不知道。看完链表之后,我也进行了思考,链表中是每一个结点中有一个指针,那如果一个结点中有两个指针,分别指向两个地址,应该就是人们说的二叉树了吧。想到了这个东西但是还没有去实践,下一周有时间的话,可以去写一下这个程序试试看。
这是第二周的学习了,第二周的学习有一个感悟就是学代码不能仅仅局限在书中,要学会思考,在实践的基础上思考,再用实践去检验自己思考的结果。书毕竟篇幅有限,不可能所有的东西面面俱到,看懂书上的例子的同时要多问几个如果,如果修改一下会是什么样的结果。慢慢的学习过程中不仅要每天敲代码,而且要慢慢的敲自己代码,不能跟着别人的思路走,要把自己的思路用代码表现出来,然后再对比进行优化,从不敲代码到敲代码是开始阶段,从敲代码到敲自己的代码是提升阶段,慢慢的要开始提高自己代码的质量。