目录
9. 指针
取地址运算
sizeof 是一个运算符,给出某个类型或变量在内存中所占的字节数:sizeof(int), sizeof(i);
#include <stdio.h>
int main()
{
int a = 6;
printf("sizeof(double)=%ld\n",sizeof(double));
printf("sizeof(int)=%ld\n",sizeof(int));
printf("sizeof(a)=%ld\n",sizeof(a));
return 0;
}
结果:sizeof(double)=8; sizeof(int)=4; sizeof(a)=4——int占据4个字节,一个字节8个bit,意味着这是32位变量,内存占据32bit。
运算符&:scanf("%d,&i")里面的&:
取得变量的地址,他的操作数必须是变量:int i; scanf("%d, &i")
地址的大小是否与int相同取决于编译器:int i; printf("%p, &i");
输出地址时应该用%p,而不要把地址转化为int类型后输出,不同架构值不一定同。
%p:会把值作为地址以十六进制输出,前面加0x
&不能取的地址:&必须对变量取地址,右边不是变量就不行:&(a+b)、&(a++)、&(++a)。
—— 相邻变量的地址:
相差4个字节(32位架构下的一个int)先定义的在更高的地方、数字更大,C分配变量自顶向下分配。
—— 数组的地址:
&a=a=a[0]=a[1]-4个字节,数组相邻位置的地址相差4.
#include <stdio.h>
int main()
{
int a = 6;
printf("sizeof(double)=%ld\n",sizeof(double));
printf("sizeof(int)=%ld\n",sizeof(int));
printf("sizeof(a)=%ld\n",sizeof(a));
return 0;
}
int main()
{
int i = 0;
int p;
//相邻变量的地址
printf("%p\n",&i); // 相差4个字节(32位架构下的一个int)
printf("%p\n",&p); // 先定义的在更高的地方、数字更大,C分配变量自顶向下分配
return 0;
}
int main()
{
int a[10];
//数组的地址
printf("%p\n",&a);
printf("%p\n",a);
printf("%p\n",a[0]);
printf("%p\n",&a[1]);
return 0;
}
如果能够将取得的变量的地址传递给一个函数,能否通过这个地址在那个函数内访问这个变量?. scanf("%d" , &i);
scanf()的原型应该是怎样的?我们需要一个参数能保存别的变量的地址,如何表达能够保存地址的变量?
什么样的类型可以接收取地址得到的那个地址?
指针:就是保存地址的变量。
int i;
int* p = &i; //定义指针p,指向i(p中存的是i的地址)
int* p,q 和 int *p,q; 意思相同,都表示:p是一个指针指向一个int,q是一个int变量(如要表达两个指针应该位int *p,*q)。
指针变量:变量的值是内存的地址,普通变量的值是实际的值,指针变量的值是具有实际值的变量的地址。
作为参数的指针:void f(int *p);
在被调用的时候得到了某个变量的地址:int i = 0; f(&i);
在函数里面可以通过这个指针访问外面的这个i
#include <stdio.h>
void f(int *p );
void g(int k);
int main(void)
{
int i=6;
printf("&i = %p\n",&i);
f(&i); //main中的i的地址传给了函数f,使得f能访问main中的变量的能力
g(i); //main中的i的值传给了函数g
return 0;
}
void f(int *p)
{
printf(" p = %p\n",p);
printf("*p = %d\n",*p);//通过p这个指针访问到了p所指的i的值
*p = 26; //通过指针更改了i的值,*p代表了i
}
void g(int k)
{
printf("k = %d\n",k);//结果发现,f函数运行后,i的值发生了更改;前面函数中如传的不是地址,函数f做处理将不影响外面main的变量。
}
访问那个地址上的变量*
*是一个单目运算符,用来访问指针的值所表示的地址上的变量。可以做右值,也可以做左值:int k = *p; *p = k+1;
传入地址:为什么int i; scanf("%d",i);编译没报错?整数和地址一样大,他以为传进去的i是i的地址,实际传进去的是6,以为是地址,用这个6来进行操作,运行一定出错。
指针的作用
一、交换两个变量的值:
前面学函数时,在函数中交换两个数的值,出来后值并没有交换,只是函数的变量空间中,值交换了。
#include <stdio.h>
// 交换两个变量的值
void swap(int *pa, int *pb);
int main(void)
{
int a = 5;
int b = 6;
swap(&a,&b);
printf("a = %d, b = %d\n",a,b);
return 0;
}
void swap(int *pa, int *pb)
{
int t = *pa;
*pa = *pb;
*pb = t;
}
二、 函数返会多个值,某些值就只能通过指针返回:传入的参数实际上是需要保存带回的结果的变量。
#include <stdio.h>
void minmax(int a[], int len, int *max, int *min) ;
int main(void)
{
int a[] = {1,2,3,4,5,6,7,8,9,12,13,14,16,17,21,23,55,};
int min, max;
minmax(a, sizeof(a)/sizeof(a[0] ), &min, &max);
printf( "min=%d , max=%d\n", min, max);
return 0;
}
// *min *max虽然是主函数传进去的参数,但它的作用是从函数中把结果值带出来
void minmax(int a[], int len, int *min, int *max)
{
int i;
*min = *max = a[0];
for ( i=1; i<len; i++ ) {
if ( a[i] < *min ) {
*min = a[i];
}
if ( a[i] >*max ) {
*max = a[i];
}
}
}
二、 函数返回运算的状态,结果通过指针返回。
常用的套路是让函数返回特殊的不属于有效范围内的值来表示出错: -1 或0 (在文件操作会看到大量的例子);
—— 但是当任何数值都是有效的可能结果时,就得分开返回了。往往状态用函数的return来返回,实际的值通过指针参数来返回。如此操作容易把函数的返回结果放在if语句中。
—— 后续语言C++、java采用了异常机制来解决这个问题。
两个整数做除法的函数:
#include <stdio.h>
/*
return如果除法成功,返回1;否则返回0
*/
int divide(int a, int b, int *kresult);
int main(void)
{
int a=5;
int b=2;
int c;
if ( divide(a,b,&c) ) { // 能除,函数返回1,ture ,if条件满足
printf("%d/%d = %d\n", a,b,c);
}
return 0;
}
int divide(int a, int b, int *result){
int ret = 1; // ret 默认1,如果b为0,无法除,ret返回0
if ( b == 0 ) ret = 0;
else {
*result = a/b;
}
return ret;
}
指针最常见的错误
定义了指针变量,还没指向任何变量,就开始使用指针。得先明确指针指向哪个变量,如果不,可能内存中存放指针的地方它本身有值,指向了某个变量1,此时对指针进行操作,会对变量1的值进行操作,如果不允许操作久会出错,且本身程序也有问题,没有达到本来的目的。
指针与数组
传入函数的数组成了什么?
sizeof(a)(a是个数组)返回的是 int* 的sizeof,而不是 int [] 的sizeof
minmax中数组a的地址和main的地址一样,是同一个数组。
在minmax函数中改变a[0]的值,在main()函数中a[0]的值也会被改变,和指针一样。
函数参数表中int a[]数组实际上是指针,可以写成 int a[],也可写成 *a
函数中仍然可以用数组的运算符[]进行运算a[0] = 1000;
以下四种函数原型是等价的:
int sum(int *ar, int n);
int sum(int *, int);
int sum(int ar[], int n);
int sum(int [], int);
数组变量是特殊的指针
—— 数组变量本身表达地址:
int a[10]; int *p = a; 取地址不需要用&
但是数组的单元表达的是变量,需要用&取地址
a == &a[0] a的地址等于a[0]的地址。
—— []运算符可以对数组做,也可以对指针做:
p[0]<==>a[0],p[0]就相当于*p
—— *运算符可以对指针做,也可以对数组做:
*a = 25;
—— 数组变量是const的指针,所以不能被赋值。
int a[]<==> int* const a = ...
int b[] -> int * const b;
指针与const(C99 Only)
指针——可以是const;值——可以是const
若:指针是const:
—— 表示一旦得到了某个变量的地址,不能再指向其他变量。
int * const q=&i;//q是const
*q=26; //可以改变i的值
q++; //ERROR,q只读,不能变
若:所指的是const
—— 表示不能通过这个指针取修改那个变量(不会使得那个变量成为const)
const int *p=&i;
*p=26; //ERROR!(*p)是const,不能通过p做赋值。
i=26; //可以 直接改i
P=&j; //改变p指向的地址,可以
int i;
const int*p1=&i;
int const*p2=&i;
int *const p3=&i;
加const只有两种意思:1. 地址不可改变;2.不能通过该地址来赋值。
判断哪个被const的标志是:*在const前面还是后面。
转换:总可以把一个非const的值转化成const的
void f(const int*x);// 保证函数f内部不会改变指针所指的值
int a=15;
f(&a); //可以
const int b=a;// 本来就是const的,交给f可以
f(&b); //可以
b=a+1; //不行!
当要传递的参数的类型比地址大的时候,这是常用的手段:既能用比较少的字节数传递值给参数,又能避免函数对外面的变量的修改。
const数组
const int a[] = {1,2,3,4,5,6,};
数组变量本身就是一个const指针,这里的const表明数组的每个单元都是const。
所以必须通过初始化赋值。
保护数组值
因为把数组传入函数时传递的是地址,所以那个函数内部可以修改数组的值。
为了保护数组不被函数破坏,可以这样设置参数为const。
int sum(const int a[], int length);
指针运算
指针加1会是怎样?
#include <stdio.h>
int main (){
char ac[]={0,1,2,3,4,5,6};
char *p=ac;
printf("p的地址是%p\n",p);
printf("p+1的地址是%p\n",p+1);
printf("*(p+1) = %d\n",*(p+1));
int ai[]={0,1,2,3,4,5,6};
int *q=ai;
printf("q的地址是%p\n",q);
printf("q+1的地址是%p\n",q+1);
printf("*(q+1) = %d\n",*(q+1));
return 0;
}
在地址上加一个sizeof基础类型的大小。
给一个指针加1表示要让指针指向下一个变量
int a[l0];
int*p = a;
*(p+n)—> a[n]
如果指针不是指向一片连续分配的空间,如数组,则这种运算没有意义.
注意是*(p+1)而不是*p+1,因为*号是单目运算符,优先级高。
指针运算
这些算术运算可以对指针做:
给指针加、减一个整数(+,+=,-,-=)
递增递减(++/—)
两个指针相减:得到
的是地址差/sizeof基础类型——中间能放几个这样类型的东西。
*p++
—— 取出p所指的那个数据来,完事之后顺便把p移到下一个位置去
—— *的优先级虽然高,但是没有++高
—— 常用于数组类的连续空间操作
—— 在某些CPU上,这可以直接被翻译成一条汇编指令
#include<stdio.h>
// 遍历数组
int main(void)
{
char ac[] = {0,1,2,3,4,5,6,7,8,9,-1};
char *p = &ac[0];
int i;
for ( i=0; i<sizeof(ac)/sizeof(ac [0] ); i++ ) {
printf( "%d\n", ac[i] );
}
//for ( p=ac; *p!=-1 ; ) {
while ( *p != -1 ) {
printf ("%d\n",*p++);
}
return 0;
}
指针比较
—— <,<=,==,>,>=,!=都可以对指针做比较
—— 比较它们在内存中的地址
—— 数组中的单元的地址肯定是线性递增的
0地址
- 当然你的内存中有0地址,但是0地址通常是个不能随便碰的地址
- 所以你的指针不应该具有0值
- 因此可以用0地址来表示特殊的事情:
——返回的指针是无效的
——指针没有被真正初始化(先初始化为0)
- NULL是一个预定定义的符号,表示0地址
——有的编译器不愿意你用0来表示0地址
指针的类型
无论指向什么类型,所有的指针的大小都是一样的,因为都是地址
但是指向不同类型的指针是不能直接互相赋值的
这是为了避免用错指针
指针的类型转换
void*表示不知道指向什么东西的指针
计算时与char*相同(但不相通)
指针也可以转换类型
int*p = &i; void*q = (void*)p;
这并没有改变p所指的变量的类型,而是让后人用不同的眼光通过p看它所指的变量
我不再当你是int啦,我认为你就是个void!
#include <stdio.h>
//int main (){
//
// char ac[]={0,1,2,3,4,5,6};
// char *p = ac[0];
// char *p1 = ac[5];
// printf("p的地址是%p\n",p);
// printf("p+1的地址是%p\n",p+1);
// printf("*(p+1) = %d\n",*(p+1));
// printf("*(p1-p) = %d\n",p1-p);
//
// int ai[]={0,1,2,3,4,5,6};
// int *q = ai[0];
// int *q1 = ai[6];
// printf("q的地址是%p\n",q);
// printf("q+1的地址是%p\n",q+1);
// printf("*(q+1) = %d\n",*(q+1));
// printf("*(q1-q) = %d\n", q1-q);
//
// return 0;
//}
int main(void)
{
char ac[] = {0,1,2,3,4,5,6,7,8,9,-1};
char *p = &ac[0];
int i;
for ( i=0; i<sizeof(ac)/sizeof(ac [0] ); i++ ) {
printf( "%d\n", ac[i] );
}
//for ( p=ac; *p!=-1 ; ) {
while ( *p != -1 ) {
printf ("%d\n",*p++);
}
int ai[] = {0,1,2,3,4,5,6,7,8,9,};
int *q = ai;
q = p; //[警告] assignment to 'int *' from incompatible pointer type 'char *' [-Wincompatible-pointer-types]
return 0;
}
指针用来做什么?
— 需要传入较大的数据时用作参数
— 传入数组后对数组做操作
— 函数返回不止一个结果
需要用函数来修改不止一个变量
— 动态申请的内存..
动态内存分配
输入数据
如果输入数据时,先告诉你个数,然后再输入,要记录每个数据
— C99可以用变量做数组定义的大小,C99之前呢?
—— int *a = (int*)malloc(n*sizeof(int));
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int number;
int* a;
int i;
printf("输入数量:");
scanf("%d",&number);
//int a[number]; //C99可以这么干
a = (int*)malloc(number*sizeof(int));//malloc要的是字节为单位,malloc返回void*,要转换成int*
//后面都是拿a当数组来用
// 键入产生数组
for(i=0;i<number;i++){
scanf("%d",&a[i]);
}
//逆序输出数组
for(i=number-1;i>=0;i--){
printf("%d",a[i]);
}
free(a);//释放空间
return 0;
}
malloc
需要头文件:#include <stdlib.h>
void* malloc(size_t size);
- 向malloc申请的空间大小是以字节为单位的
- 返回结果是void* 需要转换为自己需要的类型;
- (int*)malloc(number*sizeof(int));
没空间了?
如果申请失败,则返回0,或者叫做NULL。
你的系统能给你多大的空间 ?
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
void *p;
int cnt = 0;
while( (p = malloc(100*1024*1024)) ){
cnt++;
}
printf("分配了%d00MB的空间\n",cnt);
free(p);
return 0;
}
free()
把申请来的空间还给系统
申请过的空间,最终都应该要还,只能还申请来的空间的的首地址。
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
void *p;
int cnt = 0;
p=malloc ( 100*1024*1024);
p++;
free(p);//p++运算后free p
return 0;
}
释放的不是申请来的,异常终止
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int i;
void *p;
int cnt = 0;
// p=malloc ( 100*1024*1024);
// p++;
p = &i;
free(p);//p不是malloc来的
return 0;
}
释放的不是申请来的,异常终止
free(NULL) —— 没问题:0不可能是malloc得到的有效地址,
养成良好习惯:定义指针的时候就先初始为0
void *p = 0; 之后 再free(p), 不管有没使用malloc,free()都没有问题。
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int i;
void *p;
int cnt = 0;
// p=malloc ( 100*1024*1024);
// p++;
free(p);//p不是malloc来的
return 0;
}
如此,free(p) 没有问题。
free(1),不可以,1这个地址不能被free。
常见问题
申请了没有free——长时间运行内存逐渐下降:新手:忘了;老手:找不到合适的时机。
free过了再free
地址变过了,直接free