C:数组、指针、字符串

数组

  • 数组可以用来代表多个连续的同类型存储区,这些存储区叫做数组元素
  • 数组的元素个数叫做数组的长度
  • 数组需要定义后才能使用
    int a[5] = {0,1,2,3,4};
    //a是数组名,int是数组中所存储的数据类型,5是数组的长度
    

初始化数组

  • 数组可以在定义时使用大括号进行初始化,相邻数字用逗号隔开

    /*数组初始化*/
    
    /*大括号中初始化数据个数比存储区个数多就会忽略后面多余的初始化数据*/
    int a[5] = {0,1,2,3,4,5,6};	//a为{0,1,2,3,4},编译器会忽略后面多余的初始化数据
    
    /*大括号中初始化数据个数少,就会把数组中后面的数据自动初始化为0*/
    int a[5] = {0,1,2};			//a为{0,1,2,0,0},编译器会把未指定的元素自动初始化为0
    int a[5] = {1};				//a为{1,0,0,0,0}
    int a[5] = {0};				//a为{0,0,0,0,0}
    int a[5] = {};				//a为{0,0,0,0,0}
    
    /*定义数组时不初始化,其中元素可能是随机的,也可能是之前程序运行留下的数据。*/
    int a[5];
    
    /*如果初始化时不指定数组的个数,则数组元素个数就是初始化大括号里面数据的个数*/
    int a[] = {1,2,3,4,5,6,7,8,9,0}	//此时数组中有10个元素,且数组长度不能修改
    
  • 数组每个存储区都有一个编号,称为数组下标。数组下标最大值为数组长度减1

  • 数组第一个存储区下标为0,往后依次递增

    int a[5] = {0,1,2,3,4};
    int b = a[0] + a[1];	//使用数组下标访问数组元素,a[0]是数组第一个元素,a[1]是第二个元素
    int c = a[1] + a[2];	//相当于c=1+2,所以c的值为3
    a[4] = 10;				//给数组第5个元素赋值10,此时数组为{0,1,2,3,10}
    

二维数组

  • 二维数组的内存空间也是连续的,例如:

    • arr[n][m]是由n个arr[m]的连续数组构成的
    	char arr[100][100];
    	int i = 0,j = 0;
    	while(j<0){
    		arr[i / 100][i % 100] = 0;	//初始化所有数组元素为0;
            j++;
        }
    	while (i < 10000) {
    /*从第1个元素往后遍历到第10000个,输出元素地址,两种方式输出一致,可以看出地址是连续的*/
    		printf("%d\t", &(arr[0][i]));
    		printf("%d\t", &(arr[i/100][i%100]));
            /*从第1个元素往后遍历到第10000个,输出元素值*/
            printf("%d\t", arr[0][i]);	
    		printf("%d\n", arr[i / 100][i % 100]);
    		i++;
    	}
    

C99规范可以使用变长(动态)数组

声明变长数组时用变量表示数组元素个数,变长数组需要在定义后才能初始化

int n;
scanf("%d",&n);
int arr[n];
for(int i=0;i<n;i++){
    arr[i] = 0;
}

sizeof关键字

利用sizeof关键字获取变量或者数据类型对应的内存空间大小:

sizeof( 变量名 / 数据类型名 / 表达式 );

int a = 0;
printf("%d,%d,%d",sizeof(a),sizeof(int),sizeof(&a))

使用sizeof函数可以求数组元素个数:sizeof(数组名)/sizeof(数组元素)

int arr[] = {0,0,0,0,0,0,0,0,0,0};	//给数组arr初始化10个元素
int arrSize = sizeof(arr);			//arrSize值为40,arr数组占用40字节空间
int aSize = sizeof(arr[0]);			//aSize值为4,arr数组第一个元素占用4字节空间
int length = arrSize/aSize;			//length值为10,求得数组长度为10

注意:以数组名作为函数参数时,会将数组的首元素地址进行传参

指针

  • 指针:存储内存地址的特殊变量,可以通过地址获取存储区数据
  • 地址:存储区中每个字节的编号
    • 地址在32位电脑下为4字节,在64位电脑下为8字节,即:
      • CPU和内存连接了32根地址线用于寻址(32位)
      • CPU和内存连接了64根地址线用于寻址(64位)

指针变量

  • 指针变量:存储地址的变量,只能用来记录地址数据
  • 定义变量将一块存储区的首地址存储起来,这样的变量称为指针变量
  • 指针变量的作用就是用来直接操作存储区的数据
