C语言03- 数组、字符串

C语言03- 数组、字符串

10:数组

数组每个元素的类型相同,因此各个元素在内存中存放的长度也一样,即它们占用的空间是等长的,而数组名,就是这段内存的首地址。

数组是连续存储,所以它支持随机访问.

10.1:一维数组

image
下面关于数组的一些定义,有的是错误的:
int x = 10;
int a[x]; //错误,x是变量而不是常量

const int x1 = 10;
int a1[x1];//正确,x现在是常变量
int a2[10];//正确,10是整数常量

一些常见的一维数组定义:
int a[100];
char a[100];
int *a[10];//每个元素是一个指针
     a[i]:数组元素值
     &a[i]:第i个元素的地址
     &a[0]:首个元素的地址
    a,首地址
    a5+1
    &a[0]+1

sizeof是用来计算类型和数据的长度。strlen是用来计算字符串中非‘\0’的字符个数的。

  1. sizeof(a):数组的长度计算
  2. sizeof(a[0]):数组中一个元素的 长度
  3. n=sizeof(a)/sizeof(a[0]):数组元素个数
  4. #define ARRAYSIZE(a) (sizeof(a)/sizeof(a[0]))

10.2:二维数组

二维数组可以理解为数学中的矩阵的一个类似概念。二维数组定义的一般形式为:

类型说明符 数组名[常量表达式1][常量表达式2]

二维数组的初始化可以为下面的形式:

int a[5][3]={ {80,75,92},{61,65,71},{59,63,70},{85,87,90},{76,77,85} };

对于二维数组的遍历,可以先列后行(即先遍历列,再遍历行),也可以先行后列(即先遍历行,再遍历列)进行。比如,对于一个m行n列的整型二维数组a[m][n]进行遍历:
//先行后列:

for(int i = 0; i < m;i++) {
         for(int j = 0; j < n; j++) {
              printf(“%d/n”, a[i][j]);
         }
}

//输出为:a[0][0] a[0][1] a[0][2] a[0][3]…a[0][9]...

//先列后行:
for(int i = 0; i < n;i++) {
         for(int j = 0; j < m; j++) {
                   printf(“%d/n”, a[j][i]);
         }

}
//输出为:a[0][0] a[1][0] a[2][0] a[3][0] a[4][0]

由于数组是按行的顺序存放的,同一列的数据就有可能存放在不同的页中,那么先列后行的访问将引起更多的缺页中断,降低了遍历的效率。

10.3:数组重要注意事项

int a1[10];
int a2[4][5];
//a1,&a1,a2,&a2都是数组的首地址,值相同,但是类型不同。
//类型为什么不一样呢?
//a1:int * -
//&a1:int (*a1)[10] -包含十个元素的一维数组的指针类型
//a2:int (*a2)[5] -数组指针,指向含有5个元素的数组,代表一行
//&a2:int (*a2)[4][5] -数组指针,指向还有4行5列的一个数组

--------------------------------------------

   int a[3] = {0,1,2};
   
   printf("a:%p\n", a);//a是int *类型;0x7ffee02f693c
   printf("&a:%p\n", &a);//&a是int (*a)[3]类型;0x7ffee02f693c
   printf("&a[0]:%p\n", &a[0]);//首个元素的地址;0x7ffee02f693c
   //打印输出a, &a, &a[0]值一样;如地址0x7ffee02f693c
   
   //sizeof判断数据类型长度符的关键字;其作用就是返回一个对象或者类型所占的内存字节数
   /*
    指针变量的sizeof
    学过数据结构的你应该知道指针是一个很重要的概念,它记录了另一个对象的地址。既然是来存放地址的,那么它当然等于计算机内部地址总线的宽度。所以在32位计算机中,一个指针变量的返回值必定是4(注意结果是以字节为单位),但是,在64位系统中指针变量的sizeof结果为8。
    */
   printf("sizeof(a):%lu\n", sizeof(a));//数组的sizeof值等于数组所占用的内存字节数
   printf("sizeof(&a):%lu\n", sizeof(&a));//
   printf("sizeof(&a[0]):%lu\n", sizeof(&a[0]));//
