C语言(五)

指针的概念

指针就是内存中的地址,地址是内存单元的编号

int * p;//p是变量的名字,int *表示p变量存放的是int类型变量的地址(int *是p变量的数据类型)
int i =3;
p = &i;//p保存的是i的地址,因此p指向i,p的值与i的值互不影响

注:如果一个指针变量指向了某个普通变量,则* 指针变量就完全等同于普通变量

int * p;
int i =3;
p = &i;
j = *p;//*p相当于i,所以j=i
//结果为 j =3
//*p就是以p的内容为地址的变量

指针和指针变量

指针是地址,指针变量是存放地址的变量,指针和指针变量是两个不同的概念,但是叙述时会把指针变量简称为指针,实际它们含义不一样。

一个指针变量占几个字节

sizeof(数据类型):返回值是该数据类型所占的字节数
sizeof(变量名):返回值是该变量所占的字节数

sizeof(int)=4   sizeof(char)=1 sizeof(double)=8
  • 假设p指向char类型的变量(1个字节)
  • 假设q指向int类型的变量(4个字节)
  • 假设r指向double类型的变量(8个字节)
  • pqr大小均为4个字节(pqr只存地址的第一个字节,但CPU与内存间有32根地址线,地址表示位有32位)

注:一个指针变量,无论它指向的变量占几个字节,该指针变量本身只占四个字节

多级指针

int i =10;
int * p = &i;
int ** q = &p;//q是存放int*类型变量的地址的指针
int *** r = &q;//r是存放int**类型变量的地址的指针

r = &p;//error,r只能存放int**类型的变量的地址

指针的分类

基本类型指针

int * p;
int i=5;
p = &i;

注:在C语言中NULL指针定义为0

基本类型指针的常见错误分析

int * p;
int i =5;
* p =i;//error,相当于将p指针指向的int变量赋值成i,但p指针指向的int变量的控制权限并没有分配给本程序
int i =5;
int * p;
int * q;
p =&i;
//* q =p;//error,*q为int类型,而p为int*类型
//* q = *p;//error

注:q的空间是属于本程序的,所以本程序可以读写q的内容,但如果q的内部是垃圾值,则本程序不能读写* q的内容,因为* q所代表的内存单元的控制权限并没有分配给本程序

指针和数组

指针和一维数组

数组名

一维数组名是一个指针常量,存放的是第一个元素的地址

下标和指针的关系

如果p是个指针变量,则p[i]永远等价于* (p+i)

//通过函数输出数组的内容
void pArr(int * p,int len){
    //p为数组名,len为数组大小
    //a[0]=*(a+0),a[2]=*(a+2)
    for(int i=0;i<len;i++){
        printf("%d",*(p+i));
    }
    printf("\n");
}
int main(void){
    a[4]={1,2,3,4};
    b[6]={1,2,3,4,5,6};
    c[100]={1,2,34};
    pArr(a,4);
    pArr(b,6);
    pArr(c,100);
    return 0;
}
指针变量的运算
  • 指针变量不能相加,不能相乘,不能相除
  • 如果两个指针变量指向的是同一块连续空间中的不同存储单元,则这两个指针变量可以相减
    int i =5;
    int j =10;
    int * p =&i;
    int * q =&j;
    p-q;//没有实际意义
    int a[5];
    int * p;
    int * q;
    p = &a[2];
    q = &a[4];
    printf("p和q所指向单元相隔%d个单元\n",p-q);

指针和二维数组

指针和函数

通过被调函数修改主调函数普通变量的值

int g(int * i) {
    *i = 1;
    return 0;
}
int main() {
    int a = 2;
    g(&a);
    printf("%d\n",a);//最终输出为1
    return 0;
}

指针使函数返回一个以上值

int g(int * p,int * q){
    *p = 1;
    *q = 2;
}
int main(void){
   int a=3,b=4;
   g(&a,&b);
   printf("%d/n",a,b);//a=1,b=2
   return 0;
}

指针和结构体

动态内存分配

传统数组的缺点(静态数组)

  • 数组长度必须事先指定,且只能是常熟,不能是变量
  • 传统形式定义的数组内存无法手动释放,只能在函数运行完毕时由系统自动释放
  • 数组的长度不能在函数运行的过程中动态的扩充或缩小
  • A函数定义的数组,在A函数运行期间可以被其他函数使用,但A函数运行完毕后,A函数中的数组将无法再被其他函数使用(不能跨函数使用)

为什么需要动态分配内存

动态数组很好的解决了传统数组的4个缺陷

### 动态内存分配举例(动态数组的构造)

malloc函数的使用

malloc 是memory allocate(内存分配)的缩写

int i = 5;//静态分配4个字节
int * p = (int *)malloc(4);//4表示请求系统为本程序分配4个字节
//一个分配了8个字节,p占用4个字节,p所指向的内存也占4个字节
//p本身所占的内存是静态分配的,p所指向的内存是动态分配的

* p =5;
free(p);//表示把p所指向的内存释放掉,p本身的内存是静态的,只能在p变量所在函数运行终止时由系统自动释放

注:1.使用malloc函数必须加上malloc.h头文件
2.malloc函数只能返回第一个字节的地址
3.malloc函数只有一个形参,并且形参是整形

动态构建一维数组

int * pArr;
int len;
int i;
printf("请输入数组元素个数:");
scanf("%d",&len);
pArr = (int *)malloc(4*len);//每4个字节代表一个元素(int为4个字节)

//对动态一维数组进行赋值
for(i=0;i<len;i++){
  scanf("%d",pArr+i);
}
//对动态一维数组进行输出
for(i=0;i<len;i++){
  printf("%d\n",*(pArr+i));
}

realloc(pArr,100);//把pArr指针所指向的内存扩大到100个字节

free(pArr);//释放pArr所指向的内存

静态内存和动态内存的比较

静态内存

  • 静态内存是由系统自动分配的,由系统自动释放
  • 静态内存是在栈中分配的

动态内存

  • 动态内存手动分配,手动释放
  • 动态内存是在堆中分配的

跨函数使用内存的问题

void f(int ** q){
    int i = 5;
    *q = &i;
}
int main(void){
  int * p;
  f(&p);
  printf("%d\n",*p);//error,语法无错误,但是i变量的内存在f()函数执行完毕后已经被释放(i已经不存在),此时*p指向的并不是i
  return 0;
}
void f(int ** q){
  * q = (int *)malloc(sizeof(int));
  ** q = 5;//等价于*p=5
}
int main(void){
   int * p;
   f(&p);
   printf("%d\n",*p);//OK,动态分配的内存是手动释放,f()中的** q在函数执行完毕后依然存在
}

   转载规则


《C语言(五)》 fightingtree 采用 知识共享署名 4.0 国际许可协议 进行许可。
  目录