本笔记记录自 Coursera课程 《计算机程式设计》 台湾大学 刘邦锋老师

Week5 Pointer

5-1 Pointer Definition and Declaration

指针和一般变量的区别。一般变量的值就是代表数据,而指针变量的值则代表另一个变量的记忆体位址。一般变量有数据类别,比如说整数,浮点数等。指针变量也有数据类型,比如说指向整数的指针,指向浮点数的指针等。

申明指标变量的方法

int *iptr;
float *fptr;
double *dptr;

在变量名称前加一个*号代表这是一个指标变量。而*之前的数据类型就是这个指标变量所能指到的变量数据类型。

例子:(size.c)指针变量所占的位元组数

#include <stdio.h>
int main(void)
{
int *iptr;
float *fptr;
double *dptr;
printf("sizeof(iptr) = %d\n", sizeof(iptr));
printf("sizeof(fptr) = %d\n", sizeof(fptr));
printf("sizeof(dptr) = %d\n", sizeof(dptr));
return 0;
}

输出结果

sizeof(iptr) = 8
sizeof(fptr) = 8
sizeof(dptr) = 8

所以说,不管是指向4个位元组的整数,还是8个位元组的倍准浮点数,指针变量都是占8个位元组,因为他们都是存8个位元组(64 bit)的记忆体位址。

指定整数指标变量的值

int i;
int *iptr1;
int *iptr2;
iptr1 = &i;
iptr2 = iptr2;
  • 当一个整数指针变量iptr的值是另一个整数变量i的记忆体位址时,称iptr指向i
  • 一个整数变量的记忆体位址可以指定给一个整数指针变量当作值,也可以将一个整数指针变量的值指定给另一个整数指针变量当作值。

使用指标变量所指到的变量

i = *iptr;
*iptr = i;
  • 当一个指针变量前加上*时,就代表从这个记忆体位址取值(dereference)
  • *iptr可以出现在=的右边或左边。

NULL绝不指向任何有效的记忆体位址

#include <stdio.h>
...
ptr = NULL;
  • 有时我们希望程序能够让一个指针变量不指向任何有效的记忆体位址,方法就是将它初始化成一个“特殊值”。
  • 为此C语言定义了NULL作为这样的用途。一般这个值是定义成0,因为0这个记忆体位址通常是保留给系统使用,一般程序是不能使用的。
  • NULL是在<stdio.h>标头档定义的,所以必须引入。

5-2 Pointer Usage

使用更加复杂一点的例子来讲解指针与普通变量的关系。可以使用指针以及指针的取值进行一些操作。

5-3 Pointer Reference and Dereference

使用更加复杂一点的例子来讲解指针与指针位址与普通变量位址的关系。

总之只要了解*是取值,&是取位址。

5-4 Pointer Parameter Passing

例子:(pointer-parameter.c)指针参数传递

#include <stdio.h>
void pointer_inc(int *p1, int *p2)
{
printf("The address of p1 is %p\n", &p1);
printf("The value of p1 is %p\n", p1);
printf("The address of p2 is %p\n", &p2);
printf("The value of p2 is %p\n", p2);
*p1 += 1;
p1 = p2;
*p1 += 2;
}
int main(void)
{
int i, j;
int *iptr = &i; scanf("%d", &i);
scanf("%d", &j);
printf("The address of i is %p\n", &i);
printf("The address of j is %p\n", &j);
printf("The address of iptr is %p\n", &iptr);
printf("i = %d, j = %d\n", i, j);
pointer_inc(iptr, &j);
printf("i = %d, j = %d\n", i, j);
*iptr += 5;
printf("i = %d, j = %d\n", i, j);
return 0;
}

输入

10 20

输出

The address of i is 0x7fff1592a9c0
The address of j is 0x7fff1592a9c4
The address of iptr is 0x7fff1592a9c8
i = 10, j = 20
The address of p1 is 0x7fff1592a9a8
The value of p1 is 0x7fff1592a9c0
The address of p2 is 0x7fff1592a9a0
The value of p2 is 0x7fff1592a9c4
i = 11, j = 22
i = 16, j =22

所以总结一下:

  • 实际参数与形式参数使用不同的记忆体,所以实际参数iptr和对应的形式参数p1位于不同的记忆体位址。
  • 虽然p1已经改指向j,但是iptr还是指向i,因为p1和iptr是位于不同记忆体位址的不同变量,改变一个并不会改变另一个。

5-5 Pointer and Array

指针指向数组的起始记忆体位址

int array[100];
int *iptr;
...
iptr = array;
...
use *iptr as array[0]
...
  • iptr = array;就是使iptr指向数组的起始记忆体位址。
  • 因为数组的起始记忆体位址是array[0]的位址,所以*iptr就如同array[0]。有如用指针的语法从数组内取元素。

将指针加1

iptr++;
  • C语言中有一套指针的算术语法,让指针可以加减一个整数,最常用的就是将指针加1。
  • 这里的加1是指加一个元素的大小。所以如果是整数,指针变量就是加sizeof(int) = 4,而如果是倍准浮点数,指针变量就是加sizeof(double) = 8。可以想象成指向下一个元素。