//    sizeof(a):12
//    sizeof(&a):8 //64位系统
//    sizeof(&a[0]):8 //64位系统
   
   printf("a:%p\n", a + 1);//a是int *类型,0x7ffee2d5c940 加一是其类型加一
   printf("&a:%p\n", &a + 1);//&a是int (*a)[3]类型,0x7ffee2d5c948
   printf("&a[0]:%p\n", &a[0] + 1);//首个元素的地址,0x7ffee2d5c940
   
   ---------------------------
   一般地:
   int a[m1][m2]...[mn]
   a, &a, &a[0]...[0]
   a+1//a的类型int (*a)[m2]...[mn],一行
   &a+1//&a的类型int (*a)[m1]...[mn]
    &a[0]...[0]//代表的第一个元素的类型int *

int a[5] = {1,2,3,4,5};
int *ptr1 = (int *)(&a+1);
int *ptr2 = (int *)((int)a+1);
printf("%x, %x", ptr1[-1], *ptr2) 
//打印0x5(0x00000005), 0x02000000;
/*
ptr1[-1]:ptr1类型是int *,ptr1[-1]相当于ptr1-1既ptr1减去int *类型长度4的地址。
*/
内存存储示例.png

10.3.1:数组做参数传递给函数,在函数内部退化为指针

void func(int a[], size_t len) {//a是数组名,len是数组元素个数。数组做函数参数,一般都这么传递
    printf(“sizeof(a) in func=%d\n”, sizeof(a));
}

int a[10] = {0};
printf(“sizeof(a)=%d\n”, sizeof(a));

func(a, 10);
/*
执行上面的代码,你会发现输出的两个结果为:
sizeof(a) = 40
sizeof(a) in func=4

也就是说,如果数组做了函数的参数,那么在函数内部,数组就变为了指针。实际上,数组是一个常量指针。比如:
int a[10];
a的类型为:int *const a;//a is a const pointer to int;
*/

10.3.2:数组溢出与预防

会造成程序的异常现象;C语言编译器对数组溢出不做检测,

image
  • 学了函数之后会发现程序1,无法退出,i=16之后,又会重新被置为0。
  • 程序2,a[9]不存在,越界溢出

11:字符串

字符串是编程语言中表示文本的数据类型。

11.1:字符串定义

字符串是由零个或多个字符组成的有限序列。C语言字符串可以定义为:”c1c2c3c4..cn\0”。

C语言的字符串是以’\0’结尾的

程序在存放字符串的时候,会自动在字符后面加上一个’\0’作为结尾。

11.1.1:转义字符

C语言中常见的转义字符如下:


image

11.2:程序中的字符串

  1. "a":字符串,存储在静态区。实际上是2个字符,末尾包好'\0'。使用方法char *str = "a";也就是可以赋值给字符指针,这个时候str指向"a"的首地址。
  2. 'a':字符常量,值类型,不存储在静态区,无内存。使用char ch='a';赋值给ch变量,ch存放在哪里,'a'就存放在哪里。不能使用char *p='a';因为'a'对应的类型是char。

并不是所有的常量都会被编译器放在常量区的,编译器认为普通的整型、浮点型或字符型常量在使用的时候是可以通过立即数来实现的,没有必要额外存储到数据区,如此节省了存储空间和运行时的访问时间。

常量区里存放的是一些不可改变的量,比如字符串常量。在实际的ELF(Executable and Linkable Format,可执行连接格式,是UNIX系统实验室(USL)作为应用程序二进制接口(Application Binary Interface,ABI)而开发和发布的。Linux作为类unix系统其程序仍然沿用了该格式。)的程序数据是分段存储的,对应的常量区就是“.rodata(只读数据)段“。

