1.初识指针
构造一个指针
int *p
p是变量的名称
int*
表示p存放的是int类型变量的地址
double *q
q是变量的名称
double*
表示q存放的是double类型变量的地址
变量类型 *指针变量
用这种形式来定义存放各种类型变量的地址
指针就是地址,地址就是指针
地址就是内存单元的编号值
指针变量就是存放地址的变量
通俗来说就是:
杯和水的关系
放水的杯子
放东西的东西
指针和指针变量是两个不同的概念
但是通常我们叙述时,会把指针变量简称为指针
int i;//普通变量
int *p;//指针变量
p = &i;
&:取地址符
*p就是以p的内容 (i的地址)为地址的变量
指针的重要性
1.指针表示一些复杂的数据结构
2.快速地快递数据,减少内存的消耗
(结构体输入输出时优势可见)
3.使函数返回一个以上的值
4.能直接访问硬件
5.能够方便的处理字符串
6.是理解面向对象语言中引用的基础
指针的定义
地址:
- 内存单元的编号
- 从零开始的非负整数
- 范围:4G【0—4*1024-1】
C —— **控制总线**控制方向————————— 内
P —— **数据总线**实现传递———————— 存
U —— **地址总线**(2^32)确定位置—— 条
指针:
-
指针就是地址,地址就是指针
-
指针变量就是存放地址的变量,或者说是存放内存单元编号的变量
-
指针的变质就是一个操作受限的非负整数
-
指针只能做减法运算
编号只有减法才有意义 即门牌号相减
*的含义
- 乘法
- 定义指针变量
int*p;
//定义了一个名字叫p的指针变量,int*p表示只能存放int类型的地址
- 指针运算符 <=> 取地址的逆运算符
p = &i
保存了i的地址
*p = i
地址索引发现p指向i
该运算符放在已经定义好的指针变量的前面
如果p是一个已经定义好的指针变量,则*p表示 以p的内容 为地址的变量
如何通过被调函数修改主调函数普通变量的值
void swap_1(int a ,int b){
int t;
t=a;
a=b;
b=t;
//发生改变的是形参的ab 运行完被释放了
return a;
}
void swap_2(int*p,int*q){
int *t;//如果要呼唤p和q的值,t必须是int*,不能是int
t=p;
q=t;
p=t;//互换家庭地址?但里面的人没有变
}
void swap_3(int*p,int*q){
//如果要互换p和q的值,t必须是int,不能是int*
int t=*p;
*p=*q;
*q=t;
}
- 实参必须为普通变量的地址
- 2形参必须为指针变量
- 在被调函数中通过 *形参名 的方式就可以修改主调函数相关变量的值
int main(){
int a=3;
int b=5;
swap(a,b);
printf("a=%d b=%d",a,b);
return 0;
}
指针的分类
- 1.基本类型指针
- 2.指针和数组
- 3.指针和函数
- 4.指针和结构体
- 5.多级指针
{
int i = 5;
int* p;
int* q;
*p = i;
//程序定义了p i这两个空间,用5修改读取了一个不属于本程序的单元
p = &i;
*q = p;//*q是整型类型,p是指针类型 类型不一致 语法错误 报错
*q = *p;//error p里面没有赋值
p = q;//error q是垃圾值,q赋给p,p也变成垃圾值
q的空间是属于本程序的,所以本程序可以读写q的内容,
但是如果q内部是垃圾值,则本程序不能读写*p的值
因为*q所代表的内存单元的控制权限并没有分配给本程序
}
2.指针和数组
指针和一维数组
一维数组名
一维数组名是个指针常量
它存放的是一维数组第一个元素的地址
下标和指针的关系
如果p是一个指针变量,则
p[i] 永远等价于 *(p+1)
确定一个一维数组需要几个参数
需要两个参数:
1.数组第一个元素的地址
2.数组长度
//f函数可以输出任何一个一维数组的内容 (遍历!)
void f(int *pAr,int len) {
/*
/0是字符串结束的标志
但是在数组中 每一个值都可能是有效的值
所以需要 一个初始的单元告诉你是什么 再确定长度
*/
int i;
for (i = 0; i < len;++i) {
printf("%d\t", *(pAr + i)); //等价于pAr[i]
}
}
int main() {
int a[5] = { 1,2,3,4,5 };
f(a,5);
return 0;
}
指针变量的运算
-
指针变量不能相加 不能相乘 不能相除
-
如果两个指针变量指向的是同一块联系空间中的不同的存储单元,则这两个指针变量才可以相减
-
门牌号要在同一个小区 才有意义!
一个指针变量到底占几个字节?
- sizeof
-
sizeof(数据类型)
功能:返回值就是该数据类型所占的字节数
例子:sizeof(char)=1
sizeof(int)=4
sizeof(double)=8
-
sizeof(变量名)
int i ; sizeof(i)
功能:返回值就是该变量所占的字节数
两种方法都可
p 指向char类型变量(1个字节)
p 指向int类型变量(4个字节)
p 指向double类型变量 (8个字节)
p q r本身所占的字节是否一样 ?
答案:一样
语言的定义本身就是有意义的
int main(void) {
char ch = 'A';
int i = 99;
double x = 66.6;
char* p = &ch;
int* q = &i;
double* r = &x;
printf("%d %d\n",sizeof(ch),sizeof(p));
printf("%d %d\n",sizeof(i), sizeof(q));
printf("%d %d\n",sizeof(x), sizeof(r));
}
8位=1个字节 32位=4个字节
每个地址都用32根地址总线表示 即4字节
地址用4个字节表示
不管是 输出最大的地址还是 输出最小的地址
这32根线都是会输出的(0或者)1
不管是输出最大还是最小,这32根线都是要输出的
32bit换算成字节为4
-
一个指针变量,无论它指向的实际变量占几个字节,该指针变量本身只占4个字节
-
一个变量的地址是由该变量的首字节的地址来表示
如何通过指针改变数组某个位置上的值
void f(int *pAr,int len)
{
pAr[3]=88;
}
int main()
{
int a[5] = { 1,2,3,4,5 };
printf("%d\n",a[3]);
f(a,5);
printf("%d\n",a[3]);
return 0;
}
指针和二维数组
int main() {
int a[5];
//a是数组名 5是数组元素的个数 元素就是变量 a[0]--a[4]
int a[3][4];
//a是数组名 3*4=12是数组元素的个数
//元素是a[0][0]--a[2][3]
//a=b;//a是常量,不可赋值
printf("%#x", &a[0]);
//#x以十六进制输出
printf("%#x", a);
return 0;
}
- a[ 0 ][ 0 ]是第一个元素
a[ i ][ j ]是 第i+1行 第j+1列
多级指针(指针套娃)
int i = 10 ;
int *p = &i ;
p是int*
类型
只能存放int
类型变量的地址
int **q = &p;
q是int**
类型
所谓int**
类型是指q只能存放int*
类型变量的地址
int ***r = &q;
r是int***
类型
所谓int***
类型是指r只能存放int**
类型变量的地址
printf("i=%d\n",*p); //i=10
printf("i=%d\n",**q); //i=10
printf("i=%d\n",***r); //i=10
动态内存分配【重点难点】
静态内存 (以传统数组为例)的缺点
-
数度长度必须事先制定,且只能是常整数,不能是变量
int a[5]; //ok int len=5; int a[len]; //error
-
传统形式定义的数组,该数组的内存程序员无法手动释放
数组一旦定义,系统为给数组分配的存储空间就会一直存在, 除非该数组所在的函数运行结束
在一个函数运行期间,系统为该函数所分配的空间会一直存在,直到该函数运行完毕时,数组的空间才会被系统释放
void g(int* pAr, int len)
{
pAr[2] = 8;//pAr[2]==a[2]
}
void f(void)
{
int a[5] = { 1,2,3,4,5 };/*
20个字节的存储空间程序员无法手动释放它
它只能在本函数运行完毕时由系统自动释放
*/
g(a, 5);
printf("%d\n", a[2]);
}
- 数组的长度不能在函数运行的过程中动态的扩充或缩小 数组的长度一旦定义,其长度就不能再改变
- A函数定义的数组,在A函数运行期间可以被其他函数使用 但A函数运行完毕后,A函数的数组将无法在被其他函数调用,传统定义的数组不能跨函数使用
为什么需要动态分配内存呢?
- 动态数组很好地解决了传统数组的四个缺陷(传统数组也叫做静态数组)
动态内村分配举例_ 动态数组的构造
#include<stdio.h>
#include<malloc.h>//插入malloc的头文件
int main(void)
{
int i = 5;//分配了4个字节 静态分配
int* p = (int*)malloc(4);//强制类型转化,值有 类型有
*p = 5;//*p就是一个int变量,只不过*p这个整型变量的内存分配和int i的分配方式不一样
free(p);
/*free(p) free point 表示把所指向的内存给释放掉
p本身的内存是静态的,不能由程序员手动释放,
p本身的内存只能由p变量所在的函数运行终止时由系统自动释放*/
return 0;
}
free()
函数原型
void free(void *p);
释放可以节约内存,保证内存的安全管理
1.要使用malloc函数,必须添加malloc.h这个头文件
malloc 是memory(内存)allocate(分配)的缩写
2.malloc函数只有1个形参,并且形参是整型类型
3. 4实参表示请求系统为本程序分配4个字节
4. malloc函数 只能返回一个字节的地址
5. 分配了8个字节(p变量占4个字节,p指向的内存也占了4个字节)
6. p本身所占的内存是静态的,p所指向的内存是动态的
#include<stdio.h>
#include<malloc.h>//插入malloc的头文件
/*
malloc 是memory(内存)allocate(分配)的缩写
*/
void f(int*q)//q是p的一份拷贝一份副本
{
*q = 200;
//free(q);//把q指向的内存释放掉
}
int main(void)
{
int i = 5;
//分配了4个字节 静态分配
int* p = (int*)malloc(sizeof(int));
//sizeof(int)的返回值是int所占的字节数
*p = 10;
printf("%d\n", *p);
f(p);
printf("%d", *p);
return 0;
}
构造一维数组
int a[5];//如果int占4个字节的话,则本数组总共含有20个字节,每4个字节相当于1个int变量来使用
int* p;
int len;
printf("请输入你要存放的元素的个数:");
scanf("%d", &len);
p = (int*)malloc(4 * len);
//相当于 int p[len]; 这一维数组的长度是len,该数组的每个元素都是int类型
q=(double*)malloc(8 * len);
realloc(p,100);
再分配 将内存字节改为100 动态地改变内存字节数
realloc动态地扩充 动态地缩小
对动态一维数组进行赋值
for (i = 0; i < len; ++i) {
scanf("%d", &p[i]);
}
对动态一维数组进行输出
for (i = 0; i < len; ++i) {
print("%d", p[i]);
}
free (p);
可以手动释放掉动态分配的数组,静态数组则不可
输入输出操作上等同于静态数组
跨函数使用内存的问题
动态内存和静态内存的比较
- 静态内存是由系统自动分配,由系统自动释放
静态内存是在找分配的 - 动态内存是由程序员手动分配,手动释放
动态内存是在堆分配的
跨函数使用内存的问题
- 动态内存可以跨函数运作
- 动态内存在函数执行完毕之后仍然可以被其他函数使用
- 静态内存不可以跨函数运作
- 静态内存在函数执行期间可以被其他函数使用,执行完毕之后就不能再被其他函数使用了
{
int i = 5;
//q是p的地址,那么*q=p
*q = &i;
// *q=i error 等价于p=i,这样写是错误的,因为i是int类型,p是int*类型
}
int main() {
int* p;
f(&p);
//要想改变p的值,必须发送p的地址给f函数
printf("%d", *p);
//本语句语法没有问题,但逻辑上有问题,访问了一个不该被访问的地址
return 0;
}