c语言指针

当时自己看《郝斌C语言》记录的课程笔记,并不用来做交流单纯的作为记录而已。有兴趣的朋友去看视频(提示:视频比较早,适合自己的最重要)

21/6/17

指针初次简单介绍
指针为C语言的灵魂,高效,强大,且危险。
指针就是地址,地址就是内存单元的编号

include <stdio.h>

Int main ()
{
int p; //P是变量的名字,int表示P变量存放的是int类型变量的地址
// 例如 P=&i;将整型变量i,的地址给变量P。 不能写成P=i;
int i=3; //如果定义一个 double*P 则不能将i赋给P,此时P只能存储double 类 //型的变量的地址

p=&i; //正确
p= i; //错误,p只能存放int类型变量的地址,不能存放int类型变量的值。
p=55; //错误,55也是整数

}

include <stdio.h>

Int main ()
{
int p;
int i=3;
p=&i; //p保存了i的地址,因此p指向i
//p不是i,i也不是P,修改i的值不会影响p的值,修改p的值也不会影响i
//的值
//指针变量能够存放其他变量的地址,普通变量只能存放其他变量的值
//如果一个指针变量指向某个普通变量,则
指针变量就完全等同于普通变量
//如果p是个指针变量,并且p存放普通变量i的地址,则p指向了普通变量 // i。
//则规定p完全等同于i。即所有出现p都可替换为i,同样出现i的地方
//也可以用p替换
//int
为数据类型,p为变量名。
//而p存放的int类型变量的地址
//可以理解为int为变量的类型,*表示地址,p为变量。P只能存放整形的变//量的地址,所以p=&i;

在这里插入图片描述

}

include <stdio.h>

Int main ()
{
int*P;
int i =3;
int j;
P=&i; j=*p;
Printf(“i=%d,j=%d”,i,j); //i,和j都等于3

}

*P代表以P的内容为地址的变量的值
指针和指针的变量的区别:
指针就是地址,地址是内存单元的编号
指针变量是存放地址的变量
指针和指针变量是两种不同的概念
但是要注意,通常叙述时把指针变量称呼为“指针”,实际是不一样的。

指针的重要性
指针可以用来表示一些复杂的数据结构(需要学数据结构)
快速的传递数据
使函数返回一个以上的值
能直接访问硬件
能够方便的处理字符串
指针是理解面向对象语言中引用的基础(学习JAVA)
指针是C语言的灵魂
指针的分类:
基本类型指针
指针和数组的关系
指针和函数的关系
指针和结构体的关系
多级指针

什么是地址
从零开始的非负整数
地址的范围4G {}0 - (4G-1)}
CPU可直接处理内存条

通过控制线控制CPU对内存条进行写或读
数据线用于数据的传输
地址线用于确定地址
在这里插入图片描述

内存中最小的单元为一个字节,可以同时操作8个0或者8个1
CPU如果地址线只有一根线,则只有两个状态,0或1
2根线,可以控制4个
3根线,控制8个

CPU的地址线一般的有32根地址线,所以可以控制2的32次方个单元,而每个单元为8位
1k=2的10次方b
1m=2的10次方kb=2的20次方b
1G=1024m=2的30次方B
而2的32次方等于2的30次方乘2的平方等于4G,所以一般的机器内存为4G

什么是指针
指针就是地址,地址就是指针
指针变量就是存放内存单元编号的变量,或者说指针变量就是存放地址的变量
指针的本质是一个操作受限的非负整数
指针相当于两个门牌号,两个门牌号是不能乘,不能加,不能除、、
但是可以相减

星号的三种含义
星号的用法:
1乘法
2定义指针 intp int表示p只能存放int变量的地址
3指针运算符,该运算符放在已经定义好的指针变量前面
如果p是一个已经定义好的指针变量,则*p是以p的内容为地址的变量

复习前面所有的指针知识

include<stdio.h>

Int main ()
{
Int p;
Int i=5;
P=5; //错误,p为int
类型的地址变量
Char ch=’A’; //如果要表示单个字符,要用单引号括起来,如果是字符串用双引号
P=&ch; //错误,ch不是整型变量地址
P=&i; //正确,*p以P的内容为地址的变量
*p=99;
Printf(“i=%d\n”,i,*p); //结果都是99

Return 0;
}

include <stdio.h>

Void swap_1(int i,int j)
{
Int t;
T=a;
A=b;
B=t;

}

Int main()

{
Int a=3;
Int b=5;
Swap_1(a,b);
Printf(“%d,%d”,a,b); //并不会改变a,b的值

}

检测实参和形参是不是同一个变量

include <stdio.h>

Void f(int i)
{
I=99;

}

Int main ()
{
Int i =6;
Printf(“i=%d\n”,i);
f(i);
Printf(“i=%d\n”,i); //结果还是6,即便实参和形参的名字一样,实参和形参不是同一//个。
Return 0;
}

include <stdio.h>

Vodi swap_2(intp,intq) //由p和q接收a,b的地址,不能把a,b的值给p,q
{
Int t;
T=*p;
*p=*q;
*q=t;

}
Int main ()
{
Int a =3;
Int a=5;
Swap_2(&a,&b);
Printf(“%d,%d”,a,b);

Return 0;
}

指针使函数返回一个以上的值举例——1

include<stdio,h>

Int f(int i,int j) //如果不使用指针,是无法改变a,b的值,除非使用return。但无法同时使//用return 返回多个值 ,最多改变一个值
{
Return 100;
Return 88; //return 结束被调用函数,返回主调函数

}

Int main()
{
Int a=3;
Int b=5;
A= f(a,b);
B=f(a,b);

}

include<stdio,h>

Int f(int p,int q) //
{
*p=1;
*q=2;

}

Int main()
{
Int a=3;
Int b=5;
f(&a,&b);
Printf(“%d,%d”,a,b);
}

如何通过被调函数修改主调函数普通变量的值
1实参必须为普通变量的地址
2形参必须是指针变量
3在被调函数中通过“*形参名”的方式,就可以修改主调函数普通变量的值。

一维数组和指针的关系
数组名

include<stdio.h>

Int main ()
{
A[5]; //a是数组名,5是数组元素的个数,a[0]–a[4]
A[3][4]; //3行4列,a[0][0]是第一个元素。A[i][j]是i加一行,J加一列
A[3]=5;

RETURN 0;
}

下标和指针的关系
一维数组名是一个指针常量,他存放的是一维数组第一个元素的地址
如果p是一个指针变量:则p[i]==*(p+i);

include<stdio.h>