/*定义指针*/
int* p;
int *p;
int *p1, *p2;
int* p1, p2;

/*给指针赋值*/
int a = 100;
p = &a;			
  • 定义指针变量时必须在变量名称前带上*
  • 在指针前面使用*表示解引用操作,即获取该指针指向的内存地址中存储的数据。
  • 在变量前面使用&表示取地址操作,即获取该变量的内存地址。
  • 没有记录有效地址的地址分为两类:
    • 空指针:固定记录空地址(用NULL表示,数值就是数字0)
    • 野指针:没有记录有效地址的指针都叫做野指针
      • 程序中禁止出现野指针
      • C语言必须对指针进行初始化,否则相当于野指针
  • 禁止对空指针解引用,会导致程序崩溃

指针计算

  • 指针支持加减整数、关系比较和相减运算
  • 指针计算的结果由指针类型决定
  • 地址数据加减整数n相当于加减n*数据类型大小
int *p = 0x1000;
p++;	//p=0x1004

char *ch = 0x2000;
ch++;	//ch=0x2001

short *s = 0x3000;
s++;	//s=0x3002

指针和数组

  • 如果一个指针记录了数组里第一个存储区的地址,就可以通过这个指针访问数组的所有元素,可以认为指针指向了整个数组
    • 支持在指针后使用[]下标来表示数组的存储区
    • 数组里第一个存储区的地址加上下标的值可以获取下标对应存储区的地址
    • 数组名本身就是一个指针,代表数组的首个元素地址
    • arr[i]等价于*(arr+i),先计算arr+i得到地址,再解引用获取存储的数据
  • 大部分情况下可以使用指针替代数组名,但是计算数组长度时不可以使用指针来替换数组名
    • 数组名相当于常量不可被修改,例如arr++;操作会报错
  • 如果需要跨函数使用存储区必须通过指针来实现
    • 将指针作为形参时,可以让被调用函数修改指针指向存储区的数据
  • 指针可以作为返回值,返回的是地址
    • 不可以将局部变量的地址返回,因为局部变量释放后地址不是有效地址,指向该地址的指针变成野指针

常量指针和指针常量

使用const关键字将指针常量化

  • 常量指针:不可以通过这种指针对它指向的存储区赋值,但是可以对常量指针本身赋值

    /*const修饰的是p中存储的地址,所以p中地址指向的存储区就不能被赋值*/
    const int* p = &val;
    
  • 指针常量 - 不可以对指针本身赋值,但是可以对它捆绑的存储区赋值

    /*const修饰的是指针p本身,p本身存储的地址不能修改,但是存储地址指向的存储区可以被赋值*/
    int* const p = &val;
    
  • 常量指针常量

    /*const同时修饰了指针和指向的存储区,两者都不能修改*/
    const int* const p = &val;
    

泛型指针(void * )

  • 定义指针变量时可以使用void作为类型名称,这种指针叫做无类型指针,它可以和任意类型的存储区捆绑
     int a = 100;
     void * p = &a;
    
  • 无法通过这种指针知道它所捆绑的存储区类型
    • 不能在void *指针前直接使用*运算符;直接*p会发生错误
    • 无类型指针必须首先强制类型转换成有类型指针才能使用
#include <iostream>
#include<stdio.h>

int main() {
	int a = 0x12345678;
	void* p = &a;
	printf("%#x\n", &a);
	printf("%#x\n", *(char*)p);
	
	printf("%#x\n", *(int*)p);

	printf("%#x\n", *(short*)p + 1);
	printf("%#x\n", (short*)p +1 );
	printf("%#x\n", (short*)(p+1));
	printf("%#x\n", *(short*)(p+1));
	return 0;
}

二级指针

  • 用来记录普通类型存储区地址的指针叫做一级指针
  • 二级指针用来记录一级指针的地址
int a = 10;		//int类型变量a
int* pa = a;	//指向int类型变量的指针pa
int** ppa = &pa;//存储指向int类型变量指针的二级指针ppa
printf("%x\n", &ppa);	//ppa的地址
printf("%x\n", ppa);	//pa的地址	ppa = &pa
printf("%x\n", *ppa);	//a的地址	*ppa = pa = &a
printf("%x\n", **ppa);	//a的值	**ppa = *pa = a

