数组
数组的初步了解
1.定义
数组:元素类型相同的数据类型集合,数组中对象可以是数值、字符、指针、结构体等类型
一般形式:数据类型 数组名称 [数组长度];
2.声明
数组的声明不同于:int 变量;double 变量;他们只能对一个变量进行声明。但数组可以将它内的所有元素进行统一声明。
int a=1;
int a[3]={1,2,3};
int a[0];//可以存在,编译可通过,但是无用
声明数组需要:指定类型、名称、元素数量
type name[size];
1.size的大小不能为0
2.要保证所输元素的值一定是小于等于size,即{}中的<=[]中的,
否则在之后输入的值会发生错误
3.数组的内存位置
数组是由连续的内存位置组成,需要注意:第一个索引值为0,且对应存储的第一个元素,同时最大索引值是数组的总数-1.
1.数组中放在[]中的数字叫做下标或索引
2.同时,编译器和运行环境都不会检查数组下标是否越界,无论是读还是写
3.一旦程序运行,越界的数组访问可能会造成问题,导致程序崩溃
#include <stdio.h>
int main(void)
{
int a[10];
int i=0;
for(;i<10;i++){
printf("&a[%d]=%p\n",i,&a[i]);
}
return 0;
}
由输出结果,可看出数组元素在内存中的地址是:从连续分配的
4.数组初始化
- 初始化:变量分配内存的同时指定其初始值
a.逐个赋值:
int n[5]={1,2,3,4,5};//完全初始化
int n[5]={1,2};//部分初始化
int n[]={1,2,3,4,5};//数组的集成初始化
int n[10]={[0]=2;[2]=3,6};//c99 only!
/*用[n]在初始化数据中给出定位
没有定位的数据接在前面的位置后面
其他位置补零
也可以不给出数组大小,让编译器算
特别适合初始数据稀疏的数组*/
//如果数组没有进行初始化,那么数组中的元素的值是随机的
b.运用循环给数组赋值
5.特点总结
1.所有的元素具有相同的类型
2.一旦创建则不能改变
3.数组中的元素在内存中是连续依次排列的
6.易错总结
int a[10]; √
int a[3+9];√
int a[]={1,2,3};√
//不必写出a[3],当元素初始化完成时,则不用书写
int a[3]={12,3};
//可以这样书写,只是程序默认最后a[2]=0
int a[3]={1,2,3,4};
//不可,超过了数组长度
char a[3]={'a',98,'c'};
//该处98则为字符类型,'b'
- 要把一个数组的所有元素交给另一个数组,必须采用遍历
数组的使用
#include <stdio.h>
int main(void)
{
int a[10];
int i=0;
for(;i<10;i++){ //不能为i<11,不可越界
a[i]=i;
}
return 0;
}
给数组赋值,常使用for循环、while循环,但要注意,不要多赋值,越界访问会发生错误
数组的大小,即数组的元素个数,可通过该方式获得:
sizeof(a)/sizeof(a[0])
sizeof(数组名)/sizeof(元素类型)
1.数组的赋值
int a[3]={0,1,2};
int b[]=a;//这样的形式是错误的
//要把一个数组的所有元素交给另一个数组,必须采用遍历
for (i=0;i<length;i++){
b[i]=a[i];
}
/*通常使用for循环,让循环变量i从0到<数组的长度,
这样循环体内最大的i正好是数组最大有效下标*/
1.数组作为函数参数时,往往必须再用另一个参数来传入数组的大小
2.数组作为函数的参数时,不能在[]中给出数组的大小,不能再利用sizeof来计算数组的元素个数
二维数组
int a[3][5]
通常理解为a是一个3行5列的矩阵
1.二维数组的遍历
for(i=0;i<3;i++){
for(j=0;j<5;j++){
a[i][j]=i*j;
}
}
/*a[i][j]是一个int,表示第i行第j列上的单元
a[i,j]=a[j] 根据运算i,j结果为 j*/
2.二维数组的初始化
1.列数是必须给出的,行数可以由编译器来数
2.每行一个{},逗号分隔
3.最后的逗号可存在,有古老的传统
4.如果省略,表示补零
一维数组与字符串
1.基本概念
- 字符数组:元素类型为char的一维数组,char arr[100];
- C语言通过字符数组来存放字符串
– char str[]=“hello”;//str数组元素个数为6个,字符串末尾’\0’
– C语言中允许通过字符串字面值对字符数组进行初始化 - 给定一个字符数组,怎么判断它是不是字符数组?只需判断字符数组中是否有’\0’
–char str[]="";//str数组元素个数是1,'\0'
- 不能用运算符对字符串做运算
- 通过数组方式可以遍历字符串
- 两个相邻字符串,输出时会被自动的链接
char str1[5]={'a','b','c','d','e'};
char str2[5]={'a','b','c','d'};
char str3[]="abcd";
/*str1不是字符串,因为str1字符数组中没有'\0',而str2、str3是字符串
- 字符个数:一个字符串个数表示的是所有字符的总个数。如:"abcd"字符个数为:5
- 字符串长度:字符串中第一个’\0’之前字符的个数,例如"abcd"字符长度为:4
2.字符串的输入输出
- printf/scanf通过%s格式转换说明符来实现字符串的输入输出
– scanf()在读取时不会读取空格,遇到空格会停止读取
–scanf()在读取时会跳过回车,容易产生无效字符
– printf()不会主动打印换行 - puts/gets字符串输入输出函数
– gets()直接读取一整行数据,包括空格
–puts()会自动在末尾打印换行符 - fflush(stdin):清理输入缓存区
3.字符串函数
- string.h里有很多处理字符串的函数
3.1 strlen()
- size_t strlen(const char *s);
- 返回s的字符串长度(不包括结尾0)
#include <stdio.h>
//调用函数,在字符串!=‘\0’时结束,进而计算出字符串长度
size_t mylen(const char*s)
{
int idx=0;
while(s[idx]!='\0'){
idx++;
}
return idx;
}
int main(int argc,char const *argv[])
{
int a;
char line[]="hello";
//使用strlen()函数
//printf("strlen=%lu\n",strlen(line));
//printf("sizeof=%lu\n",sizeof(line));
a=mylen(line);
printf("%d",a);
return 0;
}
3.2 strcmp()
int strcmp (const char *s1,const char *s2);
- 比较两个字符串
#include <stdio.h>
#include <string.h>
int main(int argc,char const *argv[])
{
char s1[]="abc";// char s1[]="Abc";
char s2[]="abc";// char s1[]="abc";
printf("%d\n",strcmp(s1,s2)); //结果为0
return 0;
}
函数自左向右逐个按照ASCII码值进行比较,直到出现不同的字符或遇’\0’为止。
如果返回值 < 0,则表示 s1 小于 s2。
如果返回值 > 0,则表示 s1 大于 s2。
如果返回值 = 0,则表示 s1 等于 s2。
#include <stdio.h>
#include <string.h>
int mycmp(const char*s1,const char* s2)
{
int idx;
while(s1[idx]==s2[idx]&&s1[idx]!='\0'){
idx++;
}
return s1[idx]-s2[idx];
}
int main(int argc,char const *argv[])
{
int a;
char s1[]="abc";
char s2[]="abc ";
a=mycmp(s1,s2);
printf("%d",a);
return 0;
}
3.3 strcpy()
char * strcpy(char *restrict dst,const char *restrict src)
- 把src的字符串拷贝到dst
– restrict表明src和dst不重叠(c99) - 返回dst
– 为了能链接起代码来
常用于复制一个字符串 char *dst=(char*)malloc(strlen(src)+1);
strcpy(dst,src);
3.4 strcat()
char *strcat(char *restrict,const char *restict s2);
- 把s2拷贝到s1后面,接成一个长的字符串
- 返回s1,s1必须具有足够的空间
- 尽可能不要使用自己创造的,会出现安全问题(越界)
3.5 putchar/getchar
putchar(c);
- 用于读取用户从键盘输入的单个字符,EOF(-1)表示写失败
- ()内可以为字符型或整型可为ASII码,但是putchar每次只能输出一个字符
- putchar函数本身不自动加回车
int main(void)
{
char ch;
ch=getchar();
putchar(ch);
return 0;
}
getchar(void);
- 此函数无参数,从键盘输入一串字符串但每次只读取第一个字符,返回值为int型(第一个字母的ASII码)
- 若想都输出可:定义一个字符变量保存这个字符,若外层嵌套循环读取,则从缓冲区内一位一位读取【将第一个字符删除,字符串左移】)
- 返回类型是int是为了返回EOF(-1)
- windows:Ctrl+Z
- Unix:Ctrl+D
#include <stdio.h>
int main(int argc,char const *argv[])
{
int ch;
while((ch=getchar())!=EOF){
putchar(ch);
}
printf("EOF\n");
return 0;
}
指针
1.引入:运算符&
- scanf("%d",&i);里的&
– 获得变量的地址,他的操作必须是明确的变量
– 地址的大小是否与int相同取决于编译器 - 指针一般形式:
地址对应内存存储的数据类型 *指针名称;
2.定义:
可理解专门来存放地址的变量
int *ptr=NULL;//定义一个整型的指针变量,初始化为NULL
char *ptr=NULL;//定义一个字符的指针变量,初始化为NULL
//易错:
int* p,q;
int *p,q;
/*两者意思相同,都仅代表p是一个指针,
而q不是,只是一个int*/
3.*p、p、&p的区分
- p:是一个指针变量的名字,表示此指针变量指向的内存地址
–如:使用%来输出,将是一个16进制的数 - *p:表示此指针指向的内存地址中存放的内容
- &p:取指针p的地址
#include <stdio.h>
int main(void)
{
int i=20;
int* p;
p=&i;
printf("&i=%p\n",&i);//i的地址
printf("p=%p\n",p);//指针p内存放的i的地址
printf("&p=%p\n",&p);//指针p的地址
printf("*p=%d\n",*p);//指针p的访问值
return 0;
}
4.常见错误
- 错误:定义了指针变量,还没有指向任何变量,就开始使用指针
- 在定义一个指针时,如果没有给它赋值,那么它的指向是不明确的,这时我们称它为野指针。
- 在定义一个指针时,如果不确定其指向,通常对将其初始化为NULL,NULL本质上是0,也就是指针
存储着0这个地址(不存在的),这个指针我们就称为空指针。
int* p;
*p=12;//这样书写是错误的
5.指针的访问
%p:打印指针时使用%p作为格式转换说明符
#include <stdio.h>
int main(void)
{
int a[10]={0,1,2,3,4,5,6,7,8,9};
printf("%p\n",a);
printf("%d\n",*arr);
return 0;
}
数组名其实存放的就是首元素地址
6.多重指针
int num = 10;
int * p1 = #
int ** p2 = &p1;
int *** p3 = &p2;
printf("%d\n", num);
printf("%d\n", *p1);
printf("%d\n", **p2);
printf("%d\n", ***p3);
printf("%p\n", p1);
printf("%p\n", *p2);
printf("%p\n", **p3);
printf("%p\n", p2);
printf("%p\n", *p3);
7.指针运算
#include <stdio.h>
int main(void)
{
char ac[]={0,1,2,3,4,5,6,7,8,9};
char *p=ac;
printf("p=%p\n",p);
printf("p+1=%p\n",p+1);
//验证:+1是移到下一个单元
printf("*(p+1)=%d\n",*(p+1));
int ai[]={0,1,2,3,4,5,6,7,8,9};
int *q=ai;
printf("q=%p\n",q);
printf("q+1=%p\n",q+1);
return 0;
}
- 指针+1:移到下一个单元,给实际指针加上的是sizeof类型
- *(p+n)—>a[n]
- 给指针加、减一个整数;递增递减;两个指针相减(指针地址之差/sizeof类型)
*p++ - p先指向下一个地址,随后取出该地址的值
- *的优先级小于++
- 常用于数组类的连续空间操作
0地址 - 当然内存中有0地址,但是0地址通常是个不能随便碰得
- 所以你的指针不应该有0
- 因此可以用0地址来表示特殊的事情
– 返回的指针是无效的
– 指针没有被真正初始化(先初始化为0 - NULL是一个预定定义的符号,表示0地址
指针的类型 - 无论指向什么类型,所有的指针的大小都是一样的,因为都是地址
- 但是指向不同类型的指针是不能直接互相赋值的,为了避免使用错指针
指针的类型转换 - void表示不知道指向什么东西的指针
– 计算时与char相同(但不相通 - 指针也可以转换类型
– int * p=&i;void* q=(void*)p; - 这并没有改变p所指的变量类型,而是让人用不同的眼光通过p看它所指的变量
指针的应用 - 需要传入较大的数据用作参数
- 传入数组做操作
- 函数返回不止一个结果
- 动态申请的内存
指针与数组
数组参数
- 四种函数原型是等价的
– int sum(int *ar, int n);
– int sum(int ar[],int n);
– int sum(int *,int n);
– int sum(int [],int n); - 数组变量是特殊的指针,数组变量本身表达地址
– int a[10]; int *p=a; //无需用&取地址
– 但是数组的单元表达的是变量,需要用&取地址符
– a=&a[0] - []运算可以对数组做也可以对指针做
– p[0]等价于a[0] - 数组变量是const的指针,所以不能被赋值
总结:
1.数组名是一个特殊的常量指针
2.数组名所表示的地址就是数组首元素的地址
3.数组访问元素下标方式,本质上是指针加数字进行寻址
4.指针也可以通过下标形式来访问数组元素
5.区别
(1)指针允许重新赋值,数组名不允许重新赋值
(2)sizeof运算符结果不一样,指针结果为4,数组名结果是数组内存大小
(3)&取地址符的结果不一样,对指针去地址获取的是指针变量的地址,对数组取地址符,获取的是数组的地址
指针-动态内存分配
int *a=(int*)malloc(n*sizeof(int));
malloc函数
1.#include <stdlib.h>需包含该头文件
void*malloc(size_t size)
2.向malloc申请的空间大小是以字节为单位的
3.返回的结果是*void,需要类型转换为自己需要的类型
(int*)malloc(n*sizeof(int))
free函数
1.把申请的来的空间还给“系统”
2.只能还申请来的空间的首地址
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int number,i;
int *a;
printf("输入数量:");
scanf("%d",&number);
//将a当数组使用
a=(int*)malloc(number*sizeof(int));
for(i=0;i<number;i++){
scanf("%d",&a[i]);
}
for(i=number-1;i>=0;i--){
printf("%d",a[i]);
}
free(a); //别忘记释放内存空间
return 0;
}