C语言之数组、指针

数组的初步了解

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 = &num;
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;
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值