将指针数组的第一个指针记录到二级指针,就可以用二级指针来代表整个指针数组

char* p1 = "hello";
char* p2 = "world";
char* arr[] = {p1 , p2};
/*arr价于arr1*/
char* arr1[] = {"hello","world"};

/*数组指针*/
int str[2][4] = { 1,2,3,4,5,6};
int (* p)[4] = str;
/*int (* p)[] = str会报错,[]里面数字要和str数组的一致
char (*)[2]" 类型的值不能用于初始化 "char (*)[]" 类型的实体
*/

/*main函数参数的指针数组*/
int main(int argc, char* argv[]){...}
/*等价于*/
int main(int argc, char** argv){...}
类型 定义 本质
指针数组 int * p[] = arr; p是一个数组,数组元素是指针
数组指针 int (*p)[] = arr; p是一个指针,指针存储数组地址

函数指针

  • C语言里函数也有地址
  • 函数名称可以用来表示函数地址
  • 函数指针用来记录函数的地址
    • 函数指针也需要先声明才能使用
    • 函数指针的声明需要根据函数声明变化得到

函数指针也分类型,不同类型的函数指针适合与不同类型的函数捆绑

int add(int x,int y){
    return x + y;
}
/*函数名add就是函数的首地址*/
int (*pfunc)(int,int) = add;//定义函数指针pfunc
int ret = pfunc(100,200); 	//等价于 int ret = add(100,200);
  • 可以通过函数指针调用函数
    • 函数指针可以作为形式参数使用
  • 会作为实际参数使用的函数叫做回调函数
    • 信号和槽机制使用的是回调函数
#include<stdio.h>
typedef int (*pfunc_t)(int, int);
/*定义加法函数*/
int cadd(int x, int y) {
	return x + y;
}
/*定义减法函数*/
int csub(int x, int y) {
	return x - y;
}
/*封装函数,通过函数指针调用对应的函数实现*/
int cpack(int x, int y, pfunc_t p) {
	return p(x, y);
}

int main(int argc, char* argv[]) {
	pfunc_t add = cadd;
	pfunc_t sub = csub;
	printf("%d",cpack(10, 5, add));
    printf("%d",cpack(10, 5, sub));
	return 0;
}

字符串

字符串用来记录文字信息

字符串初始化

  • 字符串由一组连续的字符构成,使用" "引起来,字符的末尾必须用'\0'结束
  • 字符串存储在数据类型为char的数组中
  • '\0'字符表示字符串的结束,'\0'字符对应的ASCII码值为0
char c[] = {"happy"};
char c[] = "happy";
char c[] = {'h','a','p','p','y','\0'}; // 以上三种方式是等价的。
/*如果初始化一个空串,char c[10] = {0};与memset(c,0,sizeof(c))的效果一致。*/
/*赋值:
如果同为char类型,则可以采用strcpy函数,不同类型则采用memcpy函数。*/
char c[20];
char str[] = "i am happy!";
strcpy(c,str,strlen(str)); // 将str的值赋给c

字符串常量

  • "abcd"等价于"abcd\0",属于字符串常量,编译器会自动给字符串常量末尾添加'\0'字符
  • printf函数使用%s占位符依次输出字符串中的字符,直到读到'\0'为止
  • 如果char数组中'\0'后面还有其他字符,printf函数使用%s占位符时不会输出后续字符
  • 编译器会把字符串变量当作第一个字符所在存储区的地址

"s"'s'的区别

"s"属于字符串,存储结构为{'s','\0'},占用2个字节
's'属于单个字符,占用1个字节

字符串常量是否可修改?编译器对字符串常量的优化

  • ANSI C中规定:修改字符串常量的结果是未定义的。
  • ANSI C并没有规定编译器的实现者对字符串的处理,例如:
    • 有些编译器可修改字符串常量,有些编译器则不可修改字符串常量。
    • 有些编译器把多个相同的字符串常量看成一个(这种优化可能出现在字符串常量中,节省空间),有些则不进行此优化。
      • 如果进行优化,则可能导致修改一个字符串常量导致另外的字符串常量也发生变化,结果不可知。
  • 所以尽量不要去修改字符串常量!

字符串常量地址是否相同?

  • tc2.0,同文件字符串常量地址不同。
  • Vs2013,字符串常量地址同文件和不同文件都相同。
  • Dev c++、QT同文件相同,不同文件不同。

字符串表示形式

