C语言_06 指针

 

概念

指针(Pointer)是编程语言中的一个对象,利用地址,它的值直接指向(points to)存在电脑存储器中另一个地方的值。由于通过地址能找到所需的变量单元,可以说,地址指向该变量单元。因此,将地址形象化的称为“指针”。意思是通过它能找到以它为地址的内存单元。
我们都知道, 在计算机语言中, 变量是储存在计算机的内存中的, 所以C程序想要访问得到变量必须知道储存在计算机内存中的什么位置, 在C程序中用来存储这个位置的变量就是指针.

指针的定义方法:

int *p = NULL;   //定义一个整型指针.
char *p1 = NULL; //定义一个char型指针
double *p2 = NULL;  //定义一个浮点型指针
定义指针和定义一个变量一样, 不过变量名前面加上一个星号(*), 表示这是一个什么类型的指针.

取地址符: & 和指针运算符 *

取地址符 &:
int a=3;
int *p = &a;   //取a的地址, 赋值给一个同类型的指针
scanf("%d", &a);  //我们输入一个整数需要存放进内存,所以我们必须知道将这个整数存放在内存的什么位置(地址).
指针运算符 *:
int a = 3;
int *p = &a;    //定义一个指针p,并将a的地址赋值给p, 这里的* 只是告知编译器定义的是一个指针变量.
//我们取得a的地址给了p,但是我们怎么取得a的值呢?  --指针运算符
int b = *p+3;    //效果相当于: int b = a + 3;  也相当与 int b = *&a + 3;
看到这里其实可以发现,我们平时定义的int a = 3; a = a + 3; 这个a被C语言简化隐藏起来了 *(&a), 找到a的地址, 然后取出该地址中存放的值.再对这个值进行操作.

指针和指针变量

1.指针:上面我们知道指针其实就是一段地址. 一个变量int a, 那么a的内存地址就是指针.2.指针变量: 用来存放指针(地址)的变量. 举个栗子:
int a = 4;
int *p = &a;   //取a的地址;
这个p就是指针变量, a的地址(&a)就是指针. 也就是指针变量指向的地址就是指针,存放这个地址的变量就是指针变量.
重点: 指针变量中存放的地址, 这个地址是指变量的首地址.
int a = 3;       //整型占四个字节. 如果a在内存中的位置是1000 ~ 1003
int *p = &a;   // 那么p = 1000, 因为是整型指针变量, 所以系统就知道1000~1003都是p所指向的东西.

指向指针的指针

指针变量的地址

我们知道指针变量也是存放在内存中的一个变量,那么存放在内存中,就有地址,这个就是指针的地址. int *p = &a; 那么p == &a; 指针变量的地址: &p;

指向指针的指针

我们通常定义的指针: int a = 3; int *p = &a;这个p指向的是整型变量a的地址. 那么指针变量p的地址: &p用什么来存.
int a = 3;
int *p = &a;
//不能用 int *p1 = &p;会报错,正确的方法: 
int **p1 = &p;    //p1就是指向指针的指针.
//所以我们得到p1, 怎么得到3呢? 
// *p1 == p == &a
// *(*p1) == *p == a == 3, 括号可以省略
int b = **p1 + 3;

数组与指针的关系

我们知道数组是一系列相同数据的集合, 我们通常定义一个数组: int a[4] = {1, 2, 3, 4}; 然后我们就可以通过下标任意访问数组中的任意一个元素, a[0] == 1, a[1] == 2.但其实数组名就是一个有着特殊用法的指针. 举个例子
int a[4] = {1, 2, 3, 4};  //定义一个数组
int b = a[0] + 2;  //访问数组中的第一个元素.
int *p = &a[0];    //取第一个元素的地址
printf("第一个元素的地址: %p\n", p);  //打印第一个元素的地址.
printf("a的地址: %p\n", a);  //打印a
printf("p指向的地址的值: %d\n", *p); //打印p指向的地址的值
printf("a指向的地址的值: %d\n", *a); //打印a指向的地址的值
结果:
panyiwen@god:~/temp$ ./a.out 
第一个元素的地址: 0x7ffc259b5b20
       a的地址: 0x7ffc259b5b20
p指向的地址的值: 1
a指向的地址的值: 1
我们会发现数组a其实就是一个指向数组第一个元素首地址的指针.因为我们定义了int a[4]; 所以系统就会为a开辟一个连续的占4个整型的内存.a就是这段内存的开始地址, 例子:
    int a[4] = {1, 2, 3, 4};  //定义一个数组
    int *p = &a[0];    //取第一个元素的地址
    printf("%d\n", a[1]);  //输出第二个元素
    printf("%d\n", *(p+1));  //同样可以输出第二个元素  
    //输出结果都是2
    //(指针类型+1)指加一个同样类型的长度, int 类型的p+1 相当于后移动4位, char 类型的p+1就相当于移动了1位

假设a的地址就是1000, 那么p+1就是1004, 也就是第二个元素的首地址, 这样我们就应该明白了,所谓的一对中括号[]运算符, 就是指针走到第n个位置并取出里面的值.只是c语言进行了封装.举个例子验证一下:
    int a[4] = {1, 2, 3, 4};  //定义一个数组
    int *p = &a[0];    //取第一个元素的地址
    printf("%d\n", a[1]);  //输出第二个元素
    printf("%d\n", p[1]);  //同样可以输出第二个元素  相当于*(p+1)