常量区的数据被标记为只读,也就是程序只有访问权而没有写入权,因此如果开发者需要使用某些不希望被改变的数据时可以将其放入常量区。

在C语言中常量有很多种,比如常见的:
字符常量:‘a’, ’A’, ’*’。
字符串常量:”helloworld”,”ilovechina”,”12345”。
整常量: 25,10,012,0x0a,0b00001010。
浮点常量: 3.14,123.456, 3.0E-23;

11.2.1:多字节字符串与宽字符字符串

  1. char表示一个ASCII字符占用1个字节,char类型字符串以”\0”结尾。
  2. wchar_t表示一个UNICODE字符占用2个或4个字节,wchar_t类型字符串以”\0\0”结尾。

多字节字符串

在多字节字符串中,每个字符的编码宽度都不等,可以是一个字节,也可以是多个字节。比如:
char *str = “Hello, world!您好,世界!”。
上面是一个多字节字符串。其中的英文字符占一个字节,而中文字符占2个字节。

宽字符字符串

而宽字符串中,每个字母占用的字节数是一样的。比如:
wchar_t *wstr = L“Hello, world!您好,世界!”
上面是一个宽字符串,每个字符,无论英文字母还是中文字符,都占2个字节。
scanf_s("%ws", s, 100)//不能有空格,有空格打断
fgetws(s, 100, stdin)
fputws(s, stdout)

用wctomb()等函数将宽字符串与多字节串进行相互转换

11.2.2:使用方式

字符串常量可以赋值给一个字符指针或者一个字符数组,比如:

  1. char *str = “this is a string”;
  2. char str2[]= “this is a string”;
  3. char str3[100] = “this is a string”;

语句1将”this is a string”赋值给了字符指针str。此时,str的值为”this is a string”的第一个字符的地址。”this is a string”这个常量字符串存储在内存常量区。而str即指向了存储这个常量字符串 的首地址。

语句2会将常量区中的”this is a string”拷贝到数组里面。并且数组的长度将为”this is a string” (包含’\0’)的长度。

语句3会将常量区中的”this is a string”拷贝到数组里面。并且数组的长度将为100个字节。语句3和语句2的区别是语句2没有指明数组的长度,那么数组的长度就是字符串的长度。

sizeof(str);//为指针的长度,所以在X86上是4,在X64上是8。
sizeof(str2)=17;//str2数组的长度,但str2没有显示指出数组的长度,而是按照分配给它的字符串的长度来分配。所以,值为17。
sizeof(str3)=100;//sizeof计算的是str3数组的长度,所以结果为100。
strlen(str)=16;//strlen计算的是字符串的字符个数(不包含’\0’)。
strlen(str2)=16;//原因同上。
strlen(str3)=16;//原因同上。

//把字符串存放在动态分配的内存空间中
char *p = (char *)malloc(100);

if (p == NULL)
       return; 

memset(p, 0, 100);
strcpy(p, “hello world”);//以p为首地址的内存中存这个字符串。

1,while循环遍历

char *str=“hello world!”;

while(*str!=‘\0’) {
    printf(“%c”, *str);
    str++;
}

11.3:字符串API

  1. strlen:计算字符串中字符个数(不含结尾字符’\0’);
  2. strstr:查找字符串中子串的位置
  3. strcmp/stricmp:比较2个字符串是否相等,stricmp()多了个i(ignore)比较时忽略大小写
  4. strchr/strrchr:strchr从左边开始在字符串中查找某个字符的位置,strrchr()右边开始
  5. strcpy/strcpy_s:strcpy将字符串复制到目标缓存,但不检测目标缓存的长度是否大于字符串的长度,无论是否超过,都照拷不误;在Windows平台推出了新的安全拷贝函数strcpy_s()。strcpy_s将检测字符串的长度是否超过了目标缓存的大小,如果超过,则拒绝拷贝并返回失败。
  6. strcat/strcat_s:用于字符串的拼接,并不检测目标缓冲的大小是否能够容纳下拼接之后的字符串,因此也容易造成缓冲区溢出漏洞。strcat_s()避免了这个问题。
  7. strtok/strtok_s: 将字符串s拆分为为一组字符串,delim为分隔符。