字符数组

  • 数组名不可以进行修改操作,数组存储区内容可以修改
  • 字符数组只能在定义时可以直接初始化赋值,定义后只能单独对数组各个元素进行赋值
/*字符数组初始化*/
char str1[10] = "abcd";
char str2[] = "abcd";
//char str[];  //error
char str3[10];			//定义字符数组str,长度为10
strcpy(str3,str1);		//使用strcpy函数将字符串str1拷贝给str3。需要include<string.h>
for(int i=0;i<10;i++){
    str[3] = '0';		//使用循环将str字符数组的元素全部修改为'0'
}

注意str = "hello";是错误的操作,"hello"是一个字符串常量,进行赋值操作相当于把字符串常量的地址赋值给字符数组,而数组名不可以进行修改操作的,所以这个操作会报错

字符指针

//定义一个字符指针
char* str = "abcd";
/*由于"abcd"是一个常量,类型为const char *。所以str是一个常量指针,不能修改解引用的值
vs中目前已经不支持将const char*赋值给char*
*/

char str1[] = "abcd";
char* str = str1;
str++;
printf("%c", *str);	//输出结果为b
str++;
printf("%c", *str);	//输出结果为c
str++;
printf("%c", *str);	//输出结果为d
str++;
printf("%c", *str);	//输出结果为?,随机字符

字符串输入输出

  • 输出

    printf("%s",&str);
    puts(&str);	
    
  • 输入

    scanf("%s",&str);
    gets(&str);
    
    • 注意:visual studio中使用scanf函数报错时解决方法:
      • 添加:#define _CRT_SECURE_NO_WARNINGS

字符串操作函数

  • 不可以使用运算符操作字符串
  • C语言里提供了一组标准函数库,来实现对字符串的各种操作。使用这些函数需要包含string.h文件

strlen

计算字符串长度,即字符串中字符的个数

size_t strlen(const char *s);
/*参数为字符串,返回值时size_t类型的整数,不包含最后的空字符*/

strcat

用于连接两个字符串

char* strcat(char* dest,const char*src);
/*将第2个参数的字符串连接到第1个参数字符串的末尾
返回值时修改后的目标字符串dest的指针*/

strcpy

用于将一个字符串复制到另一个字符串中

char* strcpy(char* dest,const char* src);
/*将源字符串src的内容复制到目标字符串dest中
返回值时指向目标字符串dest的指针*/

strcmp

用于比较两个字符串的大小关系

int strcmp(const char*str1, const char* str2);
/*比较字符串str1和str2的大小关系
字符串相等返回值为0;
str1大于str2返回大于0的整数
str1小于str2返回小于0的整数
*/

字符串比较原理

字符串的比较是通过组成串的字符直接的编码(ASCII码)来进行的

  • s="an" , t="be"时,s<t。因为首字母的ASCII码值a<b

  • s="happen" , t="happy"时,s<t。因为第五个字符ASCII码的值e<y

  • s="hap" , t="happy" 时,s<t。因为happy比hap多了两个字符

字符指针数组

指针数组里包含一组同类型的指针存储区

字符指针数组包含多个字符指针,每个字符指针可以代表一个字符串

/*第一种表示方式*/
char* p1 = "abc";
char* p2 = "qwe";
char* p[2] = {p1,p2};

/*第二种表示方式*/
char* p[2] = {"abc","qwe"};

命令行参数:main函数参数

  • main函数的第二个参数char* argv[]就是一个字符指针数组
    • argv包含多个字符指针,每个字符指针都是一个字符串
  • 这些字符串的内容都来自于执行程序时的命令
/*
执行命令./a.out test 123456来执行程序
argc表示argv的指针个数,此时为3
argv[0]为程序名,即"./a.out"
argv[1]为命令的第1个参数,即"test"
argv[2]为命令的第2个参数,即"123456"
*/

int main(int argc,char* argv[]){
    //argc	命令行参数的个数
    //char* argv[]	命令行参数内容
    for(int i=0;i<argc;i++){
        printf("%s\n",argv[i]);
    }
    return 0;
}

atoi

atoi是C语言中字符串处理函数之一,用于将一个字符串转换成对应的整数

int atoi(const char* str);
int a = atoi("100");	//a = 100;
int b = atoi("hello");	//a = 0; error返回0
posted @ 2024-12-06 13:02  -O-n-e-  阅读(36)  评论(0)    收藏  举报