一个指针也同样可以使用[]运算符. 输入结果和之前是一样的.
二维数组与指针:
二维数组:int a[2][2] = {{1, 2}, {3, 4}}; 在内存中仍然是开辟4个连续的int型空间.但是和int a[4] = {1, 2, 3, 4} 在存储上没什么区别, 但是意义却不同. int a[2][2]; 可以理解为:a[0] -> {1, 2}a[1] -> {3, 4}a[0], a[1] 均为指针变量.而a - > int *tmp[] = {a[0], a[1]}; tmp为存放指针的数组, 所以a是一个指向指针数组的指针.
重点: 但是a中存放的并不是一个新的指针数组(tmp)所在的地址, 而是原来数组的首位地址,所以如果我们用int **p来指代a是不对的.
//所以下面两个其实是不一样的.
int a[2][2] = {{1, 2}, {3, 4}};
int **p = a;   //a是第一个元素的首地址, 所以a == &a[0][0]
//所以*p == *(&a[0][0]) == a[0][0] = 1
//所以**p == *1, 取地址为1中的数据, 就会出现错误.
//正确用法其实是: 
int *p = a; // 然后根据二维数组的位置直接取就可以了.
int b = *(p+1*2+0)+1; // 相当于 int b = a[1][0] + 1;
//而a就是我们理解指向指针数组的指针. 
printf("%p\n", *(a+1));
printf("%p\n", a[1]);
//这两者是一样的.

结构体与指针

结构体对于C来说非常重要,学C不可避免的要学习结构体, 而结构体和指针也是息息相关的.
C语言结构体内嵌指针实现面向对象, C语言是面向过程的,但是用C语言写出的linux系统是面向对象的。 非面向对象的语言,也可以实现面向对象的代码。只是说用面向对象的语言来实现面向对象要更加简单、直观一些.而C语言实现起来复杂而已.
结构体中的指针:
在结构体内部定义指针, 和普通指针一样使用.
struct ln{
    int *num;    //定义一个结构体中的指针.      
};
结构体指针:
一个结构体类型的指针,用法和普通结构体差不多,不过访问元素用'->' 记得先分配内存再使用!
struct node{
    int data;   //普通整型
    int *next;  //结构体内部指针.
} *no;  //可以在此处定义一个全局的结构体指针变量.

//在main中的定义:
int main(){
    struct node *mynode;   //定义在main中的局部结构体指针变量

    //指针访问内部元素时候是用->, 使用前记得先申请一个内存,
    no = (struct node *)malloc(sizeof(struct node));  
    no->data = 1;  

    //或者
    struct node no1;  //定义一个非指针类型.
    mynode = &no1;
    mynode->data = 2; 
    return 0;
}

函数与指针

传递指针参数:
我们都知道, 函数是可以传递参数的, 如果我们以指针(地址)为参数传递呢?其实这就是指针优秀的地方,我们普通参数的传递,都是拷贝一份数据传给函数的形参.这就涉及到了一个很有名的例子.交换两个数.
void swap(int a, int b){
    int t = a;
    a = b;
    b = t;
}

int main(){
    int a = 1, b = 2;
    swap(a, b);
    printf("a=%d, b=%d", a, b);   //a,b并没有交换. 原因就是上面所说的数据是拷贝一份传给函数的, 实际上的a, b并没有变.
    return 0;
}
如果我们是传递指针参数
void swap(int* a, int* b){
    int t = *a;
    *a = *b;
    *b = t;
}

int main(){
    int a = 1, b = 2;
    swap(&a, &b);
    printf("a=%d, b=%d", a, b);   //a,b交换了. a, b地址传递过去, 那么对于a, b的修改自然影响到了原来的a, b.
    return 0;
}
这个例子在很多书上,百度都会有, 解释的比我要详细很多, 不懂的可以百度一下.所以我们传递一个数组上去时候, 传递的是数组名, 其实也就是一个指针变量, 所以函数中对数组的修改会影响到原来的数组.指针的好处就呼之欲出了, 如果我们的数据很多很大,我们传递一个数据上去, 不需要拷贝一份.只需要一个指针, 4个字节, 8个字节就可以得到数据.加快速度节省空间.只是需要程序员考虑指针会修改原来的数据.
指针函数
返回值是一个指针的函数, 被称为指针函数.
int *func(){   //返回类型后加一个* 
    int *p = malloc(4);   //申请堆区的内存. 使用完记得释放.
    return p;   //返回值一定要是同类型的指针(地址).
}
这样的函数就称为指针函数. 一般是在函数中申请堆区的内存.因为如果是定义一个普通的变量, 在栈区, 由系统自动释放, 那么在函数调用完就释放了. 当然也可以声明为static,放入静态(全局)区.
函数指针
指向函数的指针变量, 其本质是一个指针变量. 其实函数也是一个存储在计算机内存中的(代码区), 既然是在内存中, 就有地址.写法: 返回值类型 (*函数名) (参数);举个例子:
int max(int a, int b){
    return a > b ? a : b;
}
int main(){
    int (*pmax)(int, int);  //声明一个函数指针
    pmax = max;  //赋值    // pmax = &max;  //也可以这样写.
    int z = pmax(3, 4);    // int z = (*pmax)(3, 4);  //也可以这样写
    printf("%d\n", z);    //输出4
    return 0;
}
函数名其实就是函数在内存中的首地址, 知道这个就可以给函数指针赋值.由于函数指针也是一个变量, 所以我们的struct中也就可以放函数指针,使得结构体也可以调用方法.
int max(int a, int b){
    return a > b ? a : b;
}

struct ln{
    int data;
    int (*pmax)(int, int);  //声明一个函数指针
};

int main(){
    struct ln t;
    t.pmax=max;
    printf("%d\n",t.pmax(3, 4));  //结构体调用方法.
    return 0;
}
感谢: http://nevel.cnblogs.com/p/6370264.html

查看原文: http://www.welcom212.com/wordpress/?p=310
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值