例子:(inc3-with-pointer.c)利用指针将数组元素加3

#include <stdio.h>
int main(void)
{
int a[5];
int i;
int *ptr;
for (i = 0; i < 5; i++)
scanf("%d", &(a[i]));
for (i = 0, ptr = a; i < 5; i++, ptr++){
printf("%p\n", ptr);
*ptr += 3;
}
for (i = 0; i < 5; i++)
printf("a[%d] = %d\n", i, a[i]);
return 0;
}

输入

1 2 3 4 5

输出

0x7fff4b931d90
0x7fff4b931d94
0x7fff4b931d98
0x7fff4b931d9c
0x7fff4b931da0
a[0] = 4
a[1] = 5
a[2] = 6
a[3] = 7
a[4] = 8

5-6 Pointer and Relative Index

指针也可以使用数组的语法

*(iptr + i)
iptr[i]
  • 指针变量加i就是指向下i个的意思。
  • 指针变量iptr指向数组a的起始位置,再加i,再用星号取值,那*(iptr + i)就正是a[i]。

但是注意:数组的是绝对坐标,而指针变量的是相对坐标

5-7 Pointer Arithmetic

例子:(arith.c)指针算术

#include <stdio.h>
int main(void)
{
int array[10];
int *iptr1 = &(array[3]);
int *iptr2 = iptr1 + 4;
printf("iptr1 = %p\n", iptr1);
printf("iptr2 = %p\n", iptr2);
printf("iptr2 - iptr1 = %d\n", iptr2 - iptr1);
return 0;
}

输出

iptr1 = 0x7fffa00d9f8c
iptr2 - 0x7fffa00d9f9c
iptr2 - iptr1 = 4
  • 指针算术可以将一个指针加一个常数得到一个指针,自然也可以减去一个常数得到一个指针,而且两个指针也可以相减得到一个常数。
  • 这里的常数都不是指一般以位元组为单位的记忆体位址,而是以指针所指到元素大小为单位。

5-8 Pointer as Return Value

例子:(first-positive.c)将iptr所指到的记忆体中的第一个正整数的记忆体位址返回

#include <stdio.h>
int *first_positive(int *ptr)
{
while (*ptr <= 0)
ptr++;
return ptr;
}
int main(void)
{
int i;
int array[10];
int *iptr; for (i = 0; i < 10; i++)
scanf("%d", &(array[i]));
iptr = first_positive(array);
printf("*iptr = %d\n", *iptr);
printf("iptr -array = %d\n", iptr - array);
iptr = first_positive(array[5]);
printf("*iptr = %d\n", *iptr);
printf("iptr -array = %d\n", iptr - array);
return 0;
}

输入

0 0 0 5 9 0 0 6 0 2

输出

*iptr = 5
iptr - array = 3
*iptr = 6
iptr - array = 7

5-9 Caution in Using Pointer

指针的用途

  • 在动态分配记忆体时,我们可以直接向作业系统要求一块记忆体使用,所以我们需要一个机制,让程序有办法能记住要来的记忆体位址,这个机制就是指针变量。
  • 动态数据结构会将数据串联起来形成结构。此时数据结构的大小与形状都是依动态要求而调整,此时我们就需要指针来描述数据之间的连结关系。
  • 在处理字串时,程序常常需要以记忆体位址来沟通。此时沟通的双方未必能够使用数组的标注语法,因为其中一方未必能够知道数组的起始记忆体位址。此时使用指针直接指到记忆体是最有效的沟通方法。

注意事项

  • 除非是上述的动态配置记忆体,动态数据结构及字串处理,否则尽量避免使用指针。
  • C语言中对指针的使用没有安全机制,初学很容易弄错,而且难以除错。
  • 很多指针的使用是可以用数组语法代替的,这样不但容易月度,也容易除错。
  • 有人认为使用指针可增加效率,但是现在的编译器已经能产生非常好的执行档。为了效率牺牲可读性也许并不值得。

测验代码

老师的

#include <stdio.h>

void shuffle ( int *deck[] )
{
int i = 0;
int buf[10000];
while ( deck[i] ) {
buf[i] = *deck[i];
i++;
} int sec_s = i / 2 + i % 2;
int fir = 0, sec = sec_s, deck_p = 0; while ( deck[deck_p] ) {
*deck[deck_p++] = buf[fir++];
if ( buf[sec] )
*deck[deck_p++] = buf[sec++];
}
} void print ( int *deck[] )
{
int i = 0;
while ( deck[i] ) {
if ( i ) printf ( " " );
printf ( "%d", *deck[i++] );
}
} int main()
{
int card[10000];
int *deck[10000];
int index = 0; while (scanf("%d", &(card[index])) != EOF) {
deck[index] = &(card[index]);
index++;
}
deck[index] = NULL;
shuffle(deck);
print(deck);
return 0;
}

《计算机程式设计》Week5 课堂笔记的更多相关文章

  1. 【C语言】Coursera课程《计算机程式设计》台湾大学刘邦锋——Week6 String课堂笔记

    Coursera课程 <计算机程式设计>台湾大学 刘邦锋 Week6 String 6-1 Character and ASCII 字符变量的声明 char c; C语言使用一个位元组来储 ...

  2. 《计算机程式设计》Week4 课堂笔记

    本笔记记录自 Coursera课程 <计算机程式设计> 台湾大学 刘邦锋老师 Week4 Functions 4-1 System Function 函数主要分为两大类系统定义函数与使用者 ...

  3. 《计算机程式设计》Week3 课堂笔记

    本笔记记录自 Coursera课程 <计算机程式设计> 台湾大学 刘邦锋老师 Week3 Array 3-1 Array Usage 例子:使用数组一次申明10个整数变量 int a[10 ...

  4. 《计算机程式设计》Week2 课堂笔记

    本笔记记录自 Coursera课程 <计算机程式设计> 台湾大学 刘邦锋老师 Week2 Control Structure 2-1 If-then-else if then 判断 if ...

  5. 九章算法系列(#3 Binary Tree & Divide Conquer)-课堂笔记

    前言 第一天的算法都还没有缓过来,直接就进入了第二天的算法学习.前一天一直在整理Binary Search的笔记,也没有提前预习一下,好在Binary Tree算是自己最熟的地方了吧(LeetCode ...

  6. 九章算法系列(#5 Linked List)-课堂笔记

    前言 又是很长时间才回来发一篇博客,前一个月确实因为杂七杂八的事情影响了很多,现在还是到了大火燃眉毛的时候了,也应该开始继续整理一下算法的思路了.Linked List大家应该是特别熟悉不过的了,因为 ...

  7. 九章算法系列(#4 Dynamic Programming)-课堂笔记

    前言 时隔这么久才发了这篇早在三周前就应该发出来的课堂笔记,由于懒癌犯了,加上各种原因,实在是应该反思.好多课堂上老师说的重要的东西可能细节上有一些急记不住了,但是幸好做了一些笔记,还能够让自己回想起 ...

  8. 九章算法系列(#2 Binary Search)-课堂笔记

    前言 先说一些题外的东西吧.受到春跃大神的影响和启发,推荐了这个算法公开课给我,晚上睡觉前点开一看发现课还有两天要开始,本着要好好系统地学习一下算法,于是就爬起来拉上两个小伙伴组团报名了.今天听了第一 ...

  9. ocp11g培训内部教材_052课堂笔记(042)_体系架构

    OCP 052 课堂笔记 目录 第一部分: Oracle体系架构... 4 第一章:实例与数据库... 4 1.Oracle 网络架构及应用环境... 4 2.Oracle 体系结构... 4 3. ...

随机推荐

  1. vue.js table组件封装

    table组件 和 分页组件来自iview,在这里我根据公司业务再次做了一次封装,使用slot进行内容分发,可以随意放置input输入框和button按钮 ,再使用props向子组件传递参数,使用em ...

  2. MongoDB的使用学习之(四)权限设置--用户名、密码、端口==

    本文参照:http://hi.baidu.com/tianhuimin/item/590d96cfd7ac1509c610b26a 本人也是按照此文章操作的,但是有些不妥,红色文字就是我实践后,需要改 ...

  3. IDEA 不自动复制资源文件到编译目录 classes 的问题

    复制文件后建议编译项目

  4. Spring基础04——ApplicationContext

    1.ApplicationContext简述 ApplicationContext代表IOC容器,在SpringIOC容器中读取Bean配置创建Bean实例之前,必须对它进行实例化,只有在容器实例化后 ...

  5. js emoji 过滤

    function filteremoji(emojireg){ var ranges = [ '\ud83c[\udf00-\udfff]', '\ud83d[\udc00-\ude4f]', '\u ...

  6. Mongo--03 mongo副本集、备份与恢复

    目录 一.mongo副本集配置 二.查看副本集状态 三.副本集权重调整 四.创建节点 五.仲裁节点 六.mongo备份与恢复 七.准备测试数据 一.mongo副本集配置 1.创建节点目录和数据目录 # ...

  7. The Complex Inversion Formula. Bromwich contour.

    网址:http://www.solitaryroad.com/c916.html

  8. centos 安装samba

    1 安装 yum install samba samba-client samba-common -y 2 配置 vim /etc/samba/smb.conf 在最下面增加 [wolbo] path ...

  9. 设计模式Design Pattern(2)--单例模式

    单例顾名思义就是一个实例.类只有唯一一个实例,并提供给全局使用.解决了全局使用的类频繁地创建与销毁带了的消耗. 单例模式常用简单,但细究却又不简单,且往下看. 单例模式又可以分为 (1)懒汉式:需要使 ...

  10. LeetCode--105--从前序与中序遍历序列构造二叉树(python)

    根据一棵树的前序遍历与中序遍历构造二叉树. 注意:你可以假设树中没有重复的元素. 例如,给出 前序遍历 preorder = [3,9,20,15,7]中序遍历 inorder = [9,3,15,2 ...