数组:一组具有相同数据类型的数据的集合。
1) 一维数组
2) 二维数组
3) 字符数组
1、一维数组
定义格式:
类型说明符 数组名[整数表达式];
“类型说明符”:指定数组元素的类型,任意C语言合法的类型都可以。
“数组名”:遵循C语言标识符命名规则,是一个常量,其值为首元素的地址。
“整数表达式”(整型常量、符号常量、常量表达式):指定数组元素的个数。现在也可以是变量、变量表达式,并且变量、变量表达式必须有明确的值。如果是变量、变量表达式则数组不能初始化。eg:
int a[10]; //定义了一个有10个元素的数组 a,每一个元素都是 int 型。一维数组在内存中存放:
在连续的地址空间,从低地址到高地址依次存放数组中每一个元素。一维数组元素的引用:(只能引用数组元素而不能一次整体调用整个数组全部元素的值)
数组名[下标]
“下标”:既可以是整型常量、符号常量、常量表达式也可以是变量、变量表达式,但必须是>=0的整数。
在C语言中,下标是从0开始的。最大值是 数组大小-1
a[0]、a[1]、...a[n-1]
引用数组元素 a[i] 跟普通变量一样,即可以作为左值也可以作为右值,还可以取地址。一维数组的初始化:
数组的初始化用 { } ,并且必须在定义时直接初始化。
(1)全部初始化
int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0};
(2)可以对部分元素初始化,后面的元素就自动初始化为0
int a[10] = {1, 2, 3, 4, 5};
(3)如果对全部数组元素赋初值,那么可以不指定数组长度(元素个数)
int a[] = {1, 2, 3, 4, 5, 6};eg:
int a[100] = {0}; // 一般用于数组元素清0
#include
int main(int argc, char *argv[])
{
int s[12] = {1, 2, 3, 4, 4, 3, 2, 1, 1, 1, 2, 3}, c[5] = {0}, i;
for(i = 0; i < 12; i++)
c[s[i]]++;
for(i = 1; i < 5; i++)
printf("%d ", c[i]); //4 3 3 2
printf("\n");
return 0;
}
2、二维数组
数组元素可以出现在表达式中,也可以被赋值。 例如: b[1][2]=a[2][3] / 2
int a[4]; //定义了一个数组a,有4个元素,每个元素都是int类型
再定义一个数组 b typeof(a) <==> int [4]
typeof(a) b[3]; // 定义了一个数组b,有3个元素,每个元素都是 int[4]类型
==> int[4] b[3];
==> int b[3][4];
由推导过程可以得出:二维数组实际上就是一个一维数组,只不过该一维数组的元素又是
一个一维数组罢了。
我们再实际应用中,通过把 int b[3][4] 看成一个3行4列的矩阵。二维数组定义:
类型说明符 数组名[整数表达式][整数表达式];
多少行 每行多少列二维数组在内存中的存放:
按行存放,即先顺序存放第一行的元素,再放第二行的元素...二维数组的引用:
数组名[第几行][第几列]二维数组的初始化:用{}实现初始化,且在定义时直接初始化。
(1)分行给二维数组赋初值
int a[3][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}};
(2)将所有数值写在一个花括号里,按数组的排序顺序对元素依次赋初值
int a[3][4] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
(3)只对部分元素赋初值,其余的元素自动置0
int a[3][4] = {0}; 等价于 int a[3][4] ={ };
int a[3][4] = {1, 2, 3};
int a[3][4] = {{1, 2, 3}, {5, 6}, {9, 10, 11, 12}}; (4)如果对全部元素都赋初值,则定义数组时可以对第一维的长度省略。
int a[ ][4] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
3、字符数组(一次整体调用整个数组全部元素的值)及字符串操作函数
1)C语言中没有字符串类型,也没有字符串变量,字符串是存放在字符数组中的 2)用"%s"格式符输出字符串时,printf函数中的输出项是字符数组名,而不是数组元素名 例:printf("%s", c); 3) 如果初值个数小于数组长度,则只将这些字符赋给数组中前面那些元素,其余的元素自动定为空字符('\0')
4) 在遇到字符 '\0' 时,表示字符串结束,把它前面的字符组成一个字符串 5)中文也是字符
字符数组,就是数组元素是 char(字符型)的数组,它可以一维的,也可以是二维的。
char c1[5] = {'c', 'h', 'i', 'n', 'a'};
char c2[6] = {"china"} 等价于 char c2[6]= "china";
两者有什么区别?
char c2[] = "china";
<==> char c2[] = {'c', 'h', 'i', 'n', 'a', '\0'}; // 多一个'\0'字符串在保存时会在末尾加一个终止符 '\0','\0'的ASCII为0,终止符的作用就是用
来标识字符串的结束。
我们在进行操作字符串都是带入字符串的首地址,正因为每个字符串都会有一个终止符,
所有系统才会知道字符串的具体范围。注意:
#include
#include int main(int argc, char *argv[]) { char str[3][3] = { {'1', '2', '3'}, {'1', '2', '3'}, {'1', '2', '3'}, }; printf("%s\n", str[1]); //123123 return 0; } ⭐数组名代表一个数组的首地址(首个元素的地址)
正确:c h i n a #include
int main(int argc, char *argv[]) { char c[] = "china"; char *p = c; while (*p) //while (*p != '\0') printf("%c ", *p++); return 0; } 错误:p越界但不指向空 #include int main(int argc, char *argv[]) { char c[] = "china"; char *p = c; while (p) printf("%c\n", *p++); return 0; }
⭐字符串操作函数:
(1)用gets和puts对一个字符串进行输入输出 gets[字符数组名]; puts[字符数组名];
eg:
char ch[10];
gets(ch); //在键盘上输入一个字符串,保存到ch指向的内存空间中
puts(ch); //把ch指向的空间中保存的字符串打印出来
注意:
a) gets有巨大的Bug,gets这个函数不会考虑保存这个字符串的数组的空间大小,
如果超过了分配范围还会继续写数据,可能会改变别的变量的数据.例:
char c[3];
char b[3] = {'a','b','c'};
gets(c); //sscc
puts(c); //sscc
puts(b); //c
b) gets函数不会去获取 '\n',puts在输出时将字符串结束标志'\0'转换成'\n',即自动换行
c) gets获取字符串时,可以获取空格、tab等空白字符,回车结束输入.
而 scanf 是遇到空格、tab、回车等空白字符就结束输入
终端指令:man gets //查找man手册中 gets 函数的说明
NAME
gets - get a string from standard input (DEPRECATED)SYNOPSIS
#includechar *gets(char *s);
(2) strcpy/strncpy----字符串拷贝函数 strcpy(字符数组1,字符串或字符数组2) 不能用赋值语句将一个字符串常量或字符数组直接给一个字符数组,字符数组名是一个地址常量,不能改变其值。
NAME
strcpy, strncpy - copy a stringSYNOPSIS
#includestrcpy:字符串拷贝函数.用来把src指向的字符串,拷贝到dest 指向的内存中去.
char *strcpy(char *dest, const char *src);
@dest:指向用来保存字符串的内存空间
@src:指向要拷贝的字符串数据
返回值:
如果成功,返回拷贝后字符串的首地址, 即 dest
如果失败,返回 NULL
注意:strcpy函数也有一个Bug,不关心dest指向的空间是否够用,造成内存溢出.
strncpy正式为了修复strcpy的bug的,它的功能和strcpy相似,
只不过他顶多拷贝n个字节.
char *strncpy(char *dest, const char *src, size_t n);
@dest:指向用来保存字符串的内存空间
@src:指向要拷贝的字符串数据
@n:最多拷贝n个字节
返回值:
如果成功,返回拷贝后字符串的首地址, 即 dest
如果失败,返回 NULL注意:strncpy到底拷贝了多少个字节呢?
1) 遇到 '\0',拷贝结束('\0'有拷贝的)
2) 没遇到'\0',但是已经拷了n个字节了,结束拷贝(没有拷贝'\0')如果在复制前未对字符数组1初始化或赋值,则字符数组1各字节中的内容是无法预知的,复制时将字符数组2中的字符串和其后的'\0'一起复制到字符数组1中,取代字符数组1中的前面几个字符,最后几个字符并不一定是'\0',而是字符数组1中的最后几个字节的内容 例:字符数组内存从小到大连续存储(节约内存) char ch1[ ] = {'a', 'b', 'c'}; char ch2[ ] = {'b', 'c', 'd'}; //存储 a b c b c d
从小到大排序 a b c a a a b c
char ch1[100] = {'a', 'b', 'c'}; char ch1[100] = {'a', 'b', 'c'};
char ch2[] = {'a', 'b', 'c', 'a', 'a'}; char ch2[10] = {'a', 'b', 'c', 'a', 'a'};
puts(ch2); //abcaaabc puts(ch2); //abcaa //原因:有'\0'
strcpy(ch1, ch2); strcpy(ch1, ch2);
printf("%s", ch1); //abcaaabc printf("%s", ch1); //abcaa
注意事项:
1. 遇到 '\0'拷贝结束('\0'有拷贝的)
2. arr2数组大小如果小于arr1数组大小:必须以 '\0'结束
#include
#include //错误 数组arr1越界 int main(int argc, char *argv[]) { //字符数组内存从小到大连续存储(节约内存) abcd**********\0 char arr1[] = "**********"; //有'\0'结尾 char arr2[] = {'a', 'b', 'c', 'd'}; //没有'\0'结尾 printf("%s\n", strcpy(arr1, arr2)); return 0; } //正确 int main(int argc, char *argv[]) { //字符数组内存从小到大连续存储(节约内存) abcd**********\0 char arr1[14] = "**********"; //10个‘*’ 有'\0'结尾 char arr2[] = {'a', 'b', 'c', 'd'}; //没有'\0'结尾 printf("%s\n", strcpy(arr1, arr2)); //输出:abcd********** return 0; } //正确 int main(int argc, char *argv[]) { //字符数组内存从小到大连续存储(节约内存) abcd\0********** char arr1[10] = "**********"; //有'\0'结尾 char arr2[] = { 'a', 'b', 'c', 'd', '\0'}; //有'\0'结尾 printf("%s\n", strcpy(arr1, arr2)); //输出abcd return 0; } //正确 int main(int argc, char *argv[]) { char arr1[4] = "****"; //有'\0'结尾 char arr2[4] = {'a', 'b', 'c', 'd'}; //没有'\0'结尾 printf("%s\n", strcpy(arr1, arr2)); //输出:abcd return 0; } 这里的代码会出错,'\0'是停止拷贝的终止条件,arr2字符数组所在的内存空间后面存储的内容并不知道,不遇到 '\0' 拷贝就不会停止,这就会导致越界访问,程序就会出现问题。
3.目标空间必须足够大,以确保能放源字符串
#include
#include int main(int argc, char *argv[]) { //字符数组内存从小到大连续存储(节约内存) char arr1[5] = "*****"; char arr2[] = "hello world"; printf("%s\n", strcpy(arr1, arr2)); return 0; } 这里虽然拷贝成功并将结果输出了,但程序却崩溃了。目标空间太小,不足以放置拷贝的源字符串,会造成溢出的情况。
4.目标空间必须可变
#include
#include int main(int argc, char *argv[]) { char *str1 = "hello world"; char str2[10] = "*********"; printf("%s\n", strcpy(str1, str2)); return 0; } 这里的程序也出现了错误。str1指向的是常量字符串,是不可以被修改掉的,目标空间必须是可以被修改的,因为要将拷贝的字符串放在目标空间中。而源字符串可以是能够修改的、也可以是不能修改的,因为strcpy函数的第二个参数已经用const关键字修饰了,保证了拷贝过程中不会被修改。
5. arr1如果是指针,必须要有指向,且指向的内容必须可改变
#include
#include #include //正确 int main(int argc, char *argv[]) { char str[10] = "aa"; char *arr1 = str; //字符数组内存从小到大连续存储(节约内存) arr1以str为依据 char arr2[] = {'a', 'b', 'c', 'd'}; //没有'\0'结尾 printf("%s\n", strcpy(arr1, arr2)); //输出:abcdaa return 0; } //错误 int main(int argc, char *argv[]) { char *arr1; //或 char *arr1 = NULL; char arr2[] = {'a', 'b', 'c', 'd'}; //没有'\0'结尾 printf("%s\n", strcpy(arr1, arr2)); return 0; } //正确 int main(int argc, char *argv[]) { char *arr1 = malloc(sizeof(4)); char arr2[] = {'a', 'b', 'c', 'd'}; //没有'\0'结尾 printf("%s\n", strcpy(arr1, arr2)); //输出:abcd return 0; } //正确 int main(int argc, char *argv[]) { const char *s = "hello"; //s指向6个字节大小的字符串 char *p = malloc(strlen(s)); //分配5个字节 printf("%s\n", strcpy(p, s)); //输出hello printf("%ld\n", sizeof("hello")); return 0; }
3) strlen:用来求出s指向的字符串的长度,并返回字符串长度(是指字符串里面包含的字符个数(不算后面'\0'))遇到'\0'才结束 %ld
strlen(字符数组) 字符'\0'的ASCII值是0, 字符'0'的ASCII值是48
NAME
strlen - calculate the length of a stringSYNOPSIS
#include
size_t strlen(const char *s);char ch1[5] = {'a', 'b', 'c'}; char ch1[] = {'a', 'b', 'c'};
char ch2[3] = {'a', 'b', 'c'}; char ch2[3] = {'a', 'b', 'c'};
printf("%ld", strlen(ch1)); // 3 printf("%ld", strlen(ch1)); // 6char ch1[ ] = {'a', 'b', 'c'};
strlen(ch1); //由于ch数组没有字符串结束符,长度不能确定
char ch2[10] = {'a', 'b', 'c'};
strlen(ch2); //3
4) strcat/strncat ---------字符串连接函数 strcat(字符数组1,字符数组2) NAME
strcat, strncat - concatenate two stringsSYNOPSIS
#includechar *strcat(char *dest, const char *src); strcat : 用来把src指向的字符串拷贝到dest指向的字符串的末尾(覆盖掉'\0')
@dest:指向用来保存字符串的内存空间,注意空间大小要足够
@src:指向要拷贝的字符串数据
返回值:
如果成功,返回拷贝后字符串的首地址, 即 dest
如果失败,返回 NULL注意:strcat函数也有一个Bug,不关心dest指向的空间是否够用,造成内存溢出.
strncat正是为了修复strcat的bug的,它的功能和strcat相似,char *strncat(char *dest, const char *src, size_t n);
@dest:指向用来保存字符串的内存空间
@src:指向要拷贝的字符串数据
@n:最多拷贝n个字节
返回值:
如果成功,返回拷贝后字符串的首地址, 即 dest
如果失败,返回 NULL例:
char ch1[] = "abcd";
char ch2[] = "sscc";
printf("%s\n", strcat(ch1,ch2)); // abcdsscc
5) strcmp/strncmp --------- 字符串比较函数 strcmp(字符串1,字符串2)
那么字符串怎么比较呢?
都是从第一个字符开始比较,一个字符接着一个字符比较ASCII码值的大小
if c1 > c2, 则返回 整数
if c1 < c2, 则返回 负数
if c1 == c2, 则继续比较下一个,如果全部相等才返回 01)在英文字典中位置在后面的ASCII为“大”
2)小写字母比大写字母“大”
char ch1[4] = {'a', 'b', 'c'};
char ch2[4] = {'a', 'b', 'c', 'a'};
printf("%d", strcmp(ch1, ch2)); // -97 ASCII值比较
char ch1[] = {'a', 'b', 'c'};
char ch2[] = {'a', 'b', 'c', 'a'};
printf("%d", strcmp(ch1, ch2)); // 98
NAME
strcmp, strncmp - compare two stringsSYNOPSIS
#includeint strcmp(const char *s1, const char *s2);
注意:strcmp 无bug,因为没有进行写操作
strncmp:功能和strcmp相似,只不过比较前n个字符
int strncmp(const char *s1, const char *s2, size_t n);
6) #include
strlwr函数-----转换为小写的函数 strlwr(字符串) 将字符串中大写字母换成小写字母
strupr函数-----转换为大写的函数 strupr(字符串) 将字符串中大写字母换成小写字母
7)memset-------初始化函数
memset是一个初始化函数,作用是将某一块内存中的全部设置为指定的值。
#include
void *memset(void *s, int c, size_t n); s指向要填充的内存块。 c是要被设置的值。 n是要被设置该值的字符数。 返回类型是一个指向存储区s的指针。 需要说明的几个地方
一、不能任意赋值
memset函数是按照字节对内存块进行初始化,所以不能用它将int数组去初始化为0和-1之外的其他值(除非该值高字节和低字节相同)。
其实c的实际范围应该在0~255,因为memset函数只能取c的后八位给所输入范围的每个字节。也就是说无论c多大只有后八位二进制是有效的。=================================================================================================
对于int a[4];
memset(a, -1, sizeof(a)) 与 memset(a, 511, sizeof(a)) 所赋值的结果一样都为-1:
因为 -1 的二进制码(补码)为(11111111 11111111 11111111 11111111);511 的二进制码为(00000000 00000000 00000001 11111111);
后八位均为(11111111),所以数组中的每个字节都被赋值为(11111111)。
注意int占四个字节,例如a[0]的四个字节都被赋值为(11111111),那么a[0](11111111 11111111 11111111 11111111),即a[0] = -1。二、注意所要赋值的数组的元素类型
#include
#include //例一:对char类型的数组a初始化,设置元素全为’1’ int main(int argc, char *argv[]) { char a[4]; memset(a, '1', 4); for(int i = 0; i < 4; i++) printf("%c ", a[i]); //输出:1 1 1 1 putchar('\n'); return 0; } //例二:对int类型的数组a初始化,设置元素值全为1 int main(int argc, char *argv[]) { int a[4]; memset(a, 1, sizeof(a)); //或者memset(a, 1, 16) 0000 0001 0000 0001 0000 0001 0000 0001 for(int i = 0; i < 4; i++) printf("%d ", a[i]); //输出:16843009 16843009 16843009 16843009 putchar('\n'); return 0; } int main(int argc, char *argv[]) { int a[4]; memset(a, 1, 4); //0000 0001 0000 0001 0000 0001 0000 0001 for(int i = 0; i < 4; i++) printf("%d ", a[i]); //输出:16843009 0 0 0 putchar('\n'); return 0; } 例一程序中,数组a是字符型的,字符型占据的内存大小就是1Byte,而memset函数也是以字节为单位进行赋值的,所以输出正确。
例二程序中,数组a是整型的,整型占据的内存大小为4Byte,而memset函数还是按照字节为单位进行赋值,将1(0000 0001)赋给每一个字节。那么对于a[0]来说,其值为(00000001 00000001 00000001 00000001),即十进制的16843009。注意:
#include
#include /* 错误 当数组作为参数传递时,其传递的实际上是一个指针,这个指针指向数组的首地址, 如果用sizeof(a)函数得到的只是指针的长度,而不是数组的长度。 */ void fun1(int a[]) { memset(a, -1, sizeof(a)); //sizeof(a)是指针长度 } int main(int argc, char *argv[]) { int a[6]; fun1(a); for(int i = 0; i < 6; i++) { printf("%d ", a[i]); } printf("\n"); return 0; } //正确 void fun1(int a[], int len) { memset(a, -1, len); } int main(int argc, char *argv[]) { int a[6]; int len = sizeof(a); fun1(a, len); for(int i = 0; i < 6; i++) { printf("%d ", a[i]); } printf("\n"); return 0; }
8)strdup-----字符串拷贝
一.函数分析
1.函数原型:
#include
char *strdup(const char *s); 2.功能:
strdup()函数主要是拷贝字符串s的一个副本,由函数返回值返回,这个副本有自己的内存空间,和s没有关联。strdup函数复制一个字符串,使用完后,要使用delete函数删除在函数中动态申请的内存,strdup函数的参数不能为NULL,一旦为NULL,就会报段错误,因为该函数包括了strlen函数,而该函数参数不能是NULL。
3.strdup函数实现
char * __strdup(const char *s) { size_t len = strlen(s) +1; void *new = malloc(len); if (new == NULL) { return NULL; } return (char *)memcpy(new, s, len); }
4.函数实例
#include
#include int main(void) { char *src = "This is the jibo"; char *dest; dest = strdup(src); printf(“the dest %s\n”, dest); free(dest); return 0; } 二. strdup与strcpy函数的区别
1.共同点:
两个函数都实现了字符串的拷贝。
2.不同点:
1) strcpy函数:把从src地址开始且含有结束符的字符串复制到以dest开始的地址空间
2)由strcpy和strdup函数实现可知
strdup函数返回指向被复制的字符串的指针,所需空间由malloc()函数分配且可以由free()函数释放。stdrup可以直接把要复制的内容复制给没有初始化的指针,因为它会自动分配空间给目的指针。
strcpy的目的指针一定是已经分配好的内存指针。
3)strdup的缺点:
使用strdup函数的时候,往往会忘记内存的释放,因为申请内存空间的动作是在strdup函数内实现,如果对该函数的实现不是很了解,则会忘记使用free函数来释放空间。