目录
一、一维数组
1.一维数组的定义
- 数组是一组相同类型元素的集合,下标从0开始。
- 数组创建[]中要给一个常量才可以,不能使用变量。可以直接用常量,或者使用宏定义。
- 数组在内存中开辟是线性连续且递增的,即数组在内存中是连续存放的。
- 只能逐个引用数组元素,不能一次引用整个数组。
- 数组元素表示形式: 数组名[下标] ,下标可以是常量或整型表达式。
//代码1正确 int arr1[10]; char arr2[10]; float arr3[1]; double arr4[20]; //代码2正确 #define X 3 //用宏定义的方式 int arr5[X]; //代码3错误 int count = 10; //错误使用 int arr6[count];
2.一维数组的初始化
1.数组大小和数值个数一致,初始化所有成员变量。
int arr1[5] = {1,2,3,4,5};
2.数组大小大于初始数,后面元素都为0。
int arr2[6] = {1,2,3};
3.不指定数组大小,有4个成员。
int arr3[] = {1,2,3,4};
4.所有成员都设置为0。
int arr4[5]={0};
5.不指定字符数组大小。
char arr5[] = {'a','b','c'};
6.字符数组存储字符串。(最后一项的差别)
char arr6[] = "abcdef"; //存储的是:'a' 'b' 'c' 'd' 'e' 'f' '\0' char arr6[6] = "abcdef"; //存储的是:'a' 'b' 'c' 'd' 'e' 'f' //这样初始化是有问题的 //因为无法正常读取字符串的结束标志('\0'),导致字符串的长度和内容不能得知!!
7. 字符数组大小大于字符串中的字符数。
char arr7[6] = "wy"; //存储的是:'w' 'y' '\0' '\0' '\0' '\0'
小结
- 数组只能够整体初始化,不能被整体赋值。即使用循环从第一个逐个遍历赋值。
- 初始化时,数组的维度或元素个数可忽略 ,编译器会根据花括号中元素个数初始化数组元素的个数。
- 当花括号中用于初始化值的个数不足数组元素大小时,数组剩下的元素依次用0初始化。
- 字符型数组在计算机内部用的时对应的ascii码值进行存储的。
- 一般用“ ”引起的字符串,不用数组保存时,一般都被直接编译到字符常量区,并且不可被修改。
3.一维数组的地址
1.数组地址和数组首元素地址是两个不同的概念。(以int arr[n]为例)
1.&arr:表示数组 arr 的地址,也就是数组在内存中的起始地址。
- 这个地址的类型是指向一个包含 n 个 int 的数组的指针,即 int (*)[n]。
- 它指向的是一个完整的数组,而不是单个的 int 元素。&arr + 1会跳过整个数组【加上整个数组的总字节数】,如int *p = (int *)(&arr + 1),指针p指向数组的末尾。
- 在函数参数传递中,如果你试图传递一个数组(比如 void foo(int arr[n]);),实际上传递的是数组首元素的地址(即 &arr[0] 或 arr),而不是整个数组的地址(即 &arr)。这是因为数组在函数参数传递时会退化为指向其首元素的指针。
2.arr:为数组首元素的地址,即 &arr[0]。
- 在大多数上下文中arr 会被自动转换为指向其首元素的指针,即 int * 类型。
- 你可以对这个地址进行算术运算,比如 arr + 1 会指向数组的第二个元素。
- 在函数参数传递中,当你传递一个数组时,实际上传递的是这个指针。
3.&arr[0]:表示数组首元素的地址。
- 它的类型也是 int *,指向一个 int 类型的元素。
- 你可以对这个地址进行算术运算,比如 &arr[0] + 1 会指向数组的第二个元素。
- arr 和 &arr[0] 通常是等价的,因为 arr 在大多数上下文中都会被解释为 &arr[0]。
4.总结:
- &arr 是整个数组的起始地址,类型为指向数组的指针。
- 数组名代表数组首元素地址,它是个常量。
- arr 和 &arr[0] 通常是等价的,都表示数组首元素的地址,类型为指向单个元素的指针。在函数参数传递中,数组会退化为指向其首元素的指针。
- 数组首元素地址和数组的地址值相等。
- 数组的大小可以通过计算得到,可以采用sizeof(arr)/sizeof(arr[0])这种方式。
#include <iostream> using namespace std; int main() { int arr[5] = { 1,2,3,4,5 }; int* p1 = (int *)(&arr + 1); //&arr:整个数组的地址 //&arr + 1:指向数组的末尾处 int* p2 = (int*)(arr + 1); //arr等价于&arr[0],类型为int *类型:数组首元素地址 cout << p1[-2] << endl; //p1[-2]是arr[3]的值,即4。 cout << *p2 << endl; //2 cout << arr << endl; //和&arr[0]都输出数组首元素的地址,009DFBB8 cout << *arr << endl; //1【第1个元素值】 cout << arr + 1 << endl; //009DFBBC 后移4字节【跳过1个元素】 cout << *(arr + 1) << endl; //2【第2个元素值】 cout << &arr[0] << endl; //009DFBB8 cout << *(&arr[0]) << endl; //1【第1个元素值】 cout << &arr[0] + 1 << endl; //009DFBBC 后移4字节【跳过1个元素】 cout << *(&arr[0] + 1) << endl; //2【第2个元素值】 cout << &arr << endl; //009DFBB8 cout << *(&arr) << endl; //009DFBB8 cout << &arr + 1 << endl; //009DFBCC 后移4*5=20字节【跳过整个数组】 cout << *(&arr + 1) << endl; //009DFBCC return 0; }
二、二维数组
1.二维数组的定义
(1)数据类型 数组名[ 行数 ][ 列数 ];
(2)数据类型 数组名[ 行数 ][ 列数 ] = { {数据1,数据2} ,{数据3,数据4} };
(3)数据类型 数组名[ 行数 ][ 列数 ] = { 数据1,数据2,数据3,数据4};
(4)数据类型 数组名[ ][ 列数 ] = { 数据1,数据2,数据3,数据4};
- 第(2)种定义方式更直观,可提高代码的可读性;
- 第(3)、(4)种根据二维数组的列数推断数组元素(可省略行数,不可省略列数)。
- 定义二维数组时,若已初始化数据,则可以省略行数。
2.二维数组的数组名
1.计算二维数组所占内存空间
- 二维数组占用内存空间大小:sizeof(arr)
- 二维数组第 i 行占用内存空间大小:sizeof(arr[i])
- 二维数组某个元素占用内存空间大小:sizeof(arr[i][j])
2.计算二维数组的行数与列数
- 二维数组的行数:sizeof(arr) / sizeof(arr[0])
- 二维数组的列数:sizeof(arr[0]) / sizeof(arr[0][0])
3.获取二维数组的首地址
- 二维数组首元素地址:arr[0] 或 &arr[0][0]
- 二维数组第 0 行的地址: arr或arr[0]或arr + 0 【或*(arr + 0)】
- 二维数组第 i 行的地址:arr[i]或arr + i 【或*(arr + i)或&a[0] + i】
- 二维数组名arr:二维数组第0行(首行)的地址,等价于arr[0]或arr + 0;
4.二维数组的其它地址
- 二维数组第 i 行首元素的地址:arr[i]或arr + i或*(arr + i)或&a[0] + i
- 二维数组第 i 行第 j 列元素的地址:&arr[i][j]或*(arr + i) + j
- 通过指针解引用访问或操作某元素:*(*(arr + i) + j)
#include <iostream> using namespace std; int main() { int arr[2][3] = { {1,2,3},{4,5,6} }; //二维数组占用的内存空间 cout << "二维数组大小: " << sizeof(arr) << endl; //24 cout << "二维数组一行大小: " << sizeof(arr[0]) << endl; //12 cout << "二维数组元素大小: " << sizeof(arr[0][0]) << endl; //3 //二维数组的行数与列数 cout << "二维数组行数: " << sizeof(arr) / sizeof(arr[0]) << endl; //2 cout << "二维数组列数: " << sizeof(arr[0]) / sizeof(arr[0][0]) << endl; //3 //地址 cout << "二维数组首行地址:" << (int)arr << endl; //16053988 cout << "二维数组第一行地址:" << (int)arr[0] << endl; //16053988 cout << "二维数组第一个元素地址:" << (int)&arr[0][0] << endl; //16053988 cout << "二维数组第二行地址:" << (int)arr[1] << endl; //16054000 cout << "二维数组第二个元素地址:" << (int)&arr[0][1] << endl; //16053992 return 0; }
3.二维数组的地址
以int a[3][4]为例:
- 第0行第1列元素地址:a[0]+1、*a+1、&a[0][1](互相等价)
- 第1行第2列元素地址:a[1]+2、*(a+1)+2、&a[1][2](互相等价)
- 第 i 行第 j 列元素地址:a[i]+j、*(a+i)+j、&a[i][j](互相等价)
- 第1行第2列元素值:*(a[1]+2)、*(*(a+1)+2)、a[1][2](互相等价)
- 第 i 行第 j 列元素值:*(a[i]+j)、*(*(a+i)+j)、a[i][j](互相等价)
以int a[6][6]为例:
- a,&a[0]:分别为二维数组和首元素a[0]的地址,两者含义相同。
- a[0],*(a+0),*a,&a[0][0]:均为a[0][0]的地址,四者的含义相同。
- a[0]+1、*a+1、&a[0][1]:均为a[0][1]元素的地址,三者含义相同。
- a+1,&a[1]:均为a[1]的地址,两者含义相同。
- a[1],*(a+1),&a[1][0]:均为a[1][0]元素的地址,三者的含义相同。
- a[1]+4,*(a+1)+4,&a[1][4]:均为a[1][4]元素的地址,三者的含义相同。
- *(a[2]+4),*(*(a+1)+4),a[2][3]:均为a[2][4]元素,三者含义相同。
三、字符串
1.字符串与字符数组的区别
1.C语言中没有字符串这种数据类型,可以通过char的数组来替代;
2.字符串一定是一个char的数组,但char的数组未必是字符串;
3.数字0(和字符‘\0’等价)结尾的char数组就是一个字符串,但如果char数组没有以数字0结尾,那么就不是一个字符串,只是普通字符数组,所以字符串是一种特殊的char的数组。
char c1[] = {'a','b','c','d'};//普通字符数组 printf("%s",c1);//乱码,因为没有'\0'结束符,并不是一个字符串 //以'\0'('\0'就等价于数字0)结尾的字符数组就是字符串 char c2[] = {'a','b','c','d','\0'}; printf("%s",c2); //以'\0'(数字0)作为结束符,后面的q,w,e,r不用输出 char c3[] = {'a','b','c','d','\0','q','w','e','r'}; printf("%s",c3);
2.字符串初始化
1.初始方法一:不指定长度,没有0结束符,有多少元素就有多长。
char a1[] = {'b', 'o', 'y'}; printf("%s",a1);//错误,乱码,没有以字符串终止符 '\0' 结尾 char a1[] = {'b', 'o', 'y','\0'};//正确方式
2.初始化方式二:后面没有赋值0,会自动补0,因为0与'\0'等价。
char a2[10] = "boy"; printf("%s",a2); //数组中的a1[0],a1[1],a1[2]分别对应'b', 'o', 'y',其余元素自动定为空字符 //剩下的七个元素将被自动初始化为0(在大多数系统上,对于字符类型,这等同于 '\0')
3.初始化方式三:使用字符串初始化,编译器自动在后面补0。
char a3[] = "asdasd"; char a3[10] = "asdasd"; //a3是一个字符数组,用于存储字符串 "asdasd"。 //编译器会自动在字符串的末尾添加一个空字符'\0'来表示字符串的结束 //所以实际上 a3 数组包含以下字符'a', 's', 'd', 'a', 's', 'd', 和 '\0'
3.字符串函数
1..strlen()——求字符串长度
- 实参传入一个不能更改的字符串数组的首地址,该函数计数以‘\0'作为结束标志,所以要确保待求字符串数组是有结束标志的,否则返回值为随机值。
#include <stdio.h> #include <string.h> int main() { char str[] = { "abcdef" }; printf("%zd\n", strlen(str));//输出6 return 0; }
2.strcpy()——字符串拷贝
- 将source所指的字符串数组拷贝一份到destination中,以’\0‘为结束标志,返回destination的地址。
- destination数组容量必须要够大,能够装下source的内容。
- 会将'\0'拷贝至destination中,且destination必须为可变数组。
#include <stdio.h> #include <string.h> int main() { char str[] = { "abcdef" }; printf("%zd\n", strlen(str));//6 char str1[10] = { 0 }; printf("%s\n", strcpy(str1, str));//abcdef return 0; }
3. strncpy()——拷贝指定数量字符
- 指定将source中num位字符拷贝至destination中。
#include <stdio.h> #include <string.h> int main() { char str[20] = { "abcdef" }; printf("%zd\n", strlen(str));//6 char str1[10] = { 0 }; printf("%s\n", strcpy(str1, str));//abcdef char str3[10] = { "0" }; printf("%s\n", strncpy(str3, str1, 2));//将str1的前两个字符拷贝至str3中 return 0; }
4.strcat()——连接两个字符串
- 将source中的字符串连接到destination后面,返回destination的地址。
- destination必须足够大,且两个字符串必须有'\0'作为结尾,不能自己给自己连接。
#include <stdio.h> #include <string.h> int main() { char str[20] = { "abcdef" }; printf("%zd\n", strlen(str));//6 char str1[10] = { 0 }; printf("%s\n", strcpy(str1, str));//abcdef printf("%s", strcat(str, str1)); //abcdefabcdef return 0; }
5.strncat()——连接指定位数字符至字符串中
#include <stdio.h> #include <string.h> int main() { char str[20] = { "abcdef" }; printf("%zd\n", strlen(str));//6 char str1[10] = { 0 }; printf("%s\n", strcpy(str1, str));//abcdeg printf("%s\n", strcat(str, str1));//abcdefabcdef char str2[] = { "abced" }; printf("%s\n", strncat(str2, str1, 2));//将str1的前两个字符连接在str2后 return 0; }
6. strcmp()——比较两个字符串
- 对两个字符串进行比较,比较方式为:依次对每对字符的ASCII值进行比较(位置一一对应),直到比较出大小。
- str1>str2返回大于0的数;str1<str2返回小于0的数;str1=str2返回0。
#include <stdio.h> #include <string.h> int main() { char str[20] = { "abcdef" }; printf("%zd\n", strlen(str));//6 char str1[10] = { 0 }; printf("%s\n", strcpy(str1, str));//abcdef printf("%s\n", strcat(str, str1));//abcdefabcdef char str2[] = { "abced" }; printf("%d\n", strcmp(str1, str2));//-1 return 0; }
7.strncmp()——比较字符串前n个字符
#include <stdio.h> #include <string.h> int main() { char str[20] = { "abcdef" }; printf("%zd\n", strlen(str))//6; char str1[10] = { 0 }; printf("%s\n", strcpy(str1, str));//abcdef printf("%s\n", strcat(str, str1));//abcdefabcdef char str2[] = { "abced" }; printf("%d\n", strcmp(str1, str2));//-1 char str3[10] = { "0" }; printf("%s\n", strncpy(str3, str2, 2));//ab printf("%s\n", strncat(str3, str2, 2));//abab printf("%d\n", strncmp(str, str2, 3));//0 return 0; }
8.strstr------寻找子字符串
- 在str1中寻找str2,返回str1中出现str2的地址
#include <stdio.h> #include <string.h> int main() { char str[20] = { "abcdef" }; printf("%zd\n", strlen(str))//6; char str1[10] = { 0 }; printf("%s\n", strcpy(str1, str));//abcdef printf("%s\n", strcat(str, str1));//abcdefabcdef char str2[] = { "abced" }; printf("%d\n", strcmp(str1, str2));//-1 char str3[10] = { "0" }; printf("%s\n", strncpy(str3, str2, 2));//ab printf("%s\n", strncat(str3, str2, 2));//abab printf("%d\n", strncmp(str, str2, 3));//0 char str4[] = { "def" }; printf("%s\n", strstr(str, str4));//defabcdef return 0; }
9.strtok()——指定符号以分割字符串
- 若str为非NULL,那么就返回第一个字符的位置,并且记录下来当前位置。
- 若str为NULL时,从之前记录的位置出发,继续寻找下一个分割字符,并返回下一个分割字符的位置,直到找到\0返回空指针。
- 值得一提的是,strtok()函数会修改字符串的内容,所以如果不希望字符串别修改的话需要创建临时拷贝字符串后进行操作。
#include <stdio.h> #include <string.h> int main() { char str[] = { "345680229@qq.com" }; //printf("%s\n",strtok(str, "@.")); //printf("%s\n",strtok(NULL, "@.")); //printf("%s\n", strtok(NULL, "@.")); char* ret = NULL; for (ret = strtok(str, "@."); ret != NULL; ret = strtok(NULL, "@.")) { printf("%s\n", ret); } return 0; //依次输出的是: //345680229 //qq //com }
感谢来自于大佬们的分享,感谢佬: