指针
1,指针
指针,英文名是pointer,也叫地址,他是常量。他就是一个内存空间的首位置。
2,指针变量,(地址变量)
顾名思义,指针变量就是存放指针(地址)的变量。如果定义呢?
short int * p = NULL; //定义了一个指针变量P,并且付给初始值为零。
// short int * 就是类型。表示的是存放short int型内存块的地址的。
// p就是变量名。
//付给初始值为零,零表示不是任何内存块的地址。或者称p为空指针
//在32位系统下,所有的指针(地址)变量,都是四个字节。
//我们把一个地址赋值给一个指针变量,我们就说,指针变量指向了该内存块(变量)
3,*p的含义:这里的* 是运算符,取值运算符。
*p的含义,根据p里的地址,找对应类型的内存块。
如果p 的值为0,*p找不到内存,就会崩溃。
如果p 是乱值, 就会崩溃。
所以,未经初始化的变量是禁止使用的。
4,p + n 的含义(地址加上一个整型数的意思)
含义是:p这个地址向右移动n个存储单元(是p指向的内存单元),得到一个新的地址。
p[n],含义是p地址后第n+1个存储单元(内存块)。
由此得出,两个指针相减,等于间隔的内存块的个数。
两个地址不能相加。
5, 指针可以进行关系运算的。(在连续空间内比较才有意义)
例如,> >=,<,<=,==,!=
1、指针的基本概念
地址:(1)内存单元的编号
(2)从零开始的非负整数
(3)4G
指针:(1)指针就是地址,地址就是指针
(2)指针变量就是存放内存单元编号的变量,指针变量就是存放地址的变量
(3)指针和指针变量是两个不同的概念
(4)指针的本质是一个操作受限的非负整数
格式: 类型说明符 *指针变量名 ; (指针变量的类型, 是指针所指向的变量的类型, 而不是自身的类型)
示例: #include <stdio.h>
int main()
{
int * p; //表示p变量存放int类型变量的地址
p = & i; //(1)p存放了i的地址,因此 p 指向 i;
//(2)但 p 不是 i ,i也不是 p;更准确的说修改 p 的值不影响i的值,修改 i 的值不影响 p 的值;
//(3)如果一个指针变量指向了某个普通变量,则*p指针变量,就完全等同于普通变量;
}
指针变量的引用、
取地址运算符── “&”
引用运算符 ── “*”
说明: *p 对应类型内存块
2、指针变量的运算
(1)指针变量不能相加,不能相乘,不能相除
(2)如果两个指针变量指向的是同一块连续空间中的不同存储单元,则这两个指针变量可以相减。
3.指针与函数
通过被调函数修改主调函数变量的值,实现函数返回一个以上的值;
(1)实参必须是该普通变量的地址
(2)形参必须是指针变量
(3)在被调函数中可以通过 * 形参名=……的方式,修改主调函数相关变量的值。
示例:
#include <stdio.h>
void func ( inr * p , int * q )
{
int temp =0;
temp = * p ;
* p = * q ;
* q = temp ;
}
int main()
{
int a = 1;
int b =2;
func ( & a, & b );
printf("%d%d".a ,b );
}
3.指针和数组
指针和一维数组
(1)一维数组名是个指针常量
(2)它存放的是一维数组第一个元素的地址
指针和下标的关系
(1)如果 p 是个指针变量,则 p [ i ] 永远等价于 * (p + i)
(2)确定一个一维数组需要两个参数(数组地址和参数个数)
动态内存分配
1、传统数组缺点
(1)数组长度必须事先指定,且只能是常整数,不能是变量
(2)传统形式定义的数组,该数组的内存程序员无法手动释放,直到函数运行结束,系统自动释放。
(3)数组长度一旦定义,其长度就不能更改;
(4)传统方式定义的数组不能跨函数使用
动态分配内存可以很好地解决传统数组的缺点
2、malloc函数(memory allocate)
int * p =( int * ) malloc ( sizeof ( int ));
(1)必须添加头文件 malloc . h
(2)只有一个形参,并且形参为正数
(3)返回值为分配内存的第一个字节的地址;
(4)free(变量)手动释放分配的空间
(5)free能释放malloc分配的动态内存,不能释放 int * p 分配的静态内存;
指针应用。
(1)我现在想要模块化编程,写函数,写一个函数来实现两个数的交换。
方法一
#include <stdio.h>
void swap(int a,int b)
{
int t = 0;
t = a;
a = b;
b = t;
}
void main()
{
int a = 10;
int b = 5;
swap(a,b);
printf("%d%d",a,b);
}
上面的传递方式,我们叫他数值传递,因为值传递实参和形参是不同的内存,所以无法改变主调函数变量的值。
结论,值传递不可能改变主调函数变量的值。
方法二
include <stdio.h>
void swap(int * a,int * b)//调用时后,a的值为(4000)b的值为(5000)
{
int t = 0;
t = *a; //*a ,根据a里面的地址(4000),找对应int型内存块。所以,此时*a(被调) 等价于a(主调)
*a = *b //*b ,根据b里面的地址(5000),找对应int型内存块。所以,此时*b(被调) 等价于b(主调)
*b = t; //由此可见,通过*的运算,可以找到另外一个栈帧中的变量,隔山打牛的方式间接修改主调函数变量的值。
}
void main()
{
int a = 10;
int b = 5;
swap(&a,&b); //假设 a的地址是(4000),b的地址是(5000)
printf("%d%d",a,b);
}
方法三
#include <stdio.h>
void swap(int * a,int * b)//调用时后,a的值为(4000)//b的值为(5000)
{
int * t = 0;
t = a; //就时把a的数值赋值给变量t了。
a = b; //就时把b的数值赋值给变量a了。
b = t; //所以这个程序只是交换了a和b的地址而已。函数调用后,函数释放内存,形参和局部变量分配的空间也就释放了。
}
void main()
{
int a = 10;
int b = 5;
swap(&a,&b);//假设 a的地址是(4000),b的地址是(5000)
printf("%d%d",a,b);
}
方法四
#include <stdio.h>
void swap(int * a,int * b)//调用时后,a的值为(4000)b的值为(5000)
{
int * t = 0;
*t = *a; //崩溃
*a = *b;//
*b = *t; //所以这个程序只是交换了a和b的地址而已。函数调用后,函数释放内存,形参和局部变量分配的空间也就释放了。
}
void main()
{
int a = 10;
int b = 5;
swap(&a,&b);//假设 a的地址是(4000),b的地址是(5000)
printf("%d%d",a,b);
}
三种方法的比较,有的方法可以实现,有的方法实现不了,为什么?
(2)第二题
char * get_usr_str()
{
char mystr[30] = {0};//局部变量,数组。mystr是数组名,他是内存块(mystr[0])的首地址。我们可以假设这个地址是(4000).
printf("请输入您的字符串:
");
scanf("%s",mystr);//%s 按字符串的方式来读取,读取内容,放到mystr(4000)地址开始的内存块中。
return mystr;//把(4000)这个地址值返回了。
}
char * get_usr_str1()
{
char * p = "ilovechina";//常量文本"ilovechina",分配到常量文本去,常量文本区分配
//分配的内存,将再程序结束后,由操作系统统一释放。同时,按C语言
//语法规定,字符串赋值给指针变量,实际上是把字符串的首地址赋值
//给指针变量了,例如(5000),那么p的值就为(5000)
//额外我们说一句,字符串作为函数实参,也是把字符串的首地址作为实参
//赋值给形参了。所以我们可以认为,字符串常量的返回值类型为char *
return p;//把(5000)地址返回。
}
void main()
{
char * p = NULL; //定义了指针变量。
p = get_usr_str(); //调用完成后,p的地址被赋值给函数的返回值,(4000)了。此时p就是4000.
printf("%s
",p); //%s的意思时从某个地址开始,输出到截至 中间的部分,
//但是,因为get_usr_str()调用完成后,所有的形参和局部变量将全部释放,
//所以,此时(4000)开始的内存已经不存在,p为野指针,所以输出了乱值。
p = get_usr_str1(); //得到了地址(5000)
printf("%s
",p) ;//因为常量文本的内存并没有释放,所以此时p不是野指针,%s的意思时从某个地址开始,输出到截至 中间的部分
//常量文本最后是有 的,所以正常输出了。
}