指针
变量的地址,每个变量都是存放在内存中的某个位置的。
如何查看一个变量的地址?
int a;
&a;
printf("%p", &a);
指针变量:是一类专门存储其他变量的地址的变量。
int * p; // p是一个指针变量,它所指向的变量的类型为整型变量
p存储哪个变量的地址,我们就说p指向了谁
p = &a;
如何通过指针变量获得它所指向的变量呢?
*p ==== a
需要注意的问题
1)指针变量一定要初始化后才能够使用
2)指针变量的类型要和它实际指向变量的类型保持一致
int * p = NULL;
表示空指针,没有指向任何变量。
指针变量的用途?
1)用来作为函数的参数,实现特殊的功能
通过指针作为函数的参数,实现在函数内部修改实参的值。
void swap1(int *a, int *b)
{
int t = *a;
*a = *b;
*b = t;
}
int main()
{
int x=9, y=8;
//swap(x,y);
swap1(&x, &y);
printf("%d,%d", x, y);
return 0;
}
指针变量作为函数的形参,实参应该传递指针或者普通变量的地址(形参和实参的类型要求一致)
2)用指针可以更方便的处理连续的数据,通常是和数组结合使用(不是必须的, 建议直接使用数组的方式, 指针方式更容易出错)
指针和数组之间的关系,指向数组元素的指针,在这种情况下,指针可以进行一些运算
指针可以和整数进行算术运算 p+1 p+d p-1 p-d p++ p–
指针变量进行算术运算的含义?
并不是简单的数值加1,而是根据指针所指向的类型的大小来决定得到一个地址值,p+sizeof(int) ===> p+d
通常用在通过指针访问数组中的元素。
两个指针可以进行比较 p1 < p2
p2 - p1也是可以有意义的
p1 + p2是没有意义的
二维数组在内存中是一维结构,所以也可以用处理一维数组的指针来处理二维数组。
当需要存储多个字符串的时候,可以使用二维字符数组。
char monthsWords[][MAX_LEN] = {"January","February","March","April",
"May","June","July","August",
"September","October","November","December"};
如果想要得到每个字符串,可以用二维字符数组名+行下标的方式来得到每行字符串
int FindWord(char mws[][MAX_LEN],int n,char x[])
{
for(int i=0; i<n; i++){
if(strcmp(mws[i], x)==0){
return i;
}
}
return -1;
}
数组名可以看做是一个常量指针(也就是不能赋值的指针变量),它指向的是数组第一个元素。
数组名实际上也是一个指针。
int a[5] = {1,2,3,4,5};
printf("%d\n", *(a+1));
指针可以象数组名一样使用吗?
指针变量也可以象数组名一样使用 p[i] === *(p+i)
当实参是数组名的时候,形参可以是指针,此时参数传递的结果就是让指针指向了实参数组的第一个元素。
当函数的形参是数组的时候,形参和实参数组是同一个数组, 数组作为函数的参数。
void Split(int arr[],int n) //== void Split(int * arr, int n)
// void Split(int * arr, int n)
{
// 先统计一下有多少个0
int cnt = 0;
for(int i=0; i<n; i++){
if(arr[i]==0){
cnt ++;
}
}
for(int i=0; i<cnt; i++){
arr[i] = 0;
}
for(int i=cnt; i<n; i++){
arr[i] = 1;
}
}
函数的形参可以定义是数组,但是编译器会将它转换为指针,所以形参数组和实参数组是同一个数组
void Size(int arr[10][10])
{
cout << sizeof(arr) << endl;
}
int main()
{
int arr[10][10];
Size(arr);
cout << sizeof(arr) << endl;
}
3)可以保留动态申请的内存
内存分区
不同存储类型的变量存放在内存的不同区域中。
全局的和static修饰的变量存放在静态区。
具有动态生存期的变量(局部变量,动态内存分配的空间)存放在动态区.
只读存储区(代码和常量)
动态存储区:栈区、堆区
动态内存申请
堆区,在堆区申请的内存就是动态申请的内存。
堆和栈的区别
1)堆的空间更大(优点)
2)更加灵活(根据需要运行的时候申请可变的大小,优点)
3)管理上不方便(需要程序员自己管理内存,栈内存是系统管理的,缺点)
如何使用和管理动态申请的内存
需要程序员写代码去申请堆空间中的内存
函数原型:void * malloc(unsigned int num_bytes);
参数:num_bytes 是无符号整型,用于表示分配的字节数。
返回值:如果分配成功则返回指向被分配内存的指针(此存储区中的初始值不确定),否则返回空指针NULL。void* 表示未确定类型的指针,void *可以指向任何类型的数据,更明确的说是指申请内存空间时还不知道用户是用这段空间来存储什么类型的数据(比如是char还是int或者…)
功能:分配长度为num_bytes字节的内存块
void * 类型的指针
在使用的时候需要将该指针转换成需要指向变量的指针类型
如果申请内存失败,返回NULL(0)。
int main(void)
{
char*a=NULL;//声明一个指向a的char*类型的指针
a=(char*) malloc(100*sizeof(char));//使用malloc分配内存的首地址,然后赋值给a
if(!a)//如果malloc失败,可以得到一些log // if(a==NULL)
{
perror("malloc");
}
return-1;
}
calloc
原型:void* calloc(unsigned int num,unsigned int size);
功能:在内存的动态存储区中分配num个长度为size的连续空间;
注意:num:对象个数,size:对象占据的内存字节数,相较于malloc函数,calloc函数会自动将内存初始化为0;
更推荐使用calloc,因为会自动对内存初始化为0.
realloc
void *realloc(void *mem_address, unsigned int newsize);
重新修改动态申请的内存的大小。
释放内存的函数,把申请的内存还给系统。
void free(void * p)
要求p是指向动态申请的内存的指针。
4)构造链表中节点结构体,二叉树
定义结构体的内部不能定义自己结构体类型的变量
但是可以定义自己结构体类型的指针作为成员
链表:可以存储多个元素的一种数据结构。
数组,和数组相比,优点在于内存空间可以不连续,元素的插入和删除效率比数组要高。
链表的基础,指针和结构体。
先要定义一个节点(结构体变量,数据域,指针域)
struct Node{
int data;
struct Node * next;
};
头指针,指向链表的第一个元素。最后一个元素的指针域为空(NULL),表示后面没有元素了(尾节点)
模块化方式管理链表(创建、查找、插入、删除)
注意:
实践中链表的节点需要通过动态内存分配的方式创建
如果设计函数返回指针,一定要明确返回的指针变量指向哪里?同时确保其指向的空间没有被释放(不要指向函数内部的局部变量)
更高级的指针用法(基础学习阶段了解即可)
1)指向指针的指针
int * * p;
int main()
{
int a = 10;
int * p = &a;
int * * pp = &p;
printf("%d", *(*pp));
return 0;
}
输出结果:10
2)指向数组的指针(指向一维数组的指针)
int main()
{
int a[3] = {1,2,3};
int (*p) [3]; // 指向数组的指针,指向的是一个有3个整型元素的一维数组
p = &a;
printf("%d\n", (*p)[0]);//输出结果:1
return 0;
}
printf("%p, %p\n", a+1, p+1);
注意,此时指针的算术运算是跳过整个一维数组
二维数组的每一行,可以看成是一个一维数组,所以可以让指向一维数组的指针指向二维数组中的某一行。
#include <stdio.h>
#include <string.h>
int main()
{
int a[2][3] = {1,2,3,4,5,6};
int (*p) [3]; // 指向数组的指针,指向的是一个有3个整型元素的一维数组
p = &a[0];
printf("%d\n", (*p)[0]);
printf("%d\n", (*(p+1))[0]);
return 0;
}
输出结果:1 4
3)指向函数的指针
指针除了可以指向变量外,还可以指向函数。
void f(int a)
{
printf("f\n");
}
int main()
{
f(4);
void (*p) (int);
p = &f; // p = f;
(*p)(4); // p(5);
return 0;
}
输出结果:f f
4)指针数组,每个元素是指针类型数据的数组
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define M 10
#define N 21
void sortStrs(char * p[], int n);
int main()
{
char strsArr[M][N]; // 定义该二维字符数组的目的是存储多个输入的字符串
// 每一行存储一个字符串
char * p[M]; //指针数组
int i,n;
printf("Input n(n<=20):\n");
scanf("%d",&n);
//fflush(stdin);
getchar(); //读走回车字符
for(i=0;i<n;i++)
{
printf("%d:\n",i);
gets(strsArr[i]);
//scanf("%s", strsArr[i]);
//fgets(strsArr[i], 100, stdin);
p[i] = strsArr[i];
}
sortStrs(p,n);
printf("After sorted:\n");
for(i=0;i<n;i++)
{
printf("%d:",i);
puts(p[i]);
}
return 0;
}
//对指针数组p中各元素指向的字符串进行排序,排序的结果仍然存放在p数组中。按ASCII表编码顺序排序。
//选择法
void sortStrs(char *ptr[], int n)
{
for(int i=0; i<n-1; i++) {
char stemp[100];
strcpy(stemp, ptr[i]);
int k = i;
for(int j=i+1; j<n; j++){
if(strcmp(ptr[j], stemp)<0){
strcpy(stemp, ptr[j]);
k = j;
}
}
// strcpy(stemp, ptr[i]);
// strcpy(ptr[i], ptr[k]);
// strcpy(ptr[k], ptr[i]);
char * ptemp;
ptemp = ptr[i];
ptr[i] = ptr[k];
ptr[k] = ptemp;
}
}
5)指针作为函数的返回值(返回值类型是指针的函数)
不过需要注意的是,返回的指针不应该指向函数的局部变量,因为局部变量只在函数这一次被调用期间有效,
如果返回了指向局部变量的指针,又在之后的程序中访问了这个指针所指的内容,就会访问越界,可能会引发程序异常.
int* Ret()
{
int a = 10;//局部变量
return &a;
}
int main()
{
int *ptr = Ret();
cout << *ptr << endl; //访问越界
*ptr = 0; //写内存也是访问越界
}
只能返回全局变量或者静态变量的地址,返回实参的地址,其他情况可能引发错误,要谨慎使用。
6)指向常量的指针和指针常量
const和指针的联合使用
动态内存分配函数
malloc: 需要手动计算内存空间的大小,一个参数 void *malloc(unsigned int num_bytes); 不会对分配的空间进行初始化,里面为随机值,效率更高。
calloc:不需要手动计算内存空间的大小,两个参数 void *calloc(size_t n, size_t size);会对分配的空间进行初始化,初始值为0.
realloc函数和上面两个有本质的区别,其原型void* realloc(void *ptr, size_t new_Size)
如果size较小,原来申请的动态内存后面还有空余内存,系统将直接在原内存空间后面扩容,并返回原动态空间基地址;
如果size较大,原来申请的空间后面没有足够大的空间扩容,系统将重新申请一块(new_Size)的内存,并把原来空间的内容拷贝过去,原来空间free;
如果size非常大,系统内存申请失败,返回NULL,原来的内存不会释放。
注意:如果扩容后的内存空间较原空间小,将会出现数据丢失,如果直接realloc(p, 0);相当于free( p ).