目录
一.变量
1.变量的基本属性
包括以下内容
- 变量类型
- 变量名
- 变量值
- 变量存在于内存中位置
我们声明变量(如int number)时,程序根据变量的类型(如int)分配一块内存,并将这块内存的开始地址和变量名关联起来。
我们可以试着将变量想象成一个盒子,不同类型的变量就是装着不同类型东西的盒子。除了类型之外,变量还有一个属性就是地址(address),我们可以把这看作是贴在盒子上的一种标签。如果你把这个地址给到了张三,张三就可以利用这个地址来找到“贴着对应标签的盒子”-------对应的变量。
2.使用变量
使用变量名我们就可以访问和输出变量的值
printf("%d",number);
也可以直接修改变量的值
number=100;
3.取地址运算&
获得变量的地址,他的操作数必须为一个明确的变量,否则就不能取地址进行运算,反例:
&(a + b); &(a++); &(++a);
这些&
- 变量的地址
- 相邻变量的地址(类型的字节大小)
- &的结果的sizeof(地址和int不一定是一个类型)
- 数组的地址、数组单元的地址、相邻数组单元的地址
int a[10]; printf("%p\n",&a); printf("%p\n",a); printf("%p\n",&a[0]); printf("%p\n",&a[1]); 000000000061fdf0 000000000061fdf0 000000000061fdf0 000000000061fdf4
4.使用变量的地址
对于一个int类型的变量number,当我们写number的时候得到的是变量的值(即盒子里的东西),而当我们写&number的时候我们得到的是变量number的地址(即盒子上的标签)。
举个例子:使用取地址符可以得到内存地址
scanf("%d",&number);
通过&number得到变量number的内存地址,可以在读入值时确认要读入的内存位置。也可以用%p格式占位符将内存地址输出出来。
printf("%p",&number);
二.保存内存地址的变量
我们声明一个可以存储整数型值的变量number,会写成int number;而我们现在希望有一个变量p_number,可以存储整数型值的变量的地址,我们就需要写成int *p_number。
1.指针
取地址
保存地址的变量p
int i ; int *p = &i; int *p,q; 表示p是指针,q是整数。*是加在p上的
访问地址上的变量
*是单目运算符,用来访问指针的值所表示的地址上的变量
int i ;
int *p = &i; // p的值就是i的地址int q = *p // 此时*p就相当于i
取地址和访问地址上的变量互相反作用!
2.保存内存地址的变量的基本属性
包括:
- 存储的内存地址对应的变量类型
- 变量名
- 变量值(另一个变量的内存地址)
- 变量存在于内存中的位置(自己的变量地址
我们声明保存内存地址的变量(如int *p_number)时,*标记出这个变量是用来存储内存地址的变量,而在*前则是存储的内存地址对应的变量类型。与一般变量相同,这个变量声明后也会有自己的内存地址,声明变量后这块内存的开始地址也会与这个变量名关联到一起。
用盒子的比喻理解清楚,变量,变量的值,变量的地址,特殊的用于存储地址的变量,这些不同的概念,相信应该很容易吧。
三.指针的使用
1.1.场景一 交换两个变量的值
参数只能是地址,要是传值则离开swap函数时就起不了作用
void swap(int *a,int *b){ int t = *a; *a = *b; *b = t; } int main(){ int a,b; scanf("%d%d",&a,&b); swap(&a,&b); printf("%d %d",a,b); return 0; }
1.2.场景二
- 函数返回多个值 某些值就只能通过指针返回
- 传入的参数实际上是需要保存带回结果的变量
//函数返回运算的状态(-1或0),结果通过参数返回
2.指针最常见错误
- 定义了指针变量,还没有指向任何变量,就开始使用。
指针和数组
1.传入函数的数组成了什么?
函数参数表中的数组实际上是指针
- 在函数中 sizeof(a) == sizeof(int*)
- 但是可以用数组的运算符[ ]运算
即:以下四种函数原型等价
2.数组变量是特殊的指针
所以:
- 数组变量本身表达地址 int a [10] ; int *p = a; //无需取地址
- 但是数组的单元表达的是变量,需要用&取地址
- a == &a[0]
- [ ] 既可以对数组做 也可以对指针做 p[0] == a[0]
- * 既可以对指针做 也可以对数组做 *a == 25 // *a == a[0]
- 数组变量是const的指针 所以不能赋值
指针和const
指针是const
所指是const
判断哪一个被const的标志是const在*前面还是后面
const数组
const int a[ ] ={ 1,2,4,5 };
- 数组变量本身就是const的指针,这里的const表明数组的每一个单元都是const int。这种数组必须初始化赋值
- 因为数组传入函数时传递的是地址,所以函数内部可以修改数组的值。为了保护数组不被破坏,可以设置参数为const。
指针的运算
- 指针+数字 指针p加上整数
j
表示指针向后移动j
个单位(每个单位大小为指针p
的类 型所占的字节数),指向p
原先指向的元素后的第j
个元素。若p
指向数 组a[i]
,则p+j
指向a[i+j]
- 指针-数字 同理,往前移动。
- 指针-指针 得到的是指针之间元素的个数。若指针
p
指向a[i]
,指针q
指向a[j]
,则p-q
等于i-j
。
表达式 | 含义 |
---|---|
*p++或*(p++) | 首先计算表达式的值为*p,然后p自增1 |
(*p)++ | 首先计算表达式的值为*p,然后*p自增1 |
*++p或*(++p) | 首先将p自增1,然后计算表达式的值为*p |
++*p或++(*p) | 首先将(*p)自增1,然后计算表达式的值为(*p) |
四.动态内存分配
在C中我们开辟内存空间有两种方式 :
1.静态开辟内存 :
int a; int b[10];
- 这种开辟内存空间的特点是所开辟的内存是在栈中开辟的固定大小的。数组在申明时必须指定其长度,那如果想在程序运行时才确定一个数组的大小 , 这种分配内存的方法显然是不行。如:
int n; scanf("%d", &n); int a[n];
2.动态开辟内存 :
- 在C中动态开辟空间需要用到三个函数 :malloc(), calloc(), realloc() ,这三个函数都是向堆中申请的内存空间.
- 在堆中申请的内存空间不会像在栈中存储的局部变量一样 ,函数调用完会自动释放内存 , 需要我们手动释放 ,就需要free()函数来完成.
malloc和free
需要头文件 #include<stdlib.h>
malloc()
是最常用到的动态内存分配函数,其函数原型为:void* malloc(size_t size);
该函数向内存申请一块连续可用空间(size个字节,以字节为单位),并返回这块空间的指针
注意一下几点:
- 如果内存开辟成功,该函数返回指向这块空间的指针;如果失败,则返回空指针(NULL)。所以我们在用malloc()函数开辟动态内存之后, 一定要判断函数返回值是否 为NULL。
- 该函数返回void* 的指针,所以在使用时我们通常要进行强制类型转换,转换成我们需 要的类型,如需要int*的指针,就将其强制转换成int*的类型。 如: (int*)malloc(sizeof(int)*n).
int *p = NULL; //最好给指针初始化为NULL int n ; scanf("%d", &n); p = (int*)malloc(sizeof(int) * n); if(p != NULL){ //....需要进行的操作 /* //只要malloc了空间,此时p就可以当做数组 for(int i = 0;i< n;i++){ scanf("%d",&p[i]); } */ ....... }
free()
- 其函数原型为:void free(void* ptr)
- 动态开辟的内存只有在程序结束时操作系统才进行回收,所以当我们不使用动态开辟的內存时必须手动将其释放掉(不然会造成内存泄漏,通俗说就是就是占着茅坑不拉屎),释放动态开辟的内存就要用到free这个函数。
int *p = NULL; int n = 0; scanf("%d", &n); p = (int*)malloc(sizeof(int) * n); if(p != NULL){ //....需要进行的操作 } //操作完成 ,不再使用这片内存空间 free(p); p = NULL;
注意一下两点:
(1)如果实参p不是动态开辟的,那么该函数的行为是未定义的。
(2)如果实参p为空指针,那么该函数什么事都不做。