image.png

11.4:自己实现字符串API

size_t _strlen(const char *str) {
    size_t count = 0;

    while(*str++!= '\0') {
        count++;
    }
    
    return count;
} 

char *_strcpy(char *dst, char *src) {
    char *s = dst;//如果不用临时指针,最后dst指向尾部
    while(*s++ = *src);
    
    return dst;
}

/*
int strcmp(char *s1,char * s2),它的功能:比较字符串s1和s2:
当s1<s2时,返回值<0
当s1=s2时,返回值=0
当s1>s2时,返回值>0
*/
int _strcmp(const char *s1, const char *s2) {
    assert(s1!==NULL && s2!=NULL)
    
    while(*s1 && *s2 && (*s1==*s2)) {
        s1++;
        s2++;
    }
    
    return *s1-*s2
}

/*
strstr()的原型为:char *strstr(char *s1, char *s2)
功能是从字符串s1中寻找s2第一次出现的位置(不比较结束符NULL)。
*/
char *_strstr(char *s1, char *s2) {
    if (s1== NULL || s2==NULL)
        return NULL;
    
    if (!*s2)
        return ((char *)s1);
    
    char *s3, *s4;
       
    char *p = (char *)s1;
    while(*p) {
        s3 = p;
        s4 = s2;
        
        while(*s3 && *s4 && !(*s3-*s4))//*s3-*s4相等为0
            s3++, s4++;
        
        if(!*s4)//上面while在相等之后,s4指针到达尾部
            return p;
        
        p++;
    } 
    
    return NULL;
}

/*
strtok()的原型为:char *strtok(char *s, char *delim)
功能为:分解字符串为一组字符串。s为要分解的字符串,delim为分隔符字符串。
*/
//由于此函数的功能比较难理解,在给出实现算法前先看看它的具体使用例子:
char s[] = "Nice to meet you!";
char *d = " ";
char *p = NULL;
    
p = strtok(s, d);
    
while(p) {
    printf("%s\n",p);//”Nice” “to” “meet” “you!”
    p = strtok(NULL,d);
}
//实现暂略

/*tolower()的原型:char tolower(char ch)
功能为:将大写字母转换为小写字母
*/
char _tolower(char ch) {
    if(ch>=a && ch<=z) {
        return ch;
    }
    
    if(ch>=A && ch=Z) {
        return (ch + 'a' - 'A');
    }
}

//删除特定字符
void deleteChar(char *str, char c) {
    assert(str != NULL);
    char *tmp = NULL;
    
    while(*str != '\0') {
        if(*str != c) {
            *tmp++ = *str;
        }
        
        *str++; 
    }
}

void deleteChar(char *str, char c) {
    assert(str != NULL);
    int iDes = 0, iSrc = 0;
    
    do {
        if (str[iSrc] != c)
            str[iDes++] = str[iSrc];
    } while(str[iSrc++] != '\0');
}

//删除特定字符组void deleteChars(char *str, char chr[], int n)
//思路从字符串str中寻找chr第一次出现的位置,然后跑步指针赋值移位。

//逆置字符串
void reverseString(char *str) {
     char c;
     int n = strlen(str);

     for (int i = 0; i < n/2; i++) {
         c = str[i];
         str[i] = str[n-1-i];
         str[n-1-i] = c;
     }
}



©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,386评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,142评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,704评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,702评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,716评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,573评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,314评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,230评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,680评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,873评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,991评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,706评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,329评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,910评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,038评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,158评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,941评论 2 355

推荐阅读更多精彩内容