C语言复习第七天
字符串典型问题
问题一
下面的程序输出什么?
#include <stdio.h>
int main()
{
char buf[10] = {0};
char src[] = "hello %s";
snprintf(buf, sizeof(buf), src);
printf("buf = %s\n", buf);
return 0;
}
分析:
snprintf函数本身是可变参数函数,原型如下:
int snprintf(char* buffer,int buff_size,const char*fomart,…)
当函数只有三个参数时,如果第三个参数没有包含数任何的格式化信息,函数调用没有问题,相反,如果第三个参数包含了格式化信息,但缺少后续对应参数,则程序行为不确定。
上述代码正确使用案列
#include <stdio.h>
int main()
{
char buf[13] = {0};
char * str="world";
char src[] = "hello %s";
snprintf(buf, sizeof(buf), src,str);
printf("buf = %s\n", buf);
return 0;
}
因为第三个参数中包含了 %s的格式化信息,所以我们要为其填入值,否则函数行为不正确。
问题二
下面的程序输出什么:
#include <stdio.h>
#include <string.h>
int main()
{
#define STR "Hello chen\0 moqian\0 "
char* src = STR;
char buf[255] = {0};
snprintf(buf, sizeof(buf), src);
printf("strlen(STR) = %d\n", strlen(STR));
printf("sizeof(STR) = %d\n", sizeof(STR));
printf("strlen(src) = %d\n", strlen(src));
printf("sizeof(src) = %d\n", sizeof(src));
printf("strlen(buf) = %d\n", strlen(buf));
printf("sizeof(buf) = %d\n", sizeof(buf));
printf("src = %s\n", src);
printf("buf = %s\n", buf);
return 0;
}
分析:
主要区别字符指针,字符数组,字符串的不同。
- 字符串相关的函数均以第一个出现的==’\0’作为结束符==
- 编译器总是会在字符串字面量的末尾添加’\0’
- 字符串字面量的本质为数组
典型问题三
下面的程序输出什么?
#include <stdio.h>
#define S1 "hello chenmo"
#define S2 "hello chenmo"
int main()
{
if(S1==S2)
{
printf("Equal\n");
}
else
{
printf("Non Equal\n");
}
if( strcmp(S1, S2) == 0 )
{
printf("Equal\n");
}
else
{
printf("Non Equal\n");
}
retun 0;
}
gcc编译效果:
vs编译效果:
分析:
- 字符串之间的相等比较需要用strcmp完成
- 不可直接用= =进行字符串直接的比较
- == 完全相同的字符串字面量的比较结果为false
tips:
一些现代编译器能够将相同的字符串字面量映射到同一个无名字符数组,因此==比较为true.
例如:现在的gcc,mscv编译器都用优化,但是还是使用strcmp比较好。
典型问题四
编程例题
高效率:
循环左右移动字符串:
#include <stdio.h>
#include <string.h>
//字符串右移
void right_shift_r(const char* src,char*result,unsigned int n)
{
const unsigned int LEN=strlen(src);
for(int i=0;i<LEN;i++)
{
result[(n+i)%LEN]=src[i];
}
result[LEN]='\0';
}
//字符串左移
void left_shift_l(const char* src,char*result,unsigned int n)
{
const unsigned int LEN=strlen(src);
for(int i=0;i<LEN;i++)
{
i<n? (result[LEN-n+i]=src[i]):(result[i-n]=src[i]);
}
result[LEN]='\0';
}
int main()
{
char result[255] = {0};
right_shift_r("abcde", result, 2);
printf("%s\n", result);
left_shift_l("abcde", result, 2);
printf("%s\n", result);
return 0;
}
数组指针和指针数组分析
数组指针
数组类型
- C语言中的数组有自己的特定的类型
- 数组的类型由元素类型和数组大小共同决定
例
int array[5] 的类型为 int[5]
定义数组类型
- C语言中通过==typedef ==为数组类型重命名
typedef type(name)[size];
——数组类型:
typedef int(AINT5)[5];
typedef float(AFLOAT10)[10];
——数组定义:
AINT5 iArray;
AFLOAT fArray;
数组指针
- 数组指针用于指向一个数组
- 数组名是数组首元素的起始地址,但并不是数组的起始地址。
- 通过将取地址符&作用于数组名可以得到数组的起始地址
- 可通过数组类型定义数组指针 ArrayType*pointer;
- 也可以直接定义:type(*pointer)[n];
——pointer为数组指针变量名
——type为指向的数组的元素类型
——n为指向的数组的大小
示例
#include <stdio.h>
#include <stdlib.h>
typedef int(AINT5)[5];
typedef float(AFLOAT10)[10];
typedef char(ACHAR9)[9];
void str_length(char(*pcw)[9])
{
printf("size=%d\n",sizeof(*pcw));
}
int main()
{
AINT5 a1;
float fArray[10];
AFLOAT10* pf = &fArray;
ACHAR9 cArray;
char(*pc)[9] =&cArray;
char(*pcw)[9]=&cArray;
char(*pcww)[4]=&cArray;
char * pcc=cArray;
float * pff=fArray;
//int i = 0;
printf("%d, %d\n", sizeof(*pcw), sizeof(*pcww));
str_length(pcw);
for(int i=0; i<10; i++)
{
(*pc)[i]=48+i;
(*pf)[i]=i;
}
for(int i=0; i<10; i++)
{
printf("%p\n", &(*pc)[i]);
}
printf("Begin\n");
for(int i=0; i<10; i++)
{
printf("pcww[%d]=%c\n",i, (*pcww)[i]);
printf(" pcw[%d]=%c\n",i, (*pcw)[i]);
}
printf("%p, %p, %p\n", &cArray, pc+1, pcw+1);
return 0;
}
解析:
主要区分:
(*pcww)[i] // 指的是取数组指针指向的数组中的第i个元素
*pcww[i] //指的是取pcww为首地址+i之后的地址的值
//地址计算为: pcww+(*pcww)*i 取前面式子中的地址存放的值
指针数组
- 指针数组是一个普通的数组
- 指针数组中每个元素为一个指针
- 指针数组的定义:type* pArray[n];
==type*==为数组中每个元素的类型
pArray为数组名
n为数组大小
使用示例://利用字符指针数组查找元素
#include <stdio.h>
#include <string.h>
#define DIM(a) (sizeof(a)/sizeof(*a))
int lookup_keyword(const char* key, const char* table[], const int size)
{
int ret = -1;
int i = 0;
for(i=0; i<size; i++)
{
if( strcmp(key, table[i]) == 0 )
{
ret = i;
break;
}
}
return ret;
}
int main()
{
float farray[3]={1,2,3};
float* pFloat[3];
for(int i=0;i<3;i++)
{
pFloat[i]=&farray[i];
}
for(int i=0;i<3;i++)
{
printf("%f\n",*pFloat[i]);
}
for(int i=0;i<3;i++)
{
*pFloat[i]=i+3;
}
for(int i=0;i<3;i++)
{
printf("%f\n",*pFloat[i]);
}
const char* keyword[] = {
"do",
"for",
"if",
"register",
"return",
"switch",
"while",
"case",
"static"
};
printf("%d\n", lookup_keyword("return", keyword, DIM(keyword)));
printf("%d\n", lookup_keyword("main", keyword, DIM(keyword)));
return 0;
}
小结
- 数组的类型由元素类型和数组大小共同决定
- 数组指针是一个指针,指向对应类型的数组
- 指针数组是一个数组,其中每个元素都为指针
- 数组指针遵循指针运算法则
- 指针数组拥有C语言数组的各种特性
main函数与命令行参数
main函数的概念
- C语言中的main函数称之为主函数
- 一个C程序是从main函数开始执行的
main函数的本质
- main函数是操作系统调用的函数
- 操作系统总是将main函数作为应用程序的开始
- 操作系统将main函数的返回值作为程序的退出状态
编程示例:
//a.c
#include <stdio.h>
int main()
{
printf("I'm A!\n");
return 1;
}
//b.c
#include <stdio.h>
int main()
{
printf("I'm B!\n");
return 99;
}
gcc中通过echo查看上次程序运行的返回值。
同时可以编写shell脚本根据返回值,来执行下一个程序。
示例:
#!/bin/sh
./a # 运行a程序
a=$? #将a程序的返回值保存在a变量中
if [ "$a" != "0" ] #通过if判断,语法注意,前后两个空格必须加。
then
./b
fi
echo "a is"
echo $a
运行结果
main函数的参数
- 程序执行时可以向main函数传递参数
——int main()
——int main(int argc)
——int main(int argc,charargv[])
——int main(int argc,charargv[],char*env[]) //命令行参数个数,命令行参数数组,环境变量数组
示例:
#include <stdio.h>
int main(int argc, char* argv[], char* env[]) //命令行参数个数,命令行参数数组,环境变量数组
{
int i = 0;
printf("============== Begin argv ==============\n");
for(i=0; i<argc; i++)
{
printf("%s\n", argv[i]);
}
printf("============== End argv ==============\n");
printf("\n");
printf("\n");
printf("\n");
printf("============== Begin env ==============\n");
for(i=0; env[i]!=NULL; i++)
{
printf("%s\n", env[i]);
}
printf("============== End env ==============\n");
return 0;
}
main函数一定是程序执行的第一个函数吗?
gcc编译器和一些现代的编译器,支持在main函数之前和之后执行函数。
示例: 通过attribute属性关键字
#include <stdio.h>
#ifndef __GNUC__
#define __attribute__(x)
#endif
__attribute__((constructor))
void before_main()
{
printf("%s\n",__FUNCTION__);
}
__attribute__((constructor))
void befor_main1()
{
printf("%s\n",__FUNCTION__);
}
__attribute__((destructor))
void after_main()
{
printf("%s\n",__FUNCTION__);
}
int main()
{
printf("%s\n",__FUNCTION__);
return 0;
}
小结
- 一个C程序是从main函数开始执行的
- main函数是操作系统调用的函数
- main函数有参数和返回值
- 现代编译器支持在main函数前后调用其他函数
多维数组和多维指针
指向指针的指针
- 指针的本质是变量
- 指针会占用一定的内存空间
- 可以定义指针的指针来保存指针变量的地址值
示例:通过指针的指针来改变指针的指向(即在指针保存的变量地址)
#include <stdio.h>
int main()
{
int i=3;
int *p=NULL;
int** pp=NULL;
p=&i;
pp=&p;
printf("*p =%d\n",*p);
printf("p =%p\n",p);
printf("pp =%p\n",pp);
printf("*pp=%p\n",*pp);
int a=6;
*pp=&a;
printf("*p =%d\n",*p);
printf("p =%p\n",p);
printf("pp =%p\n",pp);
printf("*pp=%p\n",*pp);
return 0;
}
重置动态空间的大小
- 为什么需要指向指针的指针?
——指针在本质上也是变量
——对于指针也同样存在传值调用与传址调用
示例:重置动态空间的大小
#include <stdio.h>
#include <malloc.h>
int reset(char **pp,int size,int new_size)
{
int ret=1;
int len=0;
char *temp=NULL;
char *p= *pp;
char *pt=NULL;
if((new_size>0)&&(pp!=NULL))
{
len=new_size<size?new_size:size;
pt=(char*)malloc(sizeof(char)*len);
temp=pt;
for(int i=0;i<len;i++)
{
*temp++=*p++;
}
free(*pp);
*pp=pt;
}
else
{
ret=0;
}
return ret;
}
int main()
{
char *p=(char*)malloc(sizeof(char)*5);
char pp[]="hello";
for(int i=0;i<5;i++)
{
p[i]=48+i;
}
printf("%p\n", p);
printf("%u\n",strlen(p));
printf("%u\n",sizeof(pp));
if( reset(&p, 5, 3) )
{
printf("%p\n", p);
printf("%u\n",strlen(p));
}
free(p);
}
二维数组与二级指针
- 二位数组在内存中以一维的方式排布
- 二维数组中的第一维是一维数组
- 二维数组中的第二维才是具体的值
- 二维数组的数组名可以看作常量指针
示例:遍历二维数组
#include <stdio.h>
#include <malloc.h>
void printArray(int a[], int size)
{
int i = 0;
printf("printArray: %d\n", sizeof(a));
for(i=0; i<size; i++)
{
printf("%d\n", a[i]);
}
}
int main()
{
int a[3][3] = {{0, 1, 2}, {3, 4, 5}, {6, 7, 8}};
int* p = &a[0][0];
int i = 0;
int j = 0;
for(i=0; i<3; i++)
{
printf(" %p\n ", *(a+i)); //查看第一维,第一维实际就是有多个指针构成的。
printf("%p\n ", a[i]); //查看第一维
for(j=0; j<3; j++)
{
printf("%d, ", *(*(a+i) + j));
}
printf("\n");
}
printf("\n");
printArray(p, 9);
return 0;
}
tips
从打印结果明显可以看出,二位数组的第一维明显就是个指针,第一维其实就是数组指针
数组名
- 一维数组名代表数组首元素的地址
——int a[5] a的类型为int * - 二维数组名同样代表数组首元素的地址
——int a[2][5] a的类型为int(*)[5]
== 结论:==
- 二维数组名可以看做是指向数组的常量指针(即为数组指针)
- 二维数组可以看做是一维数组
- 二位数组中的每个元素都是同类型的一维数组
示例:(如何动态申请二位数组)
#include <stdio.h>
#include <malloc.h>
int **malloc2d(int row,int col)
{
int **ret=NULL;
if((row>0)&&(col>0))
{
ret=(int**)malloc(sizeof(int *)*row);
int *p=(int *)malloc(sizeof(int)*row*col);
if((ret!=NULL)&&(p!=NULL))
{
for(int i=0;i<row;i++)
{
ret[i]=p + i*col;
}
}
else
{
free(ret);
free(p);
ret=NULL;
p=NULL;
}
}
return ret;
}
void free2d(int **p)
{
if(*p!=NULL)
{
free(*p);
}
free(p);
}
int main()
{
int** a = malloc2d(3, 3);
int i = 0;
int j = 0;
for(i=0; i<3; i++)a
{
for(j=0; j<3; j++)
{
printf("%d, ", a[i][j]);
}
printf("\n");
}
free2d(a);
return 0;
}
小结
- C语言只支持一维数组
- C语言中的数组大小必须在编译期就作为常数确定
- C语言中的数组元素可是任何类型的数据
- C语言中的数组的元素可以是另一个数组
数组参数和指针参数分析
为什么C语言中的数组参数会退化为指针?
退化的意义
- C语言中只会以值拷贝的方式传递参数
- 当向函数传递数组时:
——将整个数组拷贝一份传入函数 ❌
——将数组名看做常量指针传数组首元素地址 ✔
tips:
C语言以高效作为最初设计目标:
- 参数传递的时候如果拷贝整个数组执行效率将大大下降
- 参数位于栈上,太大的数组拷贝将导致栈溢出
二位数组参数
- 二位数组参数同样存在退化的问题
——二维数组可以看做是一维数组
——二维数组中的每个元素是一维数组 - 二维数组参数中第一维的参数可以省略
——void f(int a[5]) <——>void f(int a[])<——>void f(int *a)
——void g(int a[3][3])<——>void g(int a[][3])<——>void g(int(*a)[3])
等价关系:
数组参数 | 等效的指针参数 |
---|---|
一维数组:float a[5] | 指针: float* a |
指针数组:float *a[5] | 指针的指针:float **a |
二维数组:float a[3][5] | 数组指针:float(*a)[5] |
被忽视的知识点
- C语言无法向一个函数传递任意的多维数组
- 必须提供除第一维之外的所有维的长度
——第一维之外维度信息用于完成指针运算
——N维数组的本质是一维数组,元素是N-1维的数组
——对于多维数组的函数参数只有第一维是可变的
示例分析:
#include <stdio.h>
void access(int a[][3], int row)
{
int col = sizeof(*a) / sizeof(int);
int i = 0;
int j = 0;
printf("sizeof(a) = %d\n", sizeof(a));
printf("sizeof(*a) = %d\n", sizeof(*a));
for(i=0; i<row; i++)
{
for(j=0; j<col; j++)
{
printf("%d\n", a[i][j]);
}
}
printf("\n");
}
void access_ex(int b[][2][3], int n)
{
int i = 0;
int j = 0;
int k = 0;
printf("sizeof(b) = %d\n", sizeof(b));
printf("sizeof(*b) = %d\n", sizeof(*b));
printf("sizeof(*b) = %d\n", sizeof(**b));
for(i=0; i<n; i++)
{
for(j=0; j<2; j++)
{
for(k=0; k<3; k++)
{
printf("%d\n", b[i][j][k]);
}
}
}
printf("\n");
}
int main()
{
int a[3][3] = {{0, 1, 2}, {3, 4, 5}, {6, 7, 8}};
int aa[2][2] = {0};
int b[1][2][3] = {0};
access(a, 3);
// access(aa, 2); warning
access_ex(b, 1);
// access_ex(aa, 2); warning
return 0;
}
小结
- C语言中只会以值拷贝的方式传递参数
- C语言中的数组参数必然退化为指针
- 多维数组参数必须提供除第一维之外的所有维长度
- 对于多维数组的函数参数只有第一维是可变的