1、变量的存储
(1)在C语言中,一般使用以下方式定义变量:
int i;
以上语句的作用是:要求系统在内存中分配一个类型为int型的存储空间。
(2)变量名实质是内存单元地址的一个符号,该符号代表内存地址(变量所占单元的首地址)。
(3)取地址符&:使用该运算符可以获取变量的内存单元地址(如果占用多个内存单元,则将得到首地址)。
查看变量地址示例:
#include<stdio.h>
int main(void)
{
int i = 10;
printf("变量i的值为:%d\t存储的地址为:%d\n", i, &i);
return 0;
}
2、指针和简单变量
(1)指针本身是一种数据类型,也要占据一定的质量(内存)。但在一些算法或数据结构分析中,指针变量往往用一个指向符号来表示,因此被称为“指针”。
(2)指针的概念:在C语言中,将内存单元的编号或地址称为指针。可通过一个变量来存放指针,这种变量称为指针变量。因此一个指针变量的值就是某个内存单元的地址,或称为某内存单元的指针(即指针变量存放指针)。
(3)访问数据的方式:直接访问和间接访问。
直接访问:使用变量名直接访问内存单元。
间接访问:通过存储的指针地址访问该地址中对应存储的数据。
3、创建指针
(1)在C语言中,必须将指针保存在指针变量中。
(2)定义指针变量的一般形式:
*类型说明符 变量名1,变量名2;
其中,星号(*)表示只是一个指针变量,变量名即定义的指针变量名,类型说明符表示本指针变量所指向的变量的数据类型。
(3)一个指针变量只能指向同类型的变量。针对不同类型的指针变量,对指针进行加减运算时指针移动的字节数将会有所不同。
4、初始化指针变量
(1)指针变量必须初始化才能使用
因为刚定义的指针变量中可能有一个随机的值,在指针变量中,该值表示一个内存地址。如果该内存地址正好是操作系统的代码区域,修改该内存地址中的值就会视系统崩溃。
(2)指针变量的赋值只能赋予地址,决不能赋予任何其他数据(0除外),否则也可能引起错误。
(3)未使用的指针应当赋值NULL,已表明它未执行任何地方。当指针的值为NULL时,称该指针为空指针。
NULL是一个符号常量,表示0值。所以可以给指针变量赋值为0,与赋值为NULL等价。
5、指针变量的引用
运算符“”不仅能从指定内存地址中取得内容,也可修改指定内存地址中的内容。
例如:可以使用pi来操作变量i中的值。
int i = 10;
int *Pi = &i;
*pi和变量i都指向完全相同的内存地址,可以互相取代。指针和变量的关系图如下:
i表示该地址中存储的数据
&i表示变量i的地址
*pi表示其中存储的地址储存的数据,该数据为i的数据
pi表示该地址中存储的数据,该数据为i的地址,即&i
&pi表示指针变量pi的地址
示例:
#include<stdio.h>
int main(void)
{
int i = 10;
int *pi = &i;
printf("i最终表示为:%d\n&i最终表示为:%d\n"
"*pi最终表示为:%d\npi最终表示为:%d\n&pi最终表示为:%d\n",
i,&i,*pi,pi,&pi);
return 0;
}
6、数组赋值
若有一个二维数组a[M][N],定义一个指针变量int *p;将二维数组的某一行的首地址赋予p有3种方式:
p = a[M];
p = &a[M];
p = &a[M][0];
7、指向多维数组的指针变量
(1)p++:相当于(p++),因为++的优先级大于*
(2)多维数组的指针表示:
①a是一个数组,a执行一个数组;
②a+i指向一个数组;
③a、a、&a和&a[0][0]的数值相同;
④a[i]+j是一个地址,(a+i)+j与其相同;
⑤a[i][j]是数组元素的值,((a+i)+j)和(a[i]+j)与其相同。
(3)定义指向多维数组额指针变量的基本格式:
*数据类型 (指针变量名)[N];
赋值方式:
指针变量名=多维数组名;
(4)示例:用指针变量指向数组的一行数据
#include<stdio.h>
#include<stdlib.h>
int main(void)
{
int a[4][5], i, j;
int(*p)[5];
p = a;
srand(NULL);
for (i = 0; i < 4; i++)
for (j = 0; j < 5; j++)
*(*(p + i) + j) = rand() % 100;
for (i = 0; i < 4; i++)
{
for (j = 0; j < 5; j++)
printf("%4d", *(*(p + i) + j));
putchar('\n');
}
return 0;
}
8、数组名作为函数的参数
(1)一维数组作为参数:当向函数传递一维数组作为实参时,只需将数组名填入函数的形参部分即可。
示例:一维数组作为函数参数
#include<stdio.h>
void display(char s[])
{
int i = 0;
while (s[i] != '\0')
printf("%4c", s[i++]);
putchar('\n');
}
int main(void)
{
char s[] = "Hello Word!";
display(s);
return 0;
}
(2)一维数组指针作为参数
#include<stdio.h>
void display(char *s)
{
int i = 0;
while (*s)
printf("%4c", *s++);
putchar('\n');
}
int main(void)
{
char s[] = "Hello Word!";
display(s);
return 0;
}
(3)二维数组作为参数
#include<stdio.h>
#include<stdlib.h>
#define M 4
#define N 5
void Putin(int a[][N])
{
int i, j;
srand(NULL);
for (i = 0; i < M; i++)
for (j = 0; j < N; j++)
a[i][j] = rand() % 100;
}
void Display(int a[][N])
{
int i, j;
for (i = 0; i < M; i++)
{
for (j = 0; j < N; j++)
printf("%4d", a[i][j]);
putchar('\n');
}
}
int main(void)
{
int a[M][N];
Putin(a);
Display(a);
return 0;
}
(4)使用数组指针的形式来定义函数的形参
#include<stdio.h>
#include<stdlib.h>
#define M 4
#define N 5
void Putin(int (*a)[N])
{
int i, j;
srand(NULL);
for (i = 0; i < M; i++)
for (j = 0; j < N; j++)
a[i][j] = rand() % 100;
}
void Display(int(*a)[N])
{
int i, j;
for (i = 0; i < M; i++)
{
for (j = 0; j < N; j++)
printf("%4d", a[i][j]);
putchar('\n');
}
}
int main(void)
{
int a[M][N];
Putin(a);
Display(a);
return 0;
}
9、指针和字符串
(1)字符串的指针表示
在字符数组的最后添加一个结束字符’\0’就是一个字符串。
定义字符串指针变量的格式:
*char 指针变量名;
其中,类型说明符必须为char。例如:char *p=”Hello world”;
(2)字符数组和字符串指针变量的区别
数组名是数组的首地址,是一个指针变量,确定存储位置后就不能改变。二字符串指针变量是一个变量,该变量保存的字符串常量首地址是可以改变的,即字符串指针变量可以指向不同的字符串常量。
例如:
char s[]="Hello World!";
是正确可行的,但以下语句却不能被编译:
char s[80];
s="Hello World!";
字符串常量只能在初始化数组时一次性保存到数组的各元素中。数组定义后,要改变字符数组的值,就只能逐个修改数组元素的值。例如,可以按以下方式对数组各元素分别赋值:
char s[80];
s[0]='H';
s[1]='e';
而对于字符串指针变量,可使用以下方式修改指针变量所指向的字符串常量:
char *s;
s="Hello World!";
s="Yes!";
在程序运行过程中,可随时改变字符串指针变量的值,实质是将另一个字符串常量的首地址填入字符串指针变量中,使其指向新的字符串常量。
10、指针数组
(1)数组指针的本质是一个指针,其指向的数据类型由一个数组构成(将数组作为一个数据类型来看待);而指针数组的本质是一个数组,数组中的每一个元素用来保存一个指针变量。
(2)指针数组的概念
一个数组的各元素值为指针类型数据时,这个数组称为指针数组。指针数组的所有元素都必须是具有相同存储类型和指向相同数据类型的指针变量。
使用以下形式定义指针数组:
类型说明符 *数组名[数组长度]
要注意指针数组和二维数组指针的区别。二维数组指针变量是单个变量,其定义形式如下:
类型说明符 (*指针变量名)[数组长度]
示例:用指针数组指向二维数组每一行
#include<stdio.h>
#include<stdlib.h>
#define M 4
#define N 5
void Putin(int *p[])
{
int i, j;
srand(NULL);
for (i = 0; i < M; i++)
for (j = 0; j < N; j++)
*(p[i]+j) = rand() % 100;
}
void Display(int *p[])
{
int i, j;
for (i = 0; i < M; i++)
{
for (j = 0; j < N; j++)
printf("%4d", *(p[i] + j));
putchar('\n');
}
}
int main(void)
{
int a[M][N];
int *p[M] = { a[0],a[1],a[2],a[3] };
Putin(p);
Display(p);
return 0;
}
示例:二维数组指针
#include<stdio.h>
#include<stdlib.h>
#define M 4
#define N 5
int main(void)
{
int a[M][N];
int(*p)[N];
p = a;
int i, j;
srand(NULL);
for (i = 0; i < M; i++)
for (j = 0; j < N; j++)
*(p[i] + j) = rand() % 100;
for (i = 0; i < M; i++)
{
for (j = 0; j < N; j++)
printf("%4d", *(p[i] + j));
putchar('\n');
}
return 0;
}
(3)指针数组将分别为每个元素分配存储空间,而二维数组指针指示一个变量,也占用一个变量的存储空间。
二维数组指针名是一个变量,所以可以对器质性自增/自减运算;而指针数组名指示表示该数组的首地址,是一个指针常量,不能对其进行自增/自减运算。
(4)用指针数组处理字符串
若需要同时处理多个字符串,则可以通过定义一个二维数组,分别存放多个字符串。例如:
char s[][14] = { "Hello","World!" };
对于二维数组每一行所占用的字节数必须相等(每个字符串后自动添加结束符’\0’),上例需要占用2x14=28字节。
示例:用指针数组处理字符串
#include<stdio.h>
/*
使用sizeof(s)/4得出的数组的元素个数,其中sizeof(s)讲得出数组s占用的内存字节数,因为一个指针占用4个字节,所以将其厨艺4,即可得到数组元素数量。
*/
int main(void)
{
int i;
char *s[] = { "Hello","World","!" };
for (i = 0; i < sizeof(s)/4; i++)
printf("%s\n", s[i]);
return 0;
}
(5)用指针数组作为函数参数
示例:指针数组作为函数参数
#include<stdio.h>
#include<string.h>
void Exchange(char *s[],int n)
{
int i, j;
char *temp;
for(i=0;i<n-1;i++)
for(j=i+1;j<n;j++)
if (strcmp(s[i], s[j]) > 0)
{
temp = s[i];
s[i] = s[j];
s[j]=temp;
}
}
void Display(char *s[],int n)
{
int i=0;
while (i < n)
printf("%s\n", s[i++]);
}
int main(void)
{
char *s[] = { "You","have","to","work","hard","!" };
printf("排序前:\n");
Display(s,sizeof(s) / 4);
Exchange(s,sizeof(s) / 4);
printf("排序后:\n");
Display(s,sizeof(s) / 4);
return 0;
}
11、指向指针的指针
(1)如果一个指针变量存放的又是另一个指针变量,则称这个指针变量为指向指针的指针变量。
(2)一般将直接执行变量的指针变量称为一级指针变量,将指向指针变量的指针变量称为二级指针变量。
(3)定义一级指针变量的基本格式:
类型说明符 指针变量名;
其中的表示定义的是一个指向“类型说明符”的指针变量。
定义二级指针变量的基本格式:
类型说明符 **指针变量名;
在指针变量名前使用了两个号,右边的表示该变量是指针类型的变量,左边的*号表示该变量所指的对象是指针变量。
二级指针变量也用来保存一个变量的地址 ,一般使用取地址运算符“&”将一级指针变量的地址保存到该变量中,使用一下语句可以定义一个二级指针变量:
int i = 0;
int *p = &i;
int **pp = &p;
i表示该地址中存储的数据
&i表示变量i的地址
*p表示其中存储的地址储存的数据,即该数据为i的数据
p表示该地址中存储的数据,即该数据为i的地址,即&i
&p表示指针变量p的地址
**pp表示其中存储的一级指针的地址存储的地址指向的数据,即该数据为i的数据
*pp表示其中存储的地址储存的数据,即该数据为p中存储的数据,即为i的地址
pp表示该地址中存储的数据,即该数据为一级指针p的地址
&pp表示该二级指针变量pp的地址
示例:
#include<stdio.h>
int main(void)
{
int i = 10;
int *p = &i;
int **pp = &p;
printf("i最终表示为:%d\n&i最终表示为:%d\n"
"*p最终表示为:%d\np最终表示为:%d\n&p最终表示为:%d\n"
"**pp最终表示为:%d\n*pp最终表示为:%d\npp最终表示为:%d\n&pp最终表示为:%d\n",
i,&i,*p,p,&p,**pp,*pp,pp,&pp);
return 0;
}
12、指针和函数
(1)返回指针的函数
每个函数可返回一个值,返回值可以为char、int等类型,当返回值类型设置为void时,表示函数没有返回值。
在C语言中,还允许一个函数的返回值是一个指针(即地址),这种返回指针的函数称为指针型函数。
定义指针型函数的形式如下:
类型说明符 函数名(形参表)
{
函数体
}
其中,函数名之前加了“”号表名这是一个指针型函数,其返回值是一个指针。类型说明符表示了返回的指针值所指向的数据类型。一般用这种函数返回一个字符串常量的首地址。
示例:指针型函数,输出某个月份的英语
#include<stdio.h>
char *Month(int month)
{
char *chancemonth[] = { "No have!","January","February","March","April","May","June","July","August","September","October","November","December" };
char *p;
if (month >= 1 && month <= 12)
p = chancemonth[month];
else
p = chancemonth[0];
return p;
}
int main(void)
{
int i;
printf("请输入某个月份的阿拉伯数字:");
scanf_s("%d", &i);
printf("%d月的英文为%s\n", i, Month(i));
return 0;
}
(2)指向函数的指针
每个函数总是占用一段连续的内存区域,而函数名就是该函数所占内存区域的首地址。可以把函数的这个首地址(或称入口地址)赋予一个指针变量,使该指针变量指向该函数,通过指针变量可以找到并调用这个函数。把这种指向函数的指针变量称为函数指针变量。
定义函数指针变量的基本格式为(括号内的数据类型的个数和种类需与对应函数的数据类型个数和种类一致):
数据类型 (*变量名)(数据类型1,数据类型2,……)
或
数据类型 (*变量名)(数据类型1 变量名1,数据类型2 变量名2,……)
赋值基本格式为:
变量名=&函数名;
示例1:声明一个可以指向MyFun()函数的函数指针变量FunP。
void(*FunP)(int);
或
void(*FunP)(int x);
整个函数指针变量的声明格式如同函数MyFun()的声明一样,这个FunP指针变量也可以指向其他具有相同参数及返回值的函数。
#include<stdio.h>
void MyFun(int x)
{
printf("数值为:%d\n", x);
}
void(*FunP)(int);
int main(void)
{
MyFun(10);
FunP = &MyFun;
(*FunP)(20);
return 0;
}
示例2:指向函数的指针作为参数
#include<stdio.h>
int add(int a, int b)
{
return a + b;
}
void count(int(*p)(int,int))
{
int i, j;
printf("请输入需要计算的两个数据:");
scanf_s("%d%d", &i, &j);
printf("%d+%d=%d\n", i, j, (*p)(i, j));
}
int main(void)
{
count(add);
return 0;
}
13、指针和const变量
const 数据类型 *指针变量名
等价于
数据类型 const *指针变量名
在这种写法中,const关键字控制“*指针变量名”的值不能修改。“*指针变量名”表示指针变量指向的变量,即使用这种方式定义的指针,不允许使用“*指针变量名”的方式修改指向变量的值。
例如,如下代码:
int i = 3, j;
int const *p = &i;
*p = 5;/*此行将出错,显示为:"表达式不可修改左值"*/
i = 8;/*此行成立*/
p = &j;/*此行成立*/
需要注意的是,使用const关键字控制的是p,所以不能通过p的方式修改变量i的值,上例第三行。但是可以直接对变量i赋值,上例第四行。
同时,因为const关键字没有控制变量p,所以变量p的值也可以修改,即可以让其指向其他变量,上例第五行。
数据类型 * const 指针变量名
在以上语句中,关键字const将*和指针变量名分隔开了。这时,使用const关键字控制变量p,所以变量p的值不允许修改,即该指针是一个指针常量,与数组名相同。
例如:
int i = 3, j=4;
int * const p = &i;/*必须在定义时就赋值*/
*p = 5;/*此行成立*/
p = &j;/*此行将出错,显示为:"表达式必须是可修改的左值"*/
在以上程序中,使用const关键字控制变量p,对于这种类型的指针变量,必须在定义的同时为其赋初值。在第二行中定义后,在以后的程序中将不允许再修改指针变量的指向(不允许修改变量p的值)。如以上程序的第四行将出错,不能通过编译。
虽然指针变量的值不允许修改,但其只想的变量的值是允许修改的,如第三行是正确可行的。