一、数据存储和地址
计算机的存储是以字节为单位的一片连续的存储空间,每一个字节都有一个编号,这个编号就称为内存地址。
程序中定义了一个变量,在对程序进行编译时,系统就会给这个变量分配内存单元,如:整型占2个字节、实型占4个字节、双精度型占8个字节、字符型占1字节,变量赋值的过程,就是这个数据放到对应的这块空间中。
间接访问 : 将一个变量的地址存放在另一个变量中。如将变量 x 的地址存放在变量p 中, 访问x 时先找到p,再由p 中存放的地址找到 x。
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[])
{
@autoreleasepool {
// 变量在内存中的存储
char a=100;
// 1意识到:a最后是存储到内存中的
// 到底存储在哪个位置
// &取地址符,用于获取一个变量的位置
// %p输出地址的格式符;
printf("%p\n",&a);
printf("%ld\n",(long)&a);
// 2.到底占用多大空间
printf("size = %lu\n",sizeof(a));
// 查看n的地址和n的大小
int n=123;
printf("size=%lu\n",sizeof(n));
printf("%p",&n);
}
return 0;
}
二、指针变量的使用
指针: 一个变量的指针就是该变量的地址(指针就是地址)。
指针变量: 存放变量地址的变量, 它用来指向另一个变量。
指针变量的定义包括三个内容:
指针类型说明,即定义变量为一个指针变量;
指针变量名;
变量值(指针)所指向的变量的数据类型, 基类型。
格式 : 数据类型 * 指针变量名 ;
例 int *p1 ;
char *p2 ;
说明 :
(1) 在变量定义时, * 号表示该变量是指针变量
( 注意: 指针变量是p1 , p2 , 不是*p1 , *p2 )
(2) 定义指针变量后, 系统为其分配存储空间, 用以存放
其他变量的地址, 但在对指针变量赋值前, 它并没有
确定的值, 也不指向一个确定的变量
示例
//思考问题:变量的地址是不是数据?
// 以后需求需要保存地址?
// 如何保存?
int num=10;
// num变量的地址:&num
// 如果需要保存一个地址,保存在指针变量中
// 语法:定义指针变量,
// 变量名:p
// 变量类型:int *
// 表示这个变量可以存储整型的地址;
int *p;
// 把一个地址保存到这个变量中
p=#
printf("&num=%p\n",&num);
printf("p= %p\n",p);
// 通过p使用地址指向的值
// *p的语法形式
printf("num=%d\n",num);
printf("p=%d\n",*p);
指针变量的引用
指针变量有两个有关的运算符:& 取地址运算符, * 指针运算符。
例如:「&a」表示变量a的地址,「*p」表示指针变量p指向的变量。
若有赋值语句p=&a;,则
1.&*p=p。「&」和「*」运算符的优先级相同,结合方向为从右往左。
「&*p」等价于「&(*p)」
「*p」就是变量a,再执行&运算,得到&a,也就是p。
因此「&*p」=「&(*p)」=「p」
2.*&a=a。「*&a」等价于「*(&a)」,
&a即为p,再执行*运算,得到*p,也就是变量a。
因此*&a=*(&a)=*p=a,可见,&和*运算符在一起,其作用相互抵消。
关于指针的引用有两点需要注意:
1.指针变量是用来存放地址的,不要给指针变量赋常数值。
2.指针变量没有指向确定地址前,不要对它所指的对象赋值。由于变量未进行初始化,其初始值是随机数,此时赋的值写入以这个随机数为地址的存储区间中,7可能会破坏系统的正常工作状态。所以应该在指针变量指向一个确定的变量后,再进行赋值。
三、指针与函数
1、形参为指针变量时,实参和形参之间的数据传递
若函数的形参为指针类型,调用该函数时对应的实参必须是基类型相同的地址值或者是已指向某个存储单元的指针变量。
示例
//把指针变量a和b所指的存储单元中的两个值相加,然后将和值作为函数返回值
myadd(int *a,int *b)
{
int sum;
sum =*a + *b;
return sum;
}
main()
{
int x,y,z;
scanf("%d%d",&x,&y);
z = myadd(&x,&y);
printf("%d + %d = %d\n",x,y,z);
}
在此程序中,主函数调用myadd函数时,系统为myadd函数的形参a和b开辟两个基类型为int 类型的临时指针变量,并通过实参&x,&y把x和y的地址传送给它们。语句sum = *a + *b的含义是:分别取指针变量a和b所指向存储单元中的内容,相加后存入变量sum中。实际上就是把主函数中x和y的变量中的值相加存入变量sum中了。
2、通过传送地址值,在被调用函数中直接改变调用函数中变量的值
到目前为止,我们已经知道形参值的改变并不能改变对应实参的值,把数据从被调用函数返回到调用函数的唯一途径是通过return语句返回函数值,这就限定了只返回一个数据。但在上述例子中通过传递地址值,可以在被调用函数中对调用函数中的变量进行引用,这也就使得通过形参改变对应实参的值有了可能,利用此形式就可以把两个或两个以上的数据从被调用函数返回到调用函数。
3、函数的调用
函数调用的一般形式为:函数名(实参表列);
注意:如果是调用无参函数,则“实参表列”没有,但括弧不能省略。
如果实参表列包含多个实参,则各参数间用逗号隔开。实参与形参的个数应相等,类型应匹配。实参与形参按顺序对应,一一传递数据。
如果实参表列包括多个实参,对实参求值的顺序并不是确定的,有的系统按自左至右顺序求实参的值,有的系统则按自右至左顺序。
函数调用的方式
按函数在程序中出现的位置来分,可以有以下三种函数调用方式:
1.函数语句
把函数调用作为一个语句。这时不要求函数带回值,只要求函数完成一定的操作。
函数出现在一个表达式中,这种表达式称为函数表达式。这时要求函数带回一个确定的值以参加表达式的运算。例如:c=2*max(a,b);函数调用作为一个函数的实参。例如:m = max (a , max ( b , c ) ) ;其中max ( b , c)是一次函数调用,它的值作为max另一次调用的实参。m的值是a、b、c三者中的最大者。又如: printf ("%d", max(a,b));也是把max ( a , b )作为printf函数的一个参数。
函数调用作为函数的参数,实质上也是函数表达式形式调用的一种,因为函数的参数本来就要求是表达式形式。(1) 函数类型 函数名(参数类型1,参数类型2……);
(2) 函数类型 函数名(参数类型1,参数名1,参数类型2,参数名2……);
2.函数表达式
3.函数参数
对被调用函数的声明和函数原型
在一个函数中调用另一个函数(即被调用函数)需要具备哪些条件呢?
(1) 首先被调用的函数必须是已经存在的函数(是库函数或用户自己定义的函数)。
(2) 如果使用库函数,应该在本文件开头用#include命令将调用有关库函数时所需用到的信息“包含”到本文件中来。
(3) 如果使用用户自己定义的函数,而该函数的位置在调用它的函数(即主调函数)的后面(在同一个文件中),应该在主调函数中对被调用的函数作声明。
函数原型声明的一般形式为
声明的作用是把函数名、函数参数的个数和参数类型等信息通知编译系统,以便在遇到函数调用时,编译系统能正确识别函数并检查调用是否合法。(例如函数名是否正确,实参与形参的类型和个数是否一致)。
注意:函数的“定义”和“声明”不是一回事。函数的定义是指对函数功能的确立,包括指定函数名,函数值类型、形参及其类型、函数体等,它是一个完整的、独立的函数单位。而函数的声明的作用则是把函数的名字、函数类型以及形参的类型、个数和顺序通知编译系统,以便在调用该函数时系统按此进行对照检查。
示例
#import<Foundation/Foundation.h>
//void swap(int x,int y)
//{
// intt=x;
// x=y;
// y=t;
//}
// 传入了a的地址&a
// 传入了b的地址&b;
// x=&a;//等价关系:*x====a
// y=&b;//等价关系:*y====b
// 修改*x的时候实际上修改的是main函数中a的值;
void swap(int *x,int *y)
{
// 传入的是x和y;
// 通过地址x和y,去交换*x和*y的值;
intt=*x;
*x=*y;
*y=t;
}
int main(int argc, const char * argv[])
{
@autoreleasepool {
// 问题:实现一个swap函数的功能,交换两个参数的值
int a=10;
int b=20;
// 需求:把a传入函数中,修改a的值
// 不能传入a的值,而应该传入a的地址;
swap(&a,&b);
printf("a=%d b=%d\n",a,b);
int num=100;
int *p;
// 建立了等价关系
p=#
printf("%d %d",num,*p);
}
return 0;
}
查找最大和最小的两个数并输出
void getMaxandMin(int a[],int len,int *pmax,int*pmin)
{
// 初始化最大值和最小值
*pmax=a[0];
*pmin=a[0];
for(int i=0; i<len; i++) {
if (a[i]>*pmax) {
*pmax=a[i];
}
if (a[i]<*pmin) {
*pmin=a[i];
}
}
}
int main(int argc, const char * argv[])
{
@autoreleasepool {
// 传入一个数组,返回最大值和最小值
// 解决方案:通过指针修改参数
//
int max,min;
int a[10]={4,7,2,5,8,6,1,3,9,0};
getMaxandMin(a, 10, &max, &min);
printf("max=%d min=%d",max,min);
}
return 0;
}
结论:
原来只有通过数组的地址传递方法才可以改变变量的值。
单个变量的值传递通常用return的方法,如果要用参数的方式就不容易实现。
现在可以通过函数调用来改变变量的值,常用指针变量,方法是改变指针变量所指的值。
四.指针处理字符串
1、字符串是存放在字符数组中的,对字符数组中的字符逐个处理时,通常将字符串作为一个整体来使用,用指针来处理字符串更加方便。当用指向字符串的指针来处理字符串时,并不关心存放字符串的数组大小,而只关心是否已处理到字符串的结束符。
2.字符型指针变量与字符数组的区别
(1)分配内存
设有定义字符型指针变量与字符数组的语句如下:char *p ,str[50];
则系统将为字符数组str分配50个字节的内存单元,用于存放50个字符。而系统只为指针变量p分配4个存储单元,用于存放一个内存单元的地址。
(2)初始化赋值含义。字符数组与字符指针变量的初始化赋值形式相同,但其含义不同。例如:
char str[50]="I am a student ! " ;
char*p="You are a student ! " ;
对于字符数组,是将字符串放到为数组分配的存储空间去,而对于字符型指针变量,是先将字符串存放到内存,然后将存放字符串的内存起始地址送到指针变量p中。
(3)赋值方式
字符数组只能对其元素逐个赋值,而不能将字符串赋给字符数组名。对于字符指针变量,字符串地址可直接赋给字符指针变量。例如:
str="Ilove C! "; //字符数组名str不能直接赋值,该语句是错误的。
p="I loveC! "; //指针变量p可以直接赋字符串地址,语句正确
(4)值的改变
在程序执行期间,字符数组名表示的起始地址是不能改变的,而指针变量的值是可以改变的。例如:str=str+5; //错误
p=str+5; //正确
示例
// 指针的相互运算
char a[10]={1,2,3,4};
// 定义指针,指向数组中第一个元素,char
char *pc=&a[0];
printf("a[0]=%d\n",*pc);
printf("pc=%p\n",pc);
pc++;
printf("pc=%p\n",pc);
printf("a[0]=%d\n",*pc);
// 结论;char*类型的指针每次加1跨越一个字节
int b[10]={11,22,33,4,4};
int *pi=&b[0];
printf("%p\n",pi);
pi++;
printf("%p\n",pi);//地址加了四个字节
printf("%d\n",*(pi+2));//输出4
printf("%d\n",*pi+2);//输出24
/* 结论:int*类型的指针每次加1跨越四个字节;
指针变量加1的时候并不是真正的加1,而是跨越了它指向的变量的字节数,
意义何在? 任何指针加1的时候都能正确指向下一个元素,*/
实例:使用指针指向字符串:使字符串中的小写字母变成大写字母
char str[]="hello WORLD";
// 使用指针指向,遍历字符串
char *pc=&str[0];
// printf("%p\n",&str[0]);
printf("%p\n",str);//输出结果一样
// 结论:数组名就是一个地址常量(不赋值和修改)
// 任何数组中,数组名是数组中首元素的地址
char *pc=str;
//使用指针处理字符串的模板
while(*pc/*!='\0'*/) {
// printf("%c\n",*pc);
// *pc就表示字符数组中的一个字符
if (*pc>='a'&&*pc<='z') {
*pc-=32;
}
printf("%c",*pc);
pc++;
}
示例:返回指针函数
作用:在一个字符串中查找指定的字符
“hello world” ‘w’
实例:
#import <Foundation/Foundation.h>
char *mystrchr(char str[],char c)
{
//在str这个字符串中找到c这个字符的位置
char*pc=str;
while(*pc) {
//*pc每次是字符串中的一个字符
if (*pc==c) {
//找到这个字符
//重点:返回这个字符的位置(地址)
return pc;
}
pc++;
}
return NULL;
//考虑找不到的情况
//返回空指针,表示找不到
}
int main(int argc, const char * argv[])
{
//
@autoreleasepool {
char str[]="hello world";
char c='a';
char *p=mystrchr(str, c);//使用了这个函数
// 重点:很多函数都会返回指针(包括在oc中)
// 需要干的事情
// 一定要检查是否为空
// 不为空使用这个指针
// 如果为空,以*p的形式使用肯定会崩溃;
if (p!=NULL) {
printf("找到了");
printf("p=%p\n",p);
printf("*p=%c\n",*p);
}else printf("没有找到");
}
return 0;
}
计算字符串的长度
#import <Foundation/Foundation.h>
int myStringlen(char str[])
{
char*pc=str;
intcount=0;
while(*pc) {
count++;
pc++;
}
return count;
}
int main(int argc, const char * argv[])
{
@autoreleasepool {
int count;
char str[]="hello";
count=myStringlen(str);
printf("%d",count);
}
return 0;
}
示例:编写一个函数,判断某个字符串是否为回文。回文就是从左边开始读 和 从右边开始读都是一样的,比如"abcba"
#include <string.h>
#include <stdio.h>
int isHuiwen(char *str);
int main()
{
printf("%d\n", isHuiwen("a"));
return 0;
}
/*
返回1代表是回文
返回0代表不是回文
*/
int isHuiwen(char *str)
{
// 1.定义一个指向变量left指向字符串的首字符
char *left = str;
// 2.定义一个指向变量right指向字符串的末字符
char *right = str + strlen(str) - 1;
while (left < right)
{
// 如果左边和右边的字符不一样
if (*left++ != *right--)
{
return 0;
}
}
return 1;
}
示例:找出多个字符串中的最大公共子字符串,如“nbitheimanb”和“itheia”的最大子串是:”ithei”。
#include <stdio.h>
#include <string.h>
//定义一个函数,功能是获取两个字符串的最大公共子字符串
//形参分别为字符型指针变量s1、s2和整型常量 len1、len2
int getMaxSonStr(char *s1, char *s2, int len1, int len2)
{
//index表示出现第一个相同字符时s1中对应字符的下标
int index = 0;
//m 表示公共子串初始长度是0, n表示每个公共子串的长度
int m = 0, n = 0;
//遍历s1
for (int i = 0; i < len1; i++)
{
//遍历s2
for (int j = 0; j < len2; j++)
{
//遇到相同字符时
//s1[i] != '\0'可以防止越界
if (s1[i]==s2[j]&& s1[i] != '\0')
{
//子串长度为1
m = 1;
// s1[i+k]!='\0',结束符相等不比较,预防越界
for (int k = 1; (s1[i+k] == s2[j+k])&& s1[i+k]!='\0'; k++)
{
//每多一个相同字符,子串长度自增+1
m++;
}
//假定n为最长的子串的长度
if(m > n)
{
//记录最长子串的起始下标
index = i;
//将最大公共子串长度赋值给最长子串的长度值
n = m;
}
}
}
}
//当存在子串时候,输出最长子串
if(n !=0)
{
printf("最长的公共子串:");
for(int i = index; i<index+n; i++)
{
printf("%c",s1[i]);
}
}
//不存在子串输出没有
else
printf("没有公共子串!\n");
return 0;
}
int main()
{
//定义两个字符串str1和str2,并对其进行初始化
char str1[110];
char str2[110];
//提示用户输入第一个字符串
printf("请输入第一个字符串:\n");
//接收用户输入的字符串
gets(str1);
//提示用户输入第二个字符串
printf("请输入第二个字符串:\n");
//接收用户输入的字符串
gets(str2);
//计算输入字符串str1的长度
int len_str1 = strlen("str1");
//计算输入字符串str2的长度
int len_str2 = strlen("str2");
//调用函数,获取两个字符串的最大公共子字符串
getMaxSonStr(str1,str2,len_str1,len_str2);
printf("\n");
return 0;
}
总结:
返回指针的函数
• 指针也是C语言中的一种数据类型,因此一个函数的返回值肯定可以是指针类型的
• 返回指针的函数的一般形式为:类型名 * 函数名(参数列表)
指向函数的指针
• 为什么指针可以指向一个函数?
函数作为一段程序,在内存中也要占据部分存储空间,它也有一个起始地址,即函数的入口地址。函数有自己的地址,那就好办了,我们的指针变量就是用来存储地址的。因此,可以利用一个指针指向一个函数。其中,函数名就代表着函数的地址。
• 指向函数的指针的定义
定义的一般形式:函数的返回值类型 (*指针变量名)(形参1, 形参2, ...);
• 由于这类指针变量存储的是一个函数的入口地址,所以对它们作加减运算(比如p++)是无意义的
• 指向函数的指针变量主要有两个用途:1.调用函数2.将函数作为参数在函数间传递.