「C」 数组、字符串、指针
一、数组
(一)数组
概念:用来存储一组数据的构造数据类型
特点:只能存放一种类型的数据,如全部是int型或者全部是char型,数组里的数据成为元素。
(二)数组的定义
格式: 类型 数组名[元素个数];
举例:存储5个人的年龄
int agrs[5]; // 在内存中开辟4x5=20个字节的存储空间
可以在定义数组的同时对数组进行初始化:
int ages[5] = {17,18,19,20,21};
遍历数组:
for(int i = 0;i<5;i++)
{
printf(“ages[%d]=%d\n”,i,ages[i]);
}
注意:
(1)数组的初始化
①. int ages[5] = {17,18,19,20,21}; // 一般写法
②. int ages[5] = {17,18}; // 只对前两个元素赋值
③. int ages[5] = {[3]=10,[4]=11}; // 对指定的元素赋值,这里为第三个和第四个
④. int ages[] = {11,12,13}. // 正确,右边的元素确定,则个数可以省略这里为3个。
⑤. int ages[]; // 错误,编译器无法知道应该分配多少的存储空间
⑥. int ages[5];ages = {17,18,19,20,21}; // 错误,只能在定义数组时这样进行初始化
⑦. int ages[‘A’] = {1,2,3}; // 正确,相当于是ages[65]
⑧. int count = 5;int ages[count]; // 如果不进行初始化,则这种写法正确,编译器不会报错为其分配20个字节的存储空间,ages[0]=1;ages[1]=2;可以像这样对数组的元素进行赋值,但是2,3,4等元素的值时不确定的。
⑨. 而int count=5;int ages[count]={1,2,3,4,5}; // 这种写法是错误的,在定义数组时对数组进行初始化,元素的个数必须为常量或者不写,不能是一个变量
(2)计算数组元素
当没有表明数组元素个数时,如何对其进行遍历(要求使用数组元素个数)?
可以使用sizeof运算符来计算数组元素的个数
int count=sizeof(ages)/sizeof(int); // 数组的总长度除以单个的长度等于元素个数
(三)数组内存存储细节
假设有数组如下:
int x[] = {1,2};
char ca[5] = {‘a’,‘A’,‘B’,‘c’,‘D’};
数组名即代表数组的地址,数组的地址==数组名(ca)==数组的首元素的地址&ca[0]
在内存中,内存从大到小进行寻址,为数组分配了存储空间后,数组的元素自然的从上往下排列存储,整个数组的地址为首元素的地址。
模拟该数组的内存存储细节如下:
注意:字符在内存中是以对应Ascii值的二进制形式存储的,而非上表的形式。
在这个例子中,数组x的地址为它的首元素的地址0x08,数组ca的地址为0x03。
(四)数组-传址调用
void change(int array[]) // 数组可以作为函数的形参,可以省略数组元素的个数
{
array[0] = 100;
}
void change2(int a) //基本类型作为函数的形参
{
a = 200;
}
int main()
{
int ages[5] = {1,2,3,4,5};
change2(ages[0]);
change(ages);
return 0;
}
array数组与ages数组的地址一致,若以数组作为函数的参数,这种传递方式是传址调用,传递的是整个数组的地址,修改形参数组元素的值,就是修改实参的值。
当你把一个数组当做参数来传递时,它会看做是一个指针,在该函数体内使用sizeof运算符来计算数组的长度,得出的数值永远为8,而非数组的实际长度,因为任何类型的指针都占8个字节的存储空间。
提示:数组作为一个函数的参数时,如果函数体涉及到数组遍历等操作,通常把数组的实际元素个数也作为参数传递给函数。
如 void maxofarray(int array[],sizeof(ages)/sizeof(int)){....}
(五)二维数组
int ages[50]; // 数组能够存放50个int类型的数据
int ages1[3][10]; // 数组能够存放3个数组,每个数组存放10个数值,共3x10=30个述职数值。
一个二维数组a,a包括两个一维数组a[0]和a[1],每个一维数组都包括三个元素。
使用场合:五子棋,俄罗斯方块等,
假设:
char Y[3][2]={
{‘A’,‘B’},
{‘c,‘D’},
{‘E,‘f’}
};
内存情况:
二、字符串
(一)字符串基础
注意:字符串一定以\0结尾。
printf(“yang\n”);
其中yang为字符串常量,“yang”=‘y’+‘a’+‘n’+‘g’+‘\0’。字符串由很多的字符组成,通常使用字符数组来存储字符串,如char name[10] = “yang”;也可以以printf(name);的形式输出,即通过数组来访问字符串,但会有警告。因为默认情况下,printf函数只接受字符串常量作为参数(对变量并未写明)。
字符串的三种写法:
- char name[8] = “yang”;//数组占用了8个字节的存储空间,但是只含有5个字符。
- char name[8] = {‘y’+‘a’+‘n’+‘g’+‘\o’};
- char name[8] = {‘y’+‘a’+‘n’+‘g’+‘0’};
这三种写法在内存中的表现都是一样的。
char name[] = {‘y’+‘a’};前面不写个数,不是一个字符串,只能说是一个普通的字符数组。
char name[] = “yang”;
name[1] = ‘o’;把字符串的第二个元素值由a改成o。
(二)字符串使用注意点
(1)分析代码,了解\0的作用。
char name[]=“yang”;
char name2[]={‘o’+‘k’};
printf(“name2=%s”,name2);
%s:根据右边的参数,打印字符串(遇到\0为止)
上面代码的打印结果为:okyang
下面是内存情况分析:
问1:char name[]=“y\0ng”;则打印结果为什么?(oky)
问2:此时打印name的值,使用%s是多少?Y\0ng还是y?
(2)strlen函数
strlen函数计算字符串的长度(字符数)但不包括\0,是字符数不是字数。比如一个汉字占三个字符。
strlen(“haha”);//长度为4
strlen(“哈haha”);//长度为7而不是5
设
char name[]=“it\0cast”;
strlen(name);值为2,因为strlen从字符串的地址开始计算,直到遇到\0为止。
假设
char name[]=“itcast”;
char name2[]={‘o’+‘k’};
int size=strlen(name);
此时size的值为8。
(3)练习,编写一个函数char_contains(char str[],char c),如果字符串中包含字符c,则返回1,否则返回0。
int char_contains(char str[],char c)
{
//遍历整个字符串
for(int i=0,i<strlen(str);i++)
{
if(str[i]==c)
return 1;
}
return 0;
}
//调用语句
int result=char_contains(“yang”,‘a’);
//使用while循环
①. while(i<strlen(str))
②. while(str[i]!=‘\0’)
③. while(str[i])
④. int i=-1;while(str[i++])
(三)字符串数组
二维字符数组,存储两个字符串数组,每个的长度为1,下面是两种写法但存储情况是一样的。
char name[2][10]={“jack”,“rose”};
char name2[2][10]={
{‘j’+‘a’+‘c’+‘k’+‘\0’},
{‘r’+‘o’+‘s’+‘e’+‘\0’}
}
把rose输出:printf(“%s”,name2[1]);
输出k:printf(“%c”,name2[0][3]);
三、指针
前导程序
1 #include<stdio.h> 2 3 void change(int *n); 4 5 int main() 6 7 { 8 9 int a = 90; 10 11 change(&a); 12 13 printf("a = %d\n",a); 14 15 return 0; 16 17 } 18 19 void change(int *n) 20 21 { 22 23 *n = 10; 24 25 }
(一)基本知识点
int a = 10;
int *p; // 定义一个int类型的指针
p = &a; // 指针变量p指向了变量a
*p = 20; // 使用指针不通过变量直接修改变量a的值为20
*p表示访问指针变量p指向的存储空间
指针一个作用:能够根据一个地址值,访问(取值 | 赋值)对应的存储空间
指针变量p前面的int,表示指针的类型
①. int *p;
②. *p = 10;
两个*的区别:前一个起标识作用,表明定义的p是一个指针,后者的*表示通过访问p指向的地址空间
(二)指针使用注意
①. int *p;
double d = 10.0;
p = &d; // 不建议此种做法
②. int *p;
p = 200; // 指针变量只能存储地址
③. int *p;
printf(“%d\n”,*p); // 指针变量未经初始化,不要拿来间接访问其他的存储空间
④. int *p = &a;但是不能写成 int *p;*p=&a;这种写法没有任何的意义,可以认为*是和类型符一起使用的。
⑤. *是指针运算符,访问指针指向的空间
1 #include <stdio.h> 2 3 int main() 4 { 5 /* 不建议写法,int *p只能指向int 类型的数据 6 int *p ; 7 8 double a = 10.0; 9 10 p = &a ; 11 */ 12 13 /* 指针变量只能存储地址 14 int *p; 15 p = 200; 16 */ 17 18 /* 指针变量未经过初始化,不要间接拿来访问其他存储空间 19 int *p; 20 21 printf("%d\n",*p ); 22 */ 23 24 int a = 10; 25 //定义变量时的*仅仅是一个象征,没有其他特殊含义 26 int *p= &a; 27 28 //不正确的写法 29 //*p = &a; 30 31 //这个时候* 的作用: 访问指向变量p的存储空间 32 *p = 20; 33 34 char c = 'A'; 35 char *cp = &c; 36 *cp = 'D'; 37 printf("%c\n",*cp); 38 39 return 0; 40 }
(三)指向指针的指针
int a = 10;
int *p = &a; // 指向int型的指针
int **p1 = &p; // 指向指针的指针
int ***p2 = &p1; // 三级指针
*p2相当于访问p1;
**p2相当于访问p;
***p2相当于访问a;
*p1相当于访问p;
一颗星一条线。
(四)指针练习
编写一个函数,计算a和b的和与差(一个函数返回两个值)
提示:指针的作用之一:实现让函数拥有多个返回值
1 #include<stdio.h> 2 3 int SumAndMinus(int n1,int n2,int *n3) 4 5 { 6 7 *n3 = n1 - n2; 8 9 return n1 + n2; 10 11 } 12 13 int main() 14 15 { 16 17 int a = 10; 18 19 int b = 11; 20 21 int sum; 22 23 int minus; 24 25 sum = SumAndMinus(a,b,&minus); 26 27 printf("和 = %d,差 = %d\n",sum,minus); 28 29 }
(五)有关指针的疑问
注意:任何类型的指针都占据8个字节的存储空间,那么为什么还要为指针加上类型呢?
对下面一段代码进行内存分析,可以证明指针类型不正确带来的严重后果。
int i = 2;
char c = 1;
int *p = &c; // 本应该是char类型的,写成了int类型
printf(“c的值是%d\n”,*p); // 打印结果为513,而非1
printf(“c的值是%d\n”,c); // 值为1
下面是上述代码的结果的内存分析:
指针p访问的本应该是1个字节空间的数据,此时因为指针的类型是int型的,因此程序自然的从指向的地址0x0a开始读取了4个字节的数据,访问的数据从1变成了513。
提示:明确了指针的数据类型,指针才能够正确的访问应该访问的空间数据。
(六)指针和数组
int ages[5] = {10,9,8,7,6};
遍历数组
for(int i = 0;i < 5;i++)
printf(“%d\n”,ages[i]);
使用指针遍历数组
int *p;
p = ages;//也可以写成p = &ages[0];,指针变量p指向了数组的首元素
元素的地址:
第一个元素的地址p &ages[0]
第二个元素的地址p+1 &ages[1]
第三个元素的地址p+2 &ages[2]
元素的值
*p ages[0]
*(p+1) ages[1]
*(p+2) ages[2]
把指针当做数组来用:
for(int i = 0;i < 5;i++)
printf(“%d\n”,*(p+i));
(1)数组元素的三种访问形式:
①. 数组名[下标]
②. 指针变量名[下标]
③. *(p+1)
(2)指针变量的+1究竟是加多少?这取决于指针的类型,如果是char类型则加1个字节,如果是int类型的,则加4个字节。
(3)利用指针来接收一个数组,指针变量指向了数组的首元素。
void change(int array[])等价于void change(int *array)。
前者存储的虽然是数组元素的首地址,但是在传递时就已经变成指针了。
示例:
void change(int *array)
{
//printf(“%d\n”,array[2]);
printf(“%d\n”,*(array+2));
}
int main()
{
int ages[5] = {1,2,3,4,5};
change(ages);
}
调用的结果为:数组的第三个元素3
若改给change(&ages[2]);则调用的结果为5,因为此时array指向的是ages[2],把ages[2]当做了array的首元素
(七)指针和字符串
(1)基础知识
下面两行代码有着本质的区别:
①. char name[] = “it”;
②. char *name2 = “it”;//指针变量name2指向了字符串的首字符i
char name[0] = ‘y’;//改变第一个元素的值
printf(“%s\n”,name);//打印结果为yt
*name2 = ‘y’;
printf(“%s\n”,name2);//此时程序崩溃
这是因为,两者一个是字符串变量,一个是字符串常量。c语言的数组元素存放于栈,里面的元素可以随便修改,称为字符串变量。而字符串常量存放于常量区,会缓存起来是不可更改的。
char *name1 = “it”;
char *name2 = “it”;
printf(“%p %p”,name1,name2); // 地址是一样的,说明name1和name2指向的是同一个字符串。
掌握字符串定义的两种方式:
①. 利用数组
特点:字符串里边的字符是可以修改的,适用于内容需要经常修改时。
②. 利用指针
特点:其实是一个常量字符串,里面的字符不能修改,适用于字符串的内容不需要修改,且这个字符串经常被使用时。
(2)指针数组
整型数组:这个数组中存放的都是整型数组
指针数组:这个数组中存放的都是指针
int ages[5];
char *name[5] = {“jack”,“rose”,“yang”}; // 字符串数组的常见写法
对应于:
char name2[3][10] = {“jack”,“rose”,“yang”};
保存字符串数组的两种方式:
①. 指针数组(字符串数组)
②. 二维字符数组(字符串数组)
如何输入字符串?(使用数组——因其可变)
int main()
{
char name[20];
printf(“请输入姓名:\n”);
scanf(“%s”,name);
printf(“%s”,name);
}
(八)返回指针的函数
程序示例:
1 #include<stdio.h> 2 3 char *test(); 4 5 int main() 6 7 { 8 9 char *name = test(); 10 11 printf(“name = %s\n”,name); 12 13 return 0; 14 15 } 16 17 char *test() // 返回指针的函数 18 19 { 20 21 return “rose”; 22 23 }
(九)指向函数的指针
数组名即数组的地址,函数名即函数的地址。
假设有函数:
void test ()
{
printf(“调用了test函数\n”);
}
void (*p)(); // void指针变量指向的函数没有返回值,()表示p指向的函数没有形参
p = test; // 有指针p,把指针p指向函数
有三种方式可以操纵函数:
①. 直接调用test();
②. 利用指针变量简介调用 (*p)();
③. 简化使用p()
练习:
假设有函数声明为 int sum(int a,int b)
则相对应的指向该函数的指针应该定义为:int (*p)(int ,int);
把指针变量p指向函数:p = sum;
调用该函数的三种方式:
(1)int c = p(10,12);
(2)int c = sum(10,12);
(3)int c = (*p)(10,12);
假设函数声明为:double haha(double a,char *b,int c);
则定义一个指向haha函数的指针应该为:double (*p)(double,char *,int) = haha;