指针
1.指针的重要性:
表示一些复杂的数据结构;快速的传递数据,减少了内存的耗用;使函数返回一个以上的值;能直接访问硬件,能够方便地处理字符串;是理解面向对象语言中引用的基础。
总结:指针是C语言的灵魂
2.指针的定义:
地址: 内存单元的编号;从零开始的非负整数 指针:
指针就是地址,地址就是指针;
指针变量就是存放内存单元编号的变量,或者说指针变量就是存放地址的变量;
指针和指针变量是两个不同的概念;
但要注意的是:通常我们叙述时会把指针变量简称为指针,实际它们含义并不一样;指针的本质就是一个操作受限的非负整数。
3.指针的分类:
基本类型指针:
int * p:p是变量的名字,int * 表示p变量存放的是int类变量的地址
//int * p;不表示定义了一个名叫*p的变量;
//int* p;应该这样理解:p是变量名,p变量的数据类型是int*类型
//所谓int* 类型,实际就是存放int变量地址的类型
int i = 3;
int j;
p = &i;
-
p保存了i的地址,因此p指向i;
-
p不是i,i也不是p,更准确的说:修改p的值不影响i的值。修改i的值也不会影响p的值;
-
如果一个指针变量指向了某个普通变量,则* 指针变量 就完全等于 普通变量;
-
如果p十个指针变量,并且p存放了普通变量i的地址,则p指向了普通变量i ;
* p就完全等同于i;或者说在所有出现*p的地方都可以替换成i,在所有出现i的地方都可以替换成* p。
附注:
* 的含义:
1.乘法;
2.定义指针变量;
3.指针运算符:该运算符放在已经定义好的指针变量的前面,如果p是一个已经定义好的指针变量,则* p表示以p的内容为地址的变量。
如何通过被调函数修改主调函数普通变量的值:
1.实参必须为该普通变量的地址;
2.形参必须为指针变量;
3.在被调函数中通过
- 形参名 =......
的方式就可以修改主调函数相关变量的值
4.指针和数组:
指针和一维数组:
一维数组名是个指针常量,它存放的是一维数组第一个元素的地址
下标和指针的关系
如果p是个指针常量,则p[i]永远的等价于* (p+i)
确定一个一维数组需要几个参数【如果一个函数要处理一个一维数组,需要确定两个参数:数组第一个元素的地址;数组的长度】
指针变量的运算:
指针变量不能相加,不能相乘,也不能相除;如果两个指针变量指向的是同一块连续空间中的不同存储单元,则这两个指针变量才可以相减。
一个指针变量到底占几个字节:
sizeof(数据类型)
功能:返回值就是该数据类型所占的字节数
- 例子:sizeof(int) = 4 ;sizeof(char) = 1;
- sizeof(double) = 8
sizeof(变量名)
功能:返回值是该变量所占的字节数
-
假设p指向char类型变量(一个字节)
-
假设q指向int类型变量(4个字节)
-
假设r指向double类型变量(8个字节)
总结:一个指针变量,无论它指向的变量占几个字节,该指针变量本身只占四个字节;一个变量的地址是用该变量首字节的地址来表示。
利用指针完成两个数字互换:
#include<stdio.h>
void swap_3(int* p, int* q)
{
int t;
t = *p ; *p = *q ; *q = t;
}
int main(void)
{
int a = 3;
int b = 5;
swap_3(&a,&b);
printf("a = %d, b = %d\n",a , b);
return 0;
}
检测实参和形参是否是用一个变量:
#include<stdio.h>
void f(int * i)
{
*i = 99;
}
int main(void)
{
int i =6 ;
printf("i = %d\n",i);
f(&i);
printf("i = %d\n",i);
return 0;
}
#include<stdio.h>
void f(int i)
{
i = 99;
}
int main(void)
{
int i =6 ;
printf("i = %d\n",i);
f(i);
printf("i = %d\n",i);
return 0;
}
指针使函数返回一个以上的值:
#include<stdio.h>
void g(int * p, int * q)
{
*p = 1;
*q = 2;
}
int main(void)
{
int a = 3,b = 5;
g(&a, &b);
printf("%d %d\n",a,b);
return 0;
}
确定一个一维数组需要两个参数:
#include<stdio.h>
void f(int * parr, int len)
{
int i;
for (i = 0;i <len; ++i)
printf("%d\n",*(parr + i));
printf("\n");
}
int main()
{
int a[5] = {1,2,3,4,5};
int b [6] ={-1,-2,-3,4,5,-6};
int c [100] = {1,99,22,33};
f(a ,5);
f(b, 6);
f(c, 100);
return 0;
}
指针变量的运算:
#include<stdio.h>
int main()
{
int i = 5;
int j =10;
int * p =&i;
int *q = &j;
int a[5];
p = &a[1];
q = &a[4];
printf("p和q所指向的单元相隔%d个单元\n",q-p);
return 0;
}
指针和二维数组:
多级指针:
int i = 10;
int * p=&i; //p只能存放int类型变量的地址
int ** q = &p;//q是int ** 类型,所谓int** 类型就是指q只能存放int* 类型变量的地址;
int *** r = &q;
printf(“i = %d\n”,*** r);
为什么需要动态分配内存:
动态数组很好的解决了传统数组的四个缺陷(传统数组也叫静态数组)
动态内存分配举例,动态数组的构造:
int* p=(int*)malloc(int len);
- malloc函数能且只能返回第一个字节的地址,所以需要把这个无任何实际意义的第一个字节的地址(俗称干地址)转化为一个有实际意义的地址,因此,malloc前面必须加(数据类型*),表示把这个无实际意义的第一个字节的地址转化为相应类型的地址。
如:
int* p=(int*)malloc(50);
- 表示将系统分配好的50个字节的第一个字节的地址转化为int* 型的地址,更准确的说是把第一个字节的地址转化为四个字节的地址,这样p就指向了第一个的四个字节,p+1就指向了第二个字节的四个字节,p+i就指向了第i+1个的四个字节。p【0】就是第一个元素,p【i】就是第i+1个元素。
double* p=(double *) malloc(80);
- 表示将系统分配好的第一个字节的地址转化为8个字节的地址,这样p就指向了第一个的8个字节,p+1就指向了第2个的8个字节,p+i就指向了第i+1的8个字节。
free (p)
- 表示把p所指向的内存给释放掉,p本身的内存是静态的;不能由程序员手动释放,p本身的内存只能在p变量所在的函数运行终止时由系统自动释放;
习题作业
打印结果如下:
2.数组ref有4个元素;
3.数组名ref指向该数组的首元素(整数8); 表达式ref+1:指向该数组的第二个元素(整数4); ++ref不是有效的表达式。
a.* ptr = 12、* (ptr+2) = 16
b.* ptr= 12、* (ptr+2)= 14
12和16
12和14