C语言将常用的操作封装到具体的函数当中,直接调用就可以,本章讲解常用的库函数。
主要介绍:字符串函数、时间和日期函数、数学运算函数、内存管理相关函数(重点)
声明字符串的方式:C语言没有单独的字符串类型,字符串被当作字符数组,即char型数组,表示方法如下。
方式1:字符数组
char str[ ] = "helle world";
方式2: 字符指针
char *str1 = "hello world";
两种方式的区别:
1.对于修改字符串:指针指向的字符串,在 C 语言内部被当作常量,不能修改字符串本身。
char* str = "hello!";
str[0] = 'z'; // 报错
如果使用数组声明字符串变量,就没有这个问题,可以修改数组的任意成员。
char str[ ] = "hello";
str[0] = 'z'; // 不报错
原因:从空间的角度来看;
因为系统会将字符串的字面量保存在内存的常量区,这个区域是不允许用户修改的。声明为指针时,指针变量存储的值是一个指向常量区的内存地址,因此用户不能通过这个地址去修改常量区。但是,声明为数组时,编译器会给数组单独分配一段内存,字符串字面量会被编译器解释成字符数组,逐个字符写入这段新分配的内存之中,而这段新内存是允许修改的。
举例:
int main() {
char str1[] = "hello"; //新开辟的空间,保存数组中的数据
char str2[] = "hello"; //新开辟的空间,保存数组中的数据
printf("%p\n",str1); //000000f4a93ff81a
printf("%p\n",str2); //000000f4a93ff814
char * str3 = "hello";
char * str4 = "hello"; //与前一个str3的数据是共享的,存
在于常量区
printf("%p\n",str3); //00007ff6842ca004
printf("%p\n",str4); //00007ff6842ca004
return 0;
}
2.指针变量可以指向其他的字符串。
char *str = "hello";
s = "world";
但是,字符数组变量不能指向另一个字符串。原因:字符数组的数组名,总是指向初始化时的字符串地址,不能修改。所以,声明字符数组后,不能直接用字符串赋值。
char str[ ] = "hello";
str = "world"; //报错
想要重新赋值,必须使用 C 语言原生提供的 strcpy() 函数,通过字符串拷贝完成赋值。这样做以后,数组变量的地址还是不变的,即 strcpy() 只是在原地址写入新的字符串,而不是让数组变量指向新的地址。strcpy (str , "world"); //正确。
两种错误的写法:
a . char *p; *p= "ABCDE"; // 错误b . char *s; scanf("%s", s); // 错误
原因:
a和b中,定义的字符串 指针都未进行初始化操作 ,指针指向的地址时随机的。(可能是非法或受保护的区域),直接向其写入数据(scanf)会导致未定义行为。
解决办法:
方法1:使用静态数组(栈内存)
char s[100]; // 分配固定大小的栈内存
scanf("%99s", s); // 限制输入长度,预留1字节给'\0'
方法2:动态内存分配(堆内存)
char *s = malloc(100 * sizeof(char)); // 动态分配内存
if (s == NULL) { /* 处理内存分配失败 */ }
scanf("%99s", s); // 限制输入长度
// ... 使用完毕后释放内存
free(s);
方法3: 更安全的输入函数(避免使用 scanf,改用 fgets 或 带长度限制的scanf_s)。
char s[100];
fgets(s, sizeof(s), stdin); // 读取包括换行符的输入
在使用字符串处理函数时,应当在程序文件的开头用 #include
1.strlen( ):返回字符串的长度,不包括末尾的空字符‘\0’。
函数原型:size_t strlen(const char * s); //参数是字符串变量,返回值是 size_t 类型的无符号整数,一般当作 int 类型处理。
2.strcpy( ):字符串的复制。
strcpy(char dest[ ], const char source[ ]);
说明:
strcpy(字符数组1,字符数组2) 函数 ,用于将一个字符串的内容复制到另一个字符串(前提:字符数组1的长度不小于字符数组2的长度,否则会溢出)。
举例:
#include
#include
int main() {
char str1[10], str2[] = "China";
strcpy(str1, str2);
printf("%s\n",str1);
//或
// strcpy(str1, "China"); //参数2,也可以是一个字符串常量
// printf("%s\n",str1)
思考如下输出结果:
int main() {
char str1[10] = "abcde1234", str2[] = "China";
strcpy(str1, str2);
printf("%s\n",str1); //
for(int i = 0;i < 10;i++){
printf("%c",str1[i]); //
}
printf("\n");
return 0;
}
输出:China
China 2345
说明:
利用strcpy函数的原理是:将第二个参数的字符串中的单个字符包括“\0”,按顺序从头开始把第一个参数位置字符串中的字符替换,“\0"也存在。当打印整个字符串时,遇到"\0",就会停止打印,故输出China。
3.strncpy( ):将字符串2中前面n个字符复制到字符数组1中去。(但复制的字符个数n不应多于str1中原有的字符(不包括′\0′)。)
strncpy (str1, str2, n);
4.strcat( ):把两个字符数组中的字符串连接起来,(把字符串2接到字符串1的后面,结果放在字符数组1中,函数调用后得到一个函数值——字符数组1的地址。)
char* strcat(char* s1, const char* s2);
注意事项:
#include
#include
int main() {
char str1[30] = {"People′s Republic of "};
char str2[] = {"China"};
printf("%s\n", strcat(str1, str2)); //People′sRepublic of China
printf("%s\n", str1); //People′s Republic of China
return 0;
}
6.strncat( ):将字符串2中前面n个字符连接到字符数组1中去。
strncat(str1, str2, n);
计算n的最大值:str1的变量长度-str1的字符长度-1(‘\0’占的字符);
举例:
#include
#include
int main() {
char s1[10] = "Hello";
char s2[8] = "World";
strncat(s1, s2, 3);
printf("%s\n", s1); //HelloWor
return 0;
}
7.strcmp( ):比较字符串1和字符串2。
字符串比较的规则是:将两个字符串自左至右逐个字符相比(按ASCII码值大小比较),直到出现不同的字符或遇到′\0′为止。
- 如全部字符相同,则认为两个字符串相等。返回值为0。
- 若出现不相同的字符,如果返回值为正数,则字符串1大;反之,返回值为负数,则字符串2大。
举例:
#include
#include
int main() {
char *str1 = "abxy";
char *str2 = "abmn";
printf("%d\n",strcmp(str1, str2)); // 1
int compare1 = strcmp("China", "Korea");
printf("%d\n",compare1); // -1
return 0;
}
8.strlwr()/strupr()
strlwr( 字符串 ) :将字符串中大写字母换成小写字母。strupr( 字符串 ) :将字符串中小写字母换成大写字母
举例:
#include
#include
int main() {
char str[] = "HelloWorld";
strlwr(str);
puts(str); //helloworld
strupr(str);
puts(str); //HELLOWORLD
}
1.基本数据类型->转字符串:
sprintf(str,"%d%d",a,b); //声明在标准的
里面。
#include
int main() {
char str1[20]; //字符数组,即字符串
char str2[20];
char str3[20];
int a = 111, b = 222;
char c = 'a';
double d = 333.444;
sprintf(str1, "%d %d", a, b);
sprintf(str2, "%d%c", a, c);
sprintf(str3, "%.5f", d);
printf("str1=%s\n", str1); //111 222
printf("str2=%s\n", str2); //111a
printf("str3=%s\n", str3); //333.44400
return 0;
}
说明:
sprintf( )和平时我们常用的printf( )函数的功能相似,只是sprintf()函数输出到字符串中,而printf()函数输出到屏幕上。
2.字符串->基本数据类型:
atoi( )或atof( );声明在
中。//注意:不能传化成字符。字符利用角标取出。
举例:
#include
#include
int main() {
char str1[10] = "123456";
char str2[4] = "111";
char str3[10] = "12.67423";
char str4[2] = "a";
int i = atoi(str1);
int j = atof(str1);
short s = atoi(str2);
double d = atof(str3);
char c = str4[0];
printf("i=%d,j=%d,s=%d,d=%lf,c=%c", i, j, s, d, c);
return 0;
}
1.返回一个值,即格林尼治时间1970年1月1日00:00:00到当前时刻的时长,时长单位是秒。
time_t time ( time_t *t);
2.获取当前时间,返回一个表示当地时间的字符串(当地时间是基于参数timer的)。
char * ctime (const time_t *timer );
3.计算time1和time2之间相差的秒数(time1-time2)。
double difftime (time_t time1, time_t time2);
举例:
#include
#include //该头文件中,声明日期和时间相关的函数
// 运行test函数,看看执行花费时间
void test() {
int i = 0;
int sum = 0;
int j = 0;
for (i = 0; i < 10000000; i++) {
sum = 0;
for (j = 0; j < 100; j++) {
sum += j;
}
}
}
int main() {
printf("程序启动...\n");
time_t start_t;
//先得到执行test前的时间
time(&start_t); //获取当前时间
test(); //执行test
time_t end_t;
//再得到执行test后的时间
time(&end_t); //获取当前时间
double diff_t; //存放时间差
diff_t = difftime(end_t, start_t); //时间差,按秒 ent_t - start_t
//然后得到两个时间差就是耗用的时间
printf("%d\n",start_t); //1697026306
printf("%d\n",end_t); //1697026308
printf("执行test()函数 耗用了%.2f 秒\n", diff_t); //执行test()函数 耗用了2.00 秒
//获取时间对应的字符串的表示
char * startTimeStr = ctime(&start_t);
printf("%s\n",startTimeStr);
return 0;
}
- double exp(double x) :返回 e 的 x 次幂的值。
- double log(double x) :返回 x 的自然对数(基数为 e 的对数)
- double pow(double x, double y) :返回 x 的 y 次幂。
- double sqrt(double x) :返回 x 的平方根。
- double fabs(double x) :返回 x 的绝对值。
举例:
#include
#include
int main() {
double d1 = pow(2.0, 3.0);
double d2 = sqrt(5.0);
printf("d1=%.2f\n", d1); //d1=8.00
printf("d2=%f\n", d2); //d2=2.236068
return 0;
}
声明 void 指针的原因:
- 向系统请求内存的时候,有时不确定会有什么样的数据写入内存,需要先获得内存块,稍后再确定写入的数据类型。
- 由于指针变量必须有类型,否则编译器无法知道如何解读内存块保存的二进制数据。为了满足这种需求,C 语言提供了一种不定类型的指针,叫 做 void 指针。
- 它只有内存块的 地址信息 , 没有类型信息 ,等到使用该块内存的时候,再向编译器补充说明,里面的数据类型是什么。
void 指针可以转为任一类型的指针相互转换:
int x = 10 ;void * p = & x ; // 整数指针转为 void 指针
int * q = p ; // void 指针转为整数指针或 (int *)p。 //强制类型转换
声明了四个在
格式:void *malloc(unsigned int size); //size的类型为无符号整型
说明:在内存的 动态存储区(堆区) 中分配一个 长度为size 的 连续空间 。并将该空间的首地址作为函数值返回,即此函数是一个指针函数
应用1:对一个变量进行内存分配
int *p = (int * ) malloc( sizeof ( int ) );
应用2:动态申请数值空间
int *p = (int *)malloc(n * sizeof( int )); //创建一个一维数组
//对一维数组进行赋值。
for(i = 0;i< n;i++){
p[i] = i;
}
步骤:
- 第一步:row和column;
- 第二步:初始化外层数组;
- 第三步:初始化内层数组,并给内存数组元素进行赋值。
int row,column;
printf("请输入行和列数:");
scanf("%d%d",&row,&column);
//初始化外层数组,二维数组的行数。使用二级指针加上malloc()函数
int **arr = (int**)malloc(row * sizeof(int*));//int*:每一个元素指向了一个一维数组
//初始化内层数组,并给元素赋值
for (int i = 0; i < row; ++i) {
arr[i] = (int*) malloc(column * sizeof (int));
//赋值
for (int j = 0; j < column; ++j) {
arr[i][j] = 1;
printf("%d",arr[i][j]);
}
printf("\n");
}
//使用完回收此数组
free(arr);
应用3:结构体结点
struct node *p;
p = (struct node *) malloc (sizeof(struct node));
//(struct node*)强制类型转化。
typedef struct node{
int data;
struct node *node; //指针。
}BTNode;
声明结点的方式1:
BTNode bt1;
方式2:熟练掌握
BTNode *bt2;
bt2 = (BTNode *)malloc(sizeof(BTNode));
调用结构体成员时,
//针对于方式1:结构体变量取成员,用"."
int x = bt1.data;
//针对于方式2:指向结构体的指针取成员,用"->"
int x = bt2->data;
int x = (*bt2).data;//以前的写法
格式:void free(void * p);
作用:释放指针变量p所指向的内存空间,使这部分内存能重新被其它变量使用。否则这个内存块会一直占用到程序运行结束。
int *p;
p=(int *)malloc(sizeof(int));
//...各种操作...
free(p); //千万不要忘了使用free()释放内存!
注意:
- 指针 p 必须是经过动态分配函数 malloc 成功后返回的首地址。
- 分配的内存块一旦释放,就不应该再次操作已经释放的地址,也不应该再次使用 free() 对该地址释放第二次。
- 如果忘记调用free()函数,同时p所在的函数调用结束后p指针已经消失了,导致无法访问未回收的内存块,构成内存泄漏:。
#include
#include
#define N 5
void checkScore(int * p,int len){
printf("低于60分成绩的有:\n");
for (int i = 0; i < N; ++i) {
if(p[i] < 60){
printf("%d ",p[i]);
}
}
}
int main(){
//1.使用malloc()函数动态申请内存空间(堆空间),对应int类型的长度为5的数组
int *person_score =(int *)malloc(N*sizeof (int));
if(person_score != NULL){
//2.给元素赋值;
for (int i = 0; i < N; ++i) {
printf("总共要输入%d个学生成绩,请输入第%d个:",N,i+1);
fflush(stdout);
scanf("%d",person_score+i);
}
//3.声明一个函数判断成绩低于60分的情况,将低于60分的成绩输出。
checkScore(person_score,N);
//4.释放空间
free(person_score);
}
函数原型:
void *calloc (unsigned int n,unsigned int size);
说明:在内存的动态存储区(堆区)中分配n个,单位长度为size的连续空间,这个空间一般比较大,总共占用n*size 个字节。并将该空间的首地址作为函数的返回值。如果函数没有成功执行,返回 NULL。
使用场景:calloc()函数适合为 一维数组 开辟动态存储空间,n为数组元素个数,每个元素长度为size。
举例:
int *p;
p = (int *)calloc(10,sizeof(int)); //开辟空间的同时,其内容初始化为零
//等同于
int* p;
p = (int *)malloc(10 * sizeof(int)); //开辟空间
memset(p, 0, sizeof(int) * 10); //对开辟的空间内容进行初始化。
函数原型:
void * realloc(void p,unsigned int size);
作用:重新分配malloc( ) 或 calloc( )函数获得的动态空间大小,即调整大小的内存空间。将先前开辟的内存块的指针p指向的动态空间大小改变为size,单位字节。
返回值:一个全新的地址(数据也会自动复制过去),也可能返回跟原来一样的地址(将原地址开辟的空间缩小)。分配失败返回NULL。
举例·1:
int *b;
b =(int*) malloc(sizeof(int) * 10);
b = realloc(b ,sizeof(int) *100);
说明:指针 b 原来指向10个成员的整数数组,使用 realloc() 调整为100个成员的数组
举例2:(在数据结构中)动态栈入栈时,判断是否需要扩容
int push(SqStack &S, ElemType e) {
if (S.top - S.bottom >= S.stacksize) {
//栈满,追加存储空间
S.bottom = (ElemType *) realloc(S.bottom,
(STACKINCREMENT + S.stacksize) * sizeof(ElemType));
if (!S.bottom) //if(S.bottom == NULL)
return FALSE; //空间分配失败
S.top = S.bottom + S.stacksize;
S.stacksize += STACKINCREMENT;
}
*S.top = e;
S.top++; // 栈顶指针加1
return TRUE;
}
- 避免分配大量的小内存块。分配堆上的内存有一些系统开销,所以分配许多小的内存块比分配几个大内存块的系统开销大
- 仅在需要时分配内存。只要使用完堆上的内存块,就需要及时释放它,否则可能出现内存泄漏。 这里需要遵守原则:谁分配,谁释放。
- 总是确保释放以分配的内存。在编写分配内存的代码时,就要确定在代码的什么地方释放内存。
1)内存分配未成功,却使用了它
创建一个动态内存后,要进行判断是否创建成功 if ( p == NULL){ } 。
2)内存分配虽然成功,但是尚未初始化就引用它(经常忽视的情况)
int * p = NULL;
p = (int*)malloc(sizeof(int));
if (p == NULL){/*...*/}
/*初始化为0*/
memset(p, 0, sizeof(int));
3)内存分配成功并且已经初始化,但操作时提示内存越界
4)忘记了释放内存,造成内存泄漏
malloc()函数要和free()函数一定要成对出现。
5)未正确的释放内存,造成内存泄
void getMemory(int *p) {
p = (int *) malloc(sizeof(int)); // 在这里修改的是局部指针 p,不会影响 main 函数中的原始指针 ptr
//....
}
int main() {
int *ptr = NULL;
getMemory(ptr); // 将 ptr 的值传递给 getMemory,但是在函数内部修改的是 p,而不是 ptr
printf("ptr = %d\n", *ptr); // 这里的 *ptr 是未定义行为,因为 ptr 没有指向有效的内存
free(ptr); // 这里试图释放未分配的内存,会导致问题
}
6)释放了内存却继续使用它
函数的return语句写错了,注意 不要返回指向“栈内存”的“指针”或者“引用” ,因为该内存在函数体结束时被自动销毁。
long *p;
void addr() {
long k;
k = 0;
p = &k;
}
注意:
在计算机系统,特别是嵌入式系统中,内存资源是非常有限的。尤其对于 移动端开发者 来说,硬件资源的限制使得其在程序设计中首要考虑的问题就是如何 有效地管理内存资源 。