目录
(2).上述代码中,通过去掉变量名,留下类型名这一方法可知,指针在定义时“ * ”代表类型说明符,代表变量p是一个int型指针变量,所以此处并不进行指针运算;
(3).指针的访问时一种间接访问,先对指针访问,在对指针中地址指向的内存空间进行访问 ;
(5).由于指针是用来存储地址的变量,所以在栈区中,指针的大小空间恒为8个字节,当然,这是对于64位系统的大小,而32位的大小则为4个字节
(1).对于一个指针变量加减n时,应对指针加减sizeof(基类型)乘上n
(3).编写函数时,返回值不得为数组,但是可以为指针,通过这一特性,便可以更便捷的去遍历以及修改数组。
(4).由于指针变量存储的是地址,而地址又具有连续性,所以可以对其进行比较
(5).指针不能进行相加,但是可以进行相减,但是相减时需注意左右操作数需要类型相同
(1).在进行数组传参时, sizeof()函数会将变量类型替代为传参数名
1.函数指针:函数的第一条语句所在地址就是函数地址,函数名即为函数地址。
一、低级指针
1.指针的定义
(1).指针类型指的是用来装地址的数据类型;
整型指的是用来装整数的类型;
int *p //(基类型 *指针变量名)
(2).上述代码中,通过去掉变量名,留下类型名这一方法可知,指针在定义时“ * ”代表类型说明符,代表变量p是一个int型指针变量,所以此处并不进行指针运算;
(3).指针的访问时一种间接访问,先对指针访问,在对指针中地址指向的内存空间进行访问 ;
注:此处不进行直接访问而是间接访问的原因
a.直接访问无法访问到目标变量,因为指针存储的是地址,而不是值;
b.直接访问函数无法实现被调函数改变只主调函数中参数的值这一功能;
指针是一种用来存储地址的数据类型,图示如下;
(4).指针的运行流程
a.根据指针变量中的值到内存中去定位;
b.从定位处开始向后偏移, sizeof(基类型) 个字节;
3.整体的将偏移好的那部分内存当作是一个基类型变量来看
(5).由于指针是用来存储地址的变量,所以在栈区中,指针的大小空间恒为8个字节,当然,这是对于64位系统的大小,而32位的大小则为4个字节
2.指针的使用
(1).野指针/疯指针(未进行初始化,不明确指向的地方)
与本文开头的代码类似,开头代码中所定义的指针就是一个野指针,没有进行初始化。
int *p;
*p = 10;
注:
定义指针时,上图中的写法是错误的,* 要求参与其运算的变量为指针为指针,而第一句中*的意义是类型说明符,而不是运算符,所以变量p就是指针,而不是*p。
(2).最大值问题的求解
由于函数问题,前几篇文章进行过讲解,所以此处进行函数调用的方式进行解答
int Max(int a ,int b, int *max)
{
max = a > b ? a : b;
return max;
}
int main(void)
{
int max ;
max = Max(4 ,6 , &max);
printf("%d\n", max);
return 0;
}
(3).交换
void swap(int *a,int *b)
{
int t = *a; // 此处t作为缓存保存了指针a所指向的值
*a = *b; // b指针指向的值赋给a;
*b = t; // 缓存值赋给b
}
int main(void)
{
int a = 10;
int b = 20;
swap(&a, &b);
printf("%d\n%d\n", a , b );
}
(4).数组遍历
int main(void)
{
int a[] = {1,2,3,4,5};
int len = sizeof(a) / sizeof(a[0]);
int i ;
int *p = a ;
for(i = 0 ;i < len; ++i)
{
//printf("%d\n",*p);
printf("%d\n", *(a + i )); //此处对于数组进行偏移的操作,这里a代表&a[0]
}//此处循环结束时,i就大于了数组长度len 会发生数组的越界访问,从而出现野指针导致程序崩溃
return 0;
}
注:
若需要得到数组下标可以进行如下操作:
int *p = a;
int *q = p + 2;
printf("%d\n", q - p);
(6).逆序
void swap(int *a, int *b)
{
int t = *a;
*a = *b;
*b = t;
}
void reverseArray(int *a, int len)
{
int i;
for(i = 0 ; i < len / 2; ++i)
{
swap(a,a + len - i - 1);//此处交换的是地址,而不是值,由于此处是指针变量,可以直接修改主调函数的值
}
}
(7).排序
void swap(int *a, int *b)
{
int t = *a;
*a = *b;
*b = t;
}
void choiceSortArray(int *a,int len)
{
int i , j ;
for(i = 0 ; i < len - 1; ++i)
{
for(j = i + 1 ; j < len ; ++j)
{
if(*(a + i) > *a[j])
{
swap(a + i,a + j);
}
}
}
}
(8).查找
int *binaryFind(int *begin, int len, unsigned long n)
{
int end = len -1 ;
while(end <= begin)
{
int mid = (end - begin) / 2;
if( n > *(a + mid))
{
begin = mid + 1;
}
else if(n < *(a + mid))
{
end = mid - 1;
}
else
{
return 1;
}
}
return -1;//此处判定, 若为-1就代表没有找到,循环为结束终止,不是运行终止
}
二、中级指针
1.进阶
(1).对于一个指针变量加减n时,应对指针加减sizeof(基类型)乘上n
int *p ;
p + 1 <==> p 地址 + sizeof(1 * 基类型)
(2).空指针
int *p = NULL; //此处NULL指的是一种状态
(3).编写函数时,返回值不得为数组,但是可以为指针,通过这一特性,便可以更便捷的去遍历以及修改数组。
错误:
int [] Name(int a)
正确:
int * Name(int a)
(4).由于指针变量存储的是地址,而地址又具有连续性,所以可以对其进行比较
int i = 10;
int *p = &i;
int *q = &i + 2;
printf("%d\n", p > q);//此处输出为一逻辑值,非零即一
(5).指针不能进行相加,但是可以进行相减,但是相减时需注意左右操作数需要类型相同
2.进阶使用
(1).迭代器
a.数组的遍历
void printArray(int *begin,int *end) //在传参时,实参应为数组的首地址和尾地址
{
while(begin <= end)
{
printf("%d\n",*begin)
++begin;
}
}
b.逆序
void swap()//参考上文中的swap函数,内容相同,后文不做陈述,直接引用
void printArray()
void reverse(int *begin,int *end)
{
while(begin < end) //
{
swap(begin,end);
begin++;
end--;
}
}
(2).递归(无循环,无变量定义)
a.数组的遍历
void printArray(int *begin,int *end)
{
if(begin > end)
{
return 0;
}
else
{
printf("%d\n",*begin);
printArray(begin + 1,end);
}
}
b.逆序
void reverse(int *begin,int *end)
{
if(begin > end)
{
return ;
}
else
{
swap(begin,end);
reverse(begin + 1, end - 1);
}
}
c.排序
选择排序
void choiceSort(int *begin, int *end)
{
if(begin >= end)
{
return ;
}
else
{
for(begin ; p < end ; ++begin)
{
int *p ;
for(p = begin + 1;p <= end;++p)
{
if(*begin > *p)
{
swap(begin,p);
}
}
}
}
}
冒泡排序
插入排序
快速排序
void qSort(int *begin,int *end)
{
if(begin > end) //这表示搜索范围为空,即元素不存在于数组中。递归的终止条件
{
return ;
}
int t = *begin;
int *p = begin;
int *q = end;//
while(p < q) //
{
while(p < q && *q >= t)
{
--q;
}
while(p < q && *p <= t)
{
++p;
}
swap(p,q);
}
swap(begin,p);在
qSort(begin,p-1);//
qSort(p + 1,end);//
}
4.字符串数组指针
(1).定义
char *p = "Hello"; //此处指针p保存的地址是Hello这个字符串常量中的首元素地址也就是H的地址
注意,在进行定义时,由于指针P实在栈区内保存,而Hello这一字符串常量在字符串常量区保存,但是字符串常量区内的值不能够被随意修改,也就是说,以下代码是不正确的
char *p = "Hello";
*p = "A";
(2).使用
a.Puts的使用
void Puts(char *s)
{
while(s)
{
putchar(*s);
++s;
}
putchar('\n');
}
b.Strlen
int Strlen(const char *s)
{
int couter = 0;
while(*s != '\0')
{
++s;
++couter;
}
return couter;
}
//递归写法
int Strlen(const char *s)
{
if(*s == 0)
{
return 0;
}
else
{
return Strlen(s + 1) + 1;
}
}
c.Strcpy
void Strcpy(char *des, const char *src)
{
while(*src)
{
*dest = *src;
dest++;
src++;
}
*dest = 0;
}
//若要返回地址,则可以进行如下操作
int *Strcpy(char *des ,const char*src)
{
int *ret = dest;
while(*src)
{
*dest++ = *src++;
}
*dest = 0;
return ret;
}
d.Strncpy(可以规定打印多少个字符,限定范围)
char *Strncpy(char *des,const char *src,size_t n)//size_t = unsigned long
{
char *ret = dest ;
while(*src && n )
{
*dest++ = *src++;
n--;
}
*dest = 0;
return ret;
}
e.Strcat与Strncat
char *Strcat(char *des, const char *src)
{
char *ret = des; //由于des会在后续运算中发生变化,所以需要用一个变量用于保存des地址,方便后续可以返回
while(*des)
{
des++;
}
while(*src)
{
*des = *src;
des++;
src++;
}
*des = 0;
return ret;
}
char *Strncat(char *des,const char *src,unsigned long n)
{
char *ret = des;
while(des)
{
des++;
}
while(*src && n > 0)
{
*des = *src;
des++;
src++;
n--;
}
*des = 0;
return des;
}
f.memcpy(内存拷贝)
void memcpy(void *des, const void *src, unsigned long n)
{
char *p = (char *)des; //此处强制转换是因为 void* 不能直接进行指针运算,会导致左右操作数的类型不同
char *q = (char *)src;
while(n)
{
*p = *q;
p++;
q++;
n--;
}
}
内存拷贝的原理与Strcpy类似,但是由于内存拷贝的形参是void*型,所以可以允许很多类型的参数继续拷贝。
g.Strcmp和Strncmp
int Strcmp(const char *s1,const char *s2)
{
while(*s1 == *s2 && *s1 && *s2)//循环条件,当两者值相同,并且s1与s2所指向的值都不等于零循环
{
s1++;
s2++;
}
return *s2 - *s1;
}
int Strncmp(const char *s1,const char *s2,unsigned long n)
{
while(--n && *s1 && *s2)
{
s1++;
s2++;
}
return *s2 - *s1;
}
(3).注意
a.const关键字的使用
在指针前加const会使用户无法修改指针所指向的变量,但是可以修改地址
char c = 'a';
const char *p = &c;
char d = 'd';
char *q = &d;
&p = 'c';//此处错误 不能修改指向值
p = q;//正确,可以修改地址
int const *p <=> const int *p;//可以修改地址,不能修改值
int *const p ; // 不可以修改地址,但是可以修改所指向的值
const取决于是否要修改指针所指向的内容。
b.万能指针
void *万能指针
可以接收任何类型的地址
三、高级指针
1.数组指针
数组指针的含义就是保存数组的地址的指针
int (*p)[10];
p = &a[0];
这样编写代码的原因,首先指针变量p可以指向长度为10的int型数组,而第一行代码中的*号,并不是指针运算符,而是类型说明符。
根据上述代码则可以证明如下:
printf("%d",p);
ptintf("%d",p + 1)
以上两行代码地址相差的值,是根据指针的加减来进行计算的,也就是p + sizeof(基类型),此处的基类型是int [10],可以得知两者相差四十个字节。
注意:
(1).若按照如下写,则会出现错误
int *p; p = &a;
由于第二行中,等号的左右操作数类型不相同,所以无法完成赋值操作。
(2).省略括号,则意义完全相反,会定义成指针数组
int *p[10];// 意义是,定义了一个指针型数组,内部存放十个指针
2.二维数组指针定义
int a[3][4];// a[i][j] <=> *(*(a + i) + j)
int (*p)[4] = NULL;//避免P成为野指针
p = a;// a --> &a[0]
此处需要对于二维数组有明确的定义,二维数组的元素并不是简单的行乘列,而是有行个装有列个int类型元素。
注意:
*(p + 1) //由于二维数组的含义,所以该表达式的含义是,向后偏移 p + i * sizeof(基类型)
此处基类型是Int型的一维数组,也就是Int *型;
*(*(p + 1) + 1) // 此处对以上代码继续加一再指针运算的含义就是:内部加一代表着数组的列数
也就是说 *(p + 1)代表着一维数组的首元素地址,若进行偏移,也依然按照指针偏移的
原则,这样就代表取出了第几行第几列的值
3.注意事项
(1).在进行数组传参时, sizeof()函数会将变量类型替代为传参数名
(2).指针传递为特殊值传递,传递的是地址
(3)不允许连续初始化
a[5] = b[4] = "China";//写法错误
(4).二维数组作为函数参数,形参为指向函数的指针
(5)malloc函数的使用:
malloc函数是一种动态开辟内存分配的函数,函数形式如下
void * malloc(size_t);
//在使用malloc函数时
1.malloc为用户开辟的空间是连续的
2.malloc函数使用应应包含头文件<stdlib.h>
3.在使用malloc函数时应与free函数一起使用,达到开辟空间,释放空间的效果,可以避免因一直开辟内存导致内存不足的问题
free(p);
p = NULL;//使用free函数后应该使指针悬空,防止指针成为野指针,规避越界访问导致的程序崩溃这一问题
4.malloc函数中size_t代表的是所需开辟空间的字节数,理解为公式也就是
开辟个数 * 所开辟空间类型所占字节数
5.在使用时,不可更改所开辟空间的地址值***
6.两次使用malloc,所开辟的空间并不连续
7.内存泄露问题,当使用malloc函数借用空间后并不销毁,会导致内存泄漏这一问题
(6).realloc函数的使用
realloc(void *ptr , size_t size);
// 函数作用:
1.可自动拷贝原有空间的值至新空间
2.可自动销毁原有空间(函数内自带free函数)
(7).编写函数时,函数内部的变量具有动态生存期,会随着本函数程序结束而自动销毁,所以函数内部不能返回函数内局部变量的地址。
解决方式:可以在局部变量前添加static关键字,该关键字的作用便是使局部变量改变为全局变量,使动态生存期改为静态生存期,把栈区内的局部变量放置静态区中。
(未进行初始化的全局变量会以位模式清零,所以未进行初始化的全局变量值均为0)
四、高级指针Plus
1.函数指针:函数的第一条语句所在地址就是函数地址,函数名即为函数地址。
(目的:降低函数的耦合性)
int (*p)(int a,int b)
1 . 此处*的意义为类型说明符,而不是指针运算符
2 . 指针变量P会指向形参都为(int ,int )这一类型的函数
2.系统内的快速排序函数
void qsort(void *base, size_t num, size_t size,int (*compar)(const void*,const void*))
注意:
1.表达式中,size_t size代表每个元素的大小
2.表达式中,size_t num代表所需排序的长度
3.void *base代表任意类型的base指针的形参变量
4.最后的函数指针,代表指针compar所指向的函数应该是返回值为Int 形参列表为(任意不可修改地址所指向值的指针)
int shortcmp(const void *a,const void *b)
{
short *p = (short *) a;
short *q = (short *) b;
if(*q = *p)
{
return -1;
}
else if(*q == *p)
{
return 0;
}
else
{
return 1;
}
}
int main(void)
{
short a[] = {2,3,1,4,-5};
int len = sizeof(a) / sizeof(a[0]);
qsort(a, len , sizeof(*a) , shortcmp);
int i = 0;
for(i = 0 ; i < len ; ++i)
{
printf("%d\n",a[i]);
}
}