指针
指针是什么:
- 地址:
- 位置信息
- 类型信息
区别:存储单元的内容
对变量的访问都是通过地址进行
指针变量是什么:
- 存放地址的变量
指针变量作为函数参数
注意:
- c语言中实参变量和形参变量之间的数据传递是单向传递的“值传递”
- 而指针作为函数参数时,同样要遵循“单向值传递的规则`!
指针指向数组:
指针运算
如: int a[10]; int *p = a;
指针法 ==> 下标法
- p ==> &a[0]
- p+1 ==> &a[1]
- *p ===> a[0]
- *(p+1) ===> a[1]
- p2-p1 ===> 2
1
2
3
4
5
int *p1 = a[3];
int *p2 = a[5]
printf("%d\n",p2-p1);
// ==> p2 - p1 ==> 2
//表示元素之间差2个元素
注意:两个地址不能相加,如p1+p2没有意义
引用数组元素的三种方法
- 1.下标法
a[i]
- 2.通过数组名计算数组元素的地址,找出元素的值
{1.2.效率相同,都是转化为*(a+i)}
- 3.使用指针变量指向数组元素
(效率最高,不必每次都重新计算地址)
注意:
- 不能将数组名a++,因为本质上:数组名是一个常量地址,不可以改变;
- 而指针变量可以进行运算,因为它是一个变量`
数组的高级用法:
- p[i]是什么
1
p[1] == *(p+1) = a[0+1] == a[1]
- 指针引用数组元素方法总结
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#include <stdio.h>
int main(int argc, char const *argv[])
{
/* code */
int a[100] = {1,2,3,4,5,6,7,8,9,10};
int *p = &a[0]; //初始化
//int *p = a
//1.p++
p++;
printf("p++: %d\n",*p);
// == 》 2
p = a;
//2. *p++ == *(p++)
//*和++优先级级相同,自右向左运算
printf("*p++: %d\n",*p++);
printf("*p: %d\n",*p);
p = a;
printf("*++p:%d\n",*(++p));
//注意:++p 和 p++ 的区别
p=a;
//3.*(p++) and *(p++)
//这里注意自增自减符的特性即可
//4.++(*p)
p++;
printf("*p: %d\n",*p);
printf("++(*p):%d\n",++(*p));
//这里注意是将数组中的值自增,而不是p移动到下一个数组元素
//5. *(++p) == a[i++] *(p--) == a[i--] *(--p) == a[--i]
//p先自增或自减运算,在进行取值*运算
p = a;
//输出数组的100个元素
//方法一
printf("输出数组的100个元素【方法一】:\n");
while (p<a+100)
{
printf("%d",*p++);
}
printf("\n");
p=a;
//方法二
printf("输出数组的100个元素【方法二】:\n");
while(p<a+100)
{
printf("%d",*p);
p++;
}
printf("\n");
return 0;
}
使用数组名作为函数参数
通过指针引用多维数组
1.多维数组元素的地址
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int main(int argc, char const *argv[])
{
int a[3][4] = {1,3,5,7,9,11,13,15,17,19,21,23};
printf("a: %d,*a: %d\n",a,*a);
//a: 0行起始地址 *a: 0行0列元素地址
printf("a[0]: %d,*(a+0): %d\n",a[0],*(a+0));
//a[0]: *(a+0): 0行0列元素地址
printf("&a[0]: %d,&a[0][0]:%d\n",&a[0],&a[0][0]);
//&a[0]: 0行起始地址 &a[0][0]: 0行0列元素地址
printf("a[1]: %d, a+1: %d\n",a[1],a+1);
//a[1]: 1行0列元素地址 a+1: 1行起始地址
printf("&a[1][0]: %d,*(a+1)+0: %d\n",&a[1][0],*(a+1)+0);
//第1行0列元素地址
printf("a[2]: %d,*(a+2):%d\n",a[2],*(a+2));
//第2行0元素地址
printf("a[1][0]: %d,*(*(a+1)+0): %d",a[1][0],*(*(a+1)+0));
//1行0列元素的值
return 0;
}
1
2
3
4
5
6
7
a: 1870657792,*a: 1870657792
a[0]: 1870657792,*(a+0): 1870657792
&a[0]: 1870657792,&a[0][0]:1870657792
a[1]: 1870657808, a+1: 1870657808
&a[1][0]: 1870657808,*(a+1)+0: 1870657808
a[2]: 1870657824,*(a+2):1870657824
a[1][0]: 9,*(*(a+1)+0): 9
tips:在二维数组a[3][4]中
a[0] 类型为int*型(指向整型变量
a 类型为int (*p)[4] (指向含有4个元素的一维数组)
2.指向多为数组元素的指针变量
利用二维数组的顺序存储方式 a[i][j] ===> a + (i*m +j) ===》基类型: int*
利用指向包含m个元素的一维数组 int (*p)[m] ===>基类型:一维数组
3.指向数组的指针作为函数参数
- 1.用指向变量的指针变量
- 2.用指向一维数组的指针变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void average(float *p,int n)
//指针变量
{
}
void search(float (*p)[4],int n)
//p是指向具有4个元素的一维数组的指针
{
}
int main()
{
float score[3][4] = {};
average(*score,12);
// -- *score int* 型
search(score);
//--- int(*p)[4]型
}
通过指针引用字符串
字符串的引用方式:
- 数组名+下标
- 用字符指针变量指向一个字符串常量
1
2
3
4
5
#include <stdio.h>
int main()
{
char* string = "absfag adg";
return 0;}
字符指针作函数参数
使用字符指针变量和字符数组的比较
tips:字符串是常量,
在C语言中没有专门的字符串变量,如果想将一个字符串存放在变量中以便保存,必须使用字符数组,即用一个字符型数组来存放一个字符串,数组中每一个元素存放一个字符。例如“char a[10]=”love”.”
- 1.
- 字符数组由若干个元素组成每个元素放一个字符;
- 字符指针变量存放的是地址
- 2.赋值方式不同:
- 可以对字符指针变量赋值
- 不可以对数组名赋值
- 3.初始化的不同:
字符数组:
- 字符数组仅仅可以在定义!时可以整体赋值
- 之后不能使用赋值语句对全部元素进行赋值
字符指针变量:
- 可以先定义字符指针,然后再给字符指针整体赋值
- 4.存储单元的内容:
- 字符数组:预先定义大小
- 字符指针:分配一个指针变量的大小
16位机器的代码时,指针占2个字节
32位机器的代码时,指针占4个字节
64位机器的代码时,指针占8个字节
- 5.
- 指针变量的值可以改变
- 字符数组名代表一个固定的值,不能改变
- 6.
- 字符数组的值是可以改变的(可以再进行赋值)
- 字符指针变量指向的字符串常量中的内容是不可以被取代在(不能再进行赋值)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <stdio.h>
int main(int argc, char const *argv[])
{
char a[] = "abcdefg"; //字符数组
char b[14] = "abcdefg";//字符数组
char *c = "house"; //字符串常量
//经典错误:
//a = "adags";
//b[] = "aggadsga";
//注意字符数组仅仅可以在定义!时可以整体赋值
//之后不能使用赋值语句对全部元素进行赋值
printf("%s\n%s\n",a,b);
//经典错误:
//*(c+1) = 'r';
//c[1] = 'r';
//字符指针变量指向的字符串常量中的内容是不可以被取代的
//即不能被赋值
printf("%s\n",c);
char *d = a;//本质上也是字符数组,是变量
*(d+1) = '0'; //合法
printf("%s\n",d);
return 0;
}
abcdefg
abcdefg
house
a0cdefg
- 7.引用数组:
- 字符数组:
- 下标法(数组名 or 下标)
- 地址法
- 指针变量指向数组:
char *a = "china";
- 可以用a[5]获取相应的值
- 字符数组:
- 8.
- 用指针变量指向一个格式字符串,可以用其代替printf中的格式字符串
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//用指针变量指向一个格式字符串,可以用其代替printf中的格式字符串
int m = 1;
float n=2.5;
char *format_1; //字符指针
format_1 = "m=%d,n=%f\n";
char format_2[] = "m=%d,n=%f\n";//字符数组
//经典错误,数组要定义大小
//char format_3[];
//format_3 = "m=%d,n=%f\n";
printf(format_1,m,n);
printf(format_2,m,n);
m=1,n=2.500000
m=1,n=2.500000
指向函数的指针
什么是函数指针:
- 函数名代表函数的起始地址
1
2
3
4
5
int (*p)(int,int)
//p是指向函数的指针变量
//可以指向 [函数返回值类型为int]
//并且函数有 [两个整型参数的函数]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int add(int x,int y)
{
printf("%d,%d\n",x,y);
return (x+y);
}
/* code */
//定义方式1
int (*f)(int,int);
//定义方式2
int (*f)();
//错误定义:
//float (*f)(int);
f = add;
//调用方式1
add(2,3);
//调用方式2
f(4,5);
//调用方式3
(*f)(7,9)
赋值:
- 指向函数的指针只能指向
在定义时
指向的类型的函数 - 只需要给出函数名,
不能给出函数参数
- 指向函数的指针只能指向
1
2
p = max(a,b); //wrong
p = max;//right
调用:
- c = (*p)(a,b);
- c = p(a,b);
- 注意返回值c的类型
- 对函数的指针变量不能进行算术运算,没有意义
- 通过指针变量可以先后调用不同的函数
1
2
if(n==1) p = max;
else if(n==2) p = min;
用指向函数的指针作为函数参数:
- 把函数的入口地址作为参数传递到其他函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int fun(int x,int y,int(*p)(int,int));
int max(int,int);
int min(int,int);
int add(int,int);
int a=34,b=-21,n;
scanf("%d",&n);
if(n==1) fun(a,b,max);
else if(n==2) fun(a,b,min);
else if(n==3) fun(a,b,add);
int fun(int x, int y,int (*p)(int,int))
{
int result;
result = (*p)(x,y);
printf("%d\n",result);
}
返回指针值的函数
- 返回值的类型是指针类型
- 类型名 * 函数名(参数列表)
指针数组和多重指针
指针数组:
一个数组–里面的元素为指针类型数据
一般用来装多个字符串
1 2
int *p[4]; //*比[]优先级低
实例:利用指针数组实现字符串排序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <stdio.h>
#include <string.h>
#define N 13
int main(int argc, char const *argv[])
{
void sort(char *name[],int n);
void print(char *name[],int n);
char *name[] ={"4PGC938", "2IYE230", "3CIO720", "1ICK750", "1OHV845", "4JZY524", "1ICK750", "3CIO720","1OHV845", "1OHV845","2RLA629", "2RLA629", "3ATW723"};
sort(name,N);
print(name,N);
return 0;
}
void sort(char *name[],int n) //使用选择排序
{
char *temp;
int i,j,k;
for(i=0;i<N-1;i++)
{
for(j=i+1;j<N;j++)
{
if(strcmp(name[i],name[j])>0)
/*如果返回值小于 0,则表示 str1 小于 str2。
如果返回值大于 0,则表示 str1 大于 str2。
如果返回值等于 0,则表示 str1 等于 str2。*/
{
temp = name[i];
name[i] = name[j];
name[j] = temp;
}
}
}
}
void print(char *name[],int n)
{
int i;
for(i=0;i<N;i++) printf("%s\n",name[i]);
}
//输出
1ICK750
1ICK750
1OHV845
1OHV845
1OHV845
2IYE230
2RLA629
2RLA629
3ATW723
3CIO720
3CIO720
4JZY524
4PGC938
注意点:
- 不能写成以下形式:
if ( * name[ k]> * name[j] ) k= j ;
这样只比较name [ k ] 和name [ j] 所指向的宇符串中的笫1 个宇符。
- 字符串比较应当用strcmp 函数。
- 想想一下如果字符串的第一个字符都相同,那么会发生什么。
- tips:
1
2
3
4
5
6
7
8
9
10
11
12
//print 函数也可改写为以下形式:
void print_2( char * name[], int n)
{
int i=0;
char * p;
p= name[0] ;
while(i < n )
{p= * (name+ i++) ;
// p为 指向数组的指针变量
printf("%s\n" , p) ;
}
}
指向指针数据的指针变量
- 指向指针的指针
1
2
3
4
5
//定义
char **p;
//相当于
char *(*p);
实例1:char**p ---> char *name[]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
int main(int argc, char const *argv[])
{
char *name[]= {"Follow me","BASIC","Great Wall","FORTRAN" ,"Computer design"};
char **p;
int i;
for(i=0;i<5;i++)
{
p=name+i;
//name是一维指针
//p指向一维指针
printf("%s\n",*p);
}
return 0;
}
实例2:int **p --> int *num[]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int demo02(void)
{
int a[5] = {1,3,5,7,9};
int *num[5] = {&a[0],&a[1],&a[2],&a[3],&a[4]};
int **p,i;
p = num;
for(i=0;i<5;i++)
{
printf("%d",**p);
p++;
}
printf("\n");
return 0;
}
指针数组作为main函数的形参
1
int main( int argc , char * argv[ ] )
argc (argument count 的缩写,意思是参数个数)
argv(argument vector 缩写,意思是参数向量)
如果用带参数的main 函数,其笫一个形参必须是int 型,用来接收形参个数.
第二个形参必须是宇符指针数组,用来接收从操作系统命令行传来的字符串中首字符的地址。
什么情况会用到这个?
例如在DOS, UNIX 或Linux 等系统的操作命令状态下,在命令行中包括了命令名和需要传给ma in 函数的参数。
命令行的一般形式为:
命令名参数1 参数2…参数n
动态内存分配与指向它的指针
- 动态分配区域
- 堆区(heap)
- 静态存储区域:
- 栈区(stack)
怎样动态分配内存:
malloc、calloc、free、realloc函数
1.malloc函数
1
void * inalloc( unsigned int size )
size:开辟连续空间,大小为size字节
返回值:如果此函数未能成功地执行(例如内存空间不足), 则返回空指针(NU LL ) 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
#include <stdlib.h>
int main () {
char *str;
/* Initial memory allocation */
str = (char *) malloc(15);
strcpy(str, "tutorialspoint");
printf("String = %s, Address = %u\n", str, str);
/* Reallocating memory */
str = (char *) realloc(str, 25);
strcat(str, ".com");
printf("String = %s, Address = %u\n", str, str);
free(str);
return(0);
}
2.calloc函数-动态数组
1
void *calloc(size_t nitems, size_t size)
nitems − 分配一维数组元素个数
size − 每个元素个数的大小
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>
#include <stdlib.h>
int main () {
int i, n;
int *a;
printf("Number of elements to be entered:");
scanf("%d",&n);
a = (int*)calloc(n, sizeof(int));
printf("Enter %d numbers:\n",n);
for( i=0 ; i < n ; i++ ) {
scanf("%d",&a[i]);
}
printf("The numbers entered are: ");
for( i=0 ; i < n ; i++ ) {
printf("%d ",a[i]);
}
free( a );
return(0);
}
3.realloc函数 – 重新分配动态存储区
1
void *realloc(void *ptr, size_t size)
如果已经通过matloc 函数或ca lloc 函数获得了动态空间,想改变其大小,可以用recalloc 函数重新分配。
ptr - 这是指向先前用 malloc、calloc 或 realloc 分配的内存块的指针,将被重新分配。如果该指针为 NULL,函数将分配一个新的内存块并返回一个指向该内存块的指针。
size - 这是内存块的新大小,以字节为单位。如果它为 0 且 ptr 指向一个现有的内存块,ptr 指向的内存块将被解分配,并返回一个 NULL 指针。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
#include <stdlib.h>
int main () {
char *str;
/* Initial memory allocation */
str = (char *) malloc(15);
strcpy(str, "tutorialspoint");
printf("String = %s, Address = %u\n", str, str);
/* Reallocating memory */
str = (char *) realloc(str, 25);
strcat(str, ".com");
printf("String = %s, Address = %u\n", str, str);
free(str);
return(0);
}
4.free函数–释放动态内存
1
void free(void *ptr)
- ptr - 这是指向先前用 malloc、calloc 或 realloc 分配的内存块的指针,将被取消分配。如果参数传递的是空指针,则不会执行任何操作。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main () {
char *str;
/* Initial memory allocation */
str = (char *) malloc(15);
strcpy(str, "tutorialspoint");
printf("String = %s, Address = %u\n", str, str);
/* Reallocating memory */
str = (char *) realloc(str, 25);
strcat(str, ".com");
printf("String = %s, Address = %u\n", str, str);
/* Deallocate allocated memory */
free(str);
return(0);
}
void指针类型: - 指向空类型
或不指向确定的类型
的数据
注意! 不要把”指向void 类型“理解为能指向“ 任何的类型”的数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int a=3;
int * p1 = &a;
char * p2;
void * p3;
p3 = (void *)p1;
p2 = (char *)p3;
printf("%d", *p1);
p3 = &a ; printf("%d", *p3);
//定义a 为整型变扯
// pl 指向int 型变扯
// p2 指向c ha r 型变扯
// p3 为无类型指针变批(基类型为void 型)
//将pl 的值转换为void *类型. 然后赋值给p3
//将p3 的值转换为char *类型. 然后赋值给p2
//合法,输出整型变蜇a 的值
//错误, p3 是无指向的,不能指向a
void指针存在的意义:
当使用动态内存分配函数时,我们其实
只希望获得其动态存储区的起始地址
在c89中
malloc函数的返回地址一律指向字符型数据,即得到char* 型指针)
原型为:
1
char * malloc( unsigned int size)
C99对此作了修改
这些函数不是返回char *指针,而是使其
无指向
,函数返回void * 指针。这种指针称为空类型指针(typeless pointer)
,它不指向任一种具体的类型数据,只提供一个纯地址。- 不能通过void * 指针存取数据,在程序中它只是
过渡性
的 - 只有转换为有指向的地址,才能存取数据
- 不能通过void * 指针存取数据,在程序中它只是
自动进行类型转换
1 2 3 4 5
int *pt; //手动 pt = (int*)malloc(100); //自动 pt = malloc(100);