Int main ()
{
A[5]; //a是数组名,5是数组元素的个数,a[0]–a[4]
A[3][4]; //3行4列,a[0][0]是第一个元素。A[i][j]是i加一行,J加一列
A[3]=5;
A=b; //错误,首先a是一个常量,这里的a是一个地址
Printf{“%#x”,&a[0]}; //int 用%d, long int 用%ld , char 用c , float 用f , double // 用lf ,八进制用o,十六进制用#x
Printf{“%#x”,a}; //输出结果同上
RETURN 0;
}

确定一个一维数组需要两个参数及其原因
(如果函数要处理一个一维数组,需要该数组的哪些信息)

include<stdio.h>

Void f(intparr,int len ) //可以输出任何一个一维数组的内容
//形参应该为int
类型,因为数组名,存放的是数组的首地址。
{
Int i;
For(i=0;i<len;i++)
{
Printf(“%d”,*(parr+i));

}
Printf(“\n”);
*parr; //就是数组的第一个元素
*(parr+1); //字符串结束有一个标志“\0”,所以只需要首地址就可以使用。而数组//没有结束标记
F(a,5); //确定一个数组需要首地址和数组的长度 ,缺一不可
}
Int main()

{
Int a[5]={1,2,3,4,5}; //没有赋值的数组都为垃圾直,如果赋值但没完全赋值的那些为0值
Int b[6]={-1,-2,-3,-4,-5,-6};
Int c[100]={1,99,22,33};
f(a,5);

Return 0;
}

include<stdio.h>

Void f(int*parr,len) //需要数组的首地址,和数组的长度
{
Parr[3]=88;
在这里插入图片描述

}
Int main ()
{
Int a[6]={1,2,3,4,5,6};
Printf(“%d”,a[3]); //结果为4
F(a,6);
F(a,1000); //下标越界,错
Printf(“%d”,a[3]); //a[3]parr[3];而a[3](a+3)=(parr+3)结果就是88

Return 0;

}

include <stdio.h>

Void f(intparr ,int len)
{
Int i;
For(i=0;i<len;i++)
{
Printf(“%d”,
(parr+i)); //(parr+i)==parr[i]; 也等价于b[i] ==(b+i)

}

}

Int main()
{

Int a[5]={1,2,3,4,5}; //没有赋值的数组都为垃圾直,如果赋值但没完全赋值的那些为0值
Int b[6]={-1,-2,-3,-4,-5,-6};
Int c[100]={1,99,22,33};
F(b,6);
Return 0;

}

复习课

include<stdio.h>

Void f(intparr,int len )
{
Parr[2]=10; //parr[2]==
(parr+2)==*(a+2)==a[2];

}
Int main ()
{
Int a[5]={1,2,3,4,5};
Printf(“%#x,%#x”,a,a[0]); // a==&a[0];
A=&a[2]; //错误, 因为a是一个常量
Printf(“%d”,a[2]); //结果为3

f(a,5);
Printf(“%d”,a[2]); //结果为10
}

include<stdio.h>

Void f(int*parr,int len )
{
Int i;
For(i=0;i<len;i++)
{
Printf(“%d”,parr[i]);

}
}
Int main ()
{
Int a[5]={1,2,3,4,5};

f(a,5);

Return 0;
}

include <stdio.h>

Void f(k);
{

。。。。。。。。。 //这种写法,形参和实参不是同一个变量,是无法改变实参的数
}
Int main ()
{
Int i =5;
F(i);
Printf(“%d”,i);
Return 0;

}

include <stdio.h>

Void f(int*p);
{
*p=10; //等价于i=10;

}
Int main ()
{
Int i =5;
F(&i);
Printf(“%d”,i);
Return 0;

}

指针变量的运算
指针变量不能加,乘,除,可以减
如果两个指针变量指向的是,同一块连续空间中的不同存储单元,则这个指针才可以用减法。
类似于两个门牌号相减

include<stdio.h >

Int main ()
{
Int i =5;
Int j =10;
Int *p =&i;
Int *q=&j; //p-q无实际意义,且 p和q 不是连续的空间

Int a[5];
P=&a[1];
Q=&a[4];
Printf{“p和q所指向的单元相隔%d个单元”,p-q};
Return 0;

}

一个指针变量到底占几个字节
预备知识: sizeof(数据类型) 返回值就是该数据类型 所占的字节数
比如 sizeof(int)=4; sizeof(cahr)=1; sizeof(double)=8
Sizeof(变量名) 返回值是该变量所占字节数

假设p指向char 类型变量(char为1个字节)
假设q指向int类型变量(int 为4个字节)
假设r指向double类型变量(double为8个字节)
问:P,q,r本身所占字节数是否一样?
答:P,q,r本身所占字节数是一样的,指针统一占用4个字节
即无论指针变量指向那种类型的变量,该指针变量本身只占4个字节
一个变量的地址使用该变量首字节的地址表示

include<stdio.h>

Int main ()
{
Char ch =’a’;
Int i =99;
Double x=66.6;
Char p=&ch;
Int
q=&i;
Double*r=&x;
Printf(“%d,%d,%d\n”,sizeof§,sizeof(q),sizeof®); //一个指针变量无论指向什么变量,//它本身只占4个字节
Return 0;

}

对于地址 ,一个字节一个地址。

在这里插入图片描述
在这里插入图片描述

Double x=66.6;
变量X为doble型占8个字节,有八个编号
上图,如果变量x占8个字节,对于指向他的指针变量p,p使用X的首字节的编号作为变量X整体的地址。
p所指向的,x的“变量类型”决定了他占几个字节,例如 doublep; double说明占8个字节。
p输出为4个字节,说明指针p所存的X的第一个字节的地址需要用4个字节来表示。

为什么是4个字节原因如下所述:
CPU有32根线,总共2的32次方个状态就是4G个状态,4g个状态就是4G个单元。不同的状态确定不同的地址
每个内存单元由32根线表示,每8个一个字节,所以是4个字节。
CPU的地址线一般的有32根地址线,所以可以控制2的32次方个单元,而每个单元为8位
1k=2的10次方b
1m=2的10次方kb=2的20次方b
1G=1024m=2的30次方B
而2的32次方等于2的30次方乘2的平方等于4G,所以一般的机器内存为4G

动态内存分布概述
传统数组的缺点:
1数组的长度必须事先指定,且只能是常整数。例如:4,5,6
不能是变量
例如 int a[5]; //正确
Int len =5;
Int a[len]; //错误 ,不可以是变量
2传统形式定义的数组,该数组的内存程序员无法手动释放。
数组一旦定义,系统为该数组分配的存储空间一直存在,除非该数组所在的函数运行结束。或者说,在一个函数运行期间,系统为该函数中数组所分配的内存空间会一直存在,直到该函数运行完毕时,数组所占空间才会被系统释放。

include <stdio.h>

Void f()
{
Int a[5]={1,2,3,4,5}; //这二十个字节的存储空间,程序员无法手动释放。
//只能在本函数运行结束后,由本函数自行释放

}
Int main ()
{

Return 0;
}

3数组的长度不能在函数运行的过程中动态的扩充或缩小。(一旦定义好数组的长度,运行过程中不能更改)
4 A函数定义的数组在A函数运行期间,可以被其它函数调用。但A函数运行结束,A函数定义的数组无法被其他函数调用。
传统方式定义的数组不能跨函数使用

include <stdio.h>

Void g(int*parr,int len)
{
Parr[2]=88; //parr[2]=a[2]

}
Void f()
{
Int a[5]={1,2,3,4,5}; //这二十个字节的存储空间,程序员无法手动释放。
//只能在本函数运行结束后,由本函数自行释放

G(a,5); //数组名即数组的首地址
Printf(“%d”,a[2]); //f函数一旦运行结束,数组a所占内存会被释放,无法调用
}
Int main ()
{
F();

Return 0;
}

为什么需要动态内存分配
动态内存解决了传统(静态)数组的问题

malloc函数的使用概述
M-momery内存 a-allocate分配

include <stdio.h>

include<malloc,h> //要是用malloc这个函数必须使用此头文件,不能省略

Int main()
{
Int i =5; //静态分配
Int p=(int )malloc(4); //这条语句说明,p变量占4个字节,并指向了malloc分配的//4个字节的动态内存地址,p为静态分配的内存,malloc分配的为//动态内存
//此处分配了8个字节,p变量占4个字节,p指向的内存//也占4个字节
//malloc只有一个形参,并且形参为整形
//4表示请求系统为本程序分配4个字节
//malloc函数只能返回第一个字节的地址。
//(int* )malloc(4); 可以这样理解,malloc总共分配了4个字节的 //内存,因为malloc只返回第一个字节的地址,所以需要强制类//型转换,以int作为一个单位
/

比方说 (int*)malloc(100); malloc总共分配了100个字节的内存地址,int限定这100个内存,按炸4个字节4个字节的方式分配。也就是25个变量。如果是char则按一个字节一个字节的方式分配,即100个变量。也就是说前面的强制类型转换限定了数据类型,后面的整数限定了内存个数。

只要是数据类型加变量名即为静态分配。例如int i =1;
而malloc函数就是动态分配

*/
*p=5;//*p代表一个整形变量,只不过与变量i的内存分配方式不同

Free§; //free§;表示把p所指向的内存释放掉。p本身的内存是静态的,不能手动释 //放。P变量在所在程序运行结束后才能由系统释放。
Printf(“同志们好!\n”)

Return 0;
}

malloc函数使用方法续

include <stdio.h>

include<malloc.h>

Void f(int*q)
{
p=200; //错误,在这里是无法更改p的内容的,因为p是在main内部定义的
Q=200; //q只能保存int
类型的数据
q=200; //正确
*q=200; //错误,只有指针变量之前可以加
Free(q); //错误,free(q)是把q所指向的内存释放掉,而q指向的正是p所指向的那4 //个字节的内存,如果释放掉内存,p所指向的内存也被释放掉。那么
p
//也将不复存在

}
Int main ()
{
Intp=(int)malloc(sizeof(int )); //sizeof(int)返回值是Int所占字节数
*p=10;
Printf(“%d\n”,p); //结果为10
F§; //p是int
类型
Printf(“%d\n”,*p); //结果为200

Return 0;

}

动态内存分配举例,动态一维数组的构造

include<stdio.h>

include<malloc.h>

Int main ()
{
Int a[5]; //int 占据4个字节,而本数组包含20个字节,每4个字节被当做一个int变 //量来使用。注意a表示第一个元素的地址,而一个元素占4个内存,parr指 //向的也是前四个字节,也是第一个元素。
Int len;
Intparr;
Printf(“请输入你要存放的元素的个数”);
Scanf(“%d”,&len);
Parr=(int
)malloc(4*len); //这里parr指向前四个字节,如果parr+1,则指向后4个字 //节
在这里插入图片描述

//本行动态的构造了一个一维数组,该一维数组的长度是len,该数组的数组//名是parr,该数组的每个元素是int类型。类似于int parr[len]

For(i=0;i<len;++i)
{
Scanf(“%d”,&parr[i]); //对动态一维数组进行赋值
}
Printf(“一维数组的内容是”);
For(i=0;i<len;i++)
{
Printf(“%d\n”,parr[i]);

}
Free(parr); 释放掉parr指向的动态内存

Return 0;
}

Realloc (parr,100); //realloc函数,将parr所指向的内存扩充到100
//如果是把150缩小到100,则会将后50个数据丢失

动态内存和静态内存的比较
在这里插入图片描述

静态内存由系统自动分配,也有系统自动释放
静态内存是在“栈”中分配的。

动态内存由程序员手动分配,手动释放。
动态内存是在“堆”分配的。

多级指针

include <stdio.h>

Int main()
{
Int i =10;
Intp=&i; //p是一个变量,既然是变量就一定有地址。那么能存放地址的就只有地址//类型的变量
Int q=&p;
Int
r=&q;
R=&p; //错误,因为r是int*类型,r只能存放int类型变量的地址
Printf(“%d”,***r);
Return 0;
}

I 地址1000h 内容 10 //i是int类型
P 地址2000h 内容 1000h //p为int*
Q 地址 3000h 内容 2000h //q为int ** 也意味着q只能存放int*类型的地址,而不能存放int的地址。
R 地址 4000h 内容 3000h

在这里插入图片描述

在此说明:*r指的是q,**r指的是p,***r指的是i。结合上图,r指的是以r的内容为地址的变量,所以以3000h为地址的是变量q,r以q的内容为地址的变量,所以以2000h为地址的变量是p,而r是以1000h为地址的变量,也就是i。

include <stdio.h>

F(int**q)
{
//*q就是p

}
Void g()
{
Int i=10;
Intp=&i;
f(&p); //p是int
类型,&p是int**类型
}
Int main()
{
G();
Return 0;
}

复习课

include <stdio.h>

include<malloc.h>

Void f(int*q)
{
*q=10;

}
Void g(int **p)
{

}

Void main ()
{
Int p=(int)malloc(4);

Printf(“*p=%d\n”,p);
G(&p); //p是int
类型 ,&p是int**类型
Printf(“*p=%d\n”,*p);

}

静态变量不能跨函数使用

include<stdio.h>

Voidf(intq) //q是个指针变量,无论q是个什么指针变量都只占4个字节
//q存放的P的地址,意味着q指向p,则*qp
{
Int i=5;
//*q
p; q和
q都不等价于p
q=i; //错误,这样写就意味着p=i;应该写成 p=&i;
q=&i; //q也等于p ,此时
p也就等于i的值,p存放的i的地址

}
Int main ()
{
Int *p;
F(&p);
Printf(“%d\n”,p) //本语句语法没问题,但逻辑有问题
//i是静态内存存放的,当f函数执行结束,变量i的内存释放,变量p依然存在。
//此时
p的值已不再是i=5这个值,p可以保存i此时的地址,而不能访问i的空间内容
Return 0;

}
上面这个程序是错误的,不能这么写。之所以没有报错是因为VC++6.0的原因导致的,他是无法检测到错误。

动态内存可以跨函数使用

include<stdio.h>

include<malloc.h>

Void f(int q)
{
q=(int)malloc(sizeof(int));等价于p=(int
)malloc(sizeof (int))
q=5;//错误
*q=5; //q=p;p存放的是地址变量
**q=5; q存放的p的地址,而
q=p,那么
q就是以q的内容(即p的地址,p存的址即I的地址)为地址。即*q=5即p=5
//静态内容是在栈内部存储的,而动态内存是在堆内部存储的
}
Int main ()
{
Int
p;
F(&p);
Printf(“%d\n”,*q);
Return 0;

}

跨函数使用内存例题
下列程序中能通过调用函数fun(),使main函数中的指针变量p指向一个合法的整形单元的是
A
Main()
{
Intp;
Fun §; //p是变量
}
Int fun(int
p)
{
Int s;
P=&s;
}
B
Main()
{
Int*p;
Fun (&p);
}
Int fun(int**q)
{
Int s;
*q=&s; //由于是静态存储,导致fun运行结束s被释放掉
}

C 正确

include <stdio.h>

Main ()
{
Int*p;
Fun(&p);
}
Int fun(int**q)
{
q=(int)malloc(4);
}
D

include <stdio.h>

Main ()
{
Int*p;
Fun§;
}
Int fun(int**q)
{
q=(int)malloc(sizeof(int));
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值