1c语言基础

发布于:2024-10-09 ⋅ 阅读:(36) ⋅ 点赞:(0)

1.关键字

一、数据类型关键字

A基本数据类型(5个)

  • void:声明函数无返回值或无参数,声明无类型指针,显式丢弃运算结果
  • char:字符型类型数据,属于整型数据的一种
  • int:整型数据,通常为编译器指定的机器字长
  • float:单精度浮点型数据,属于浮点数据的一种
  • double:双精度浮点型数据,属于浮点数据的一种

B类型修饰关键字(4个)

  • short:修饰int,短整型数据,可省略被修饰的int。
  • long:修饰int,长整形数据,可省略被修饰的int。
  • signed:修饰整型数据,有符号数据类型
  • unsigned:修饰整型数据,无符号数据类型

C复杂类型关键字(5个)

  • struct:结构体声明
  • union:共用体声明
  • enum:枚举声明
  • typedef:声明类型别名
  • sizeof:得到特定类型或特定类型变量的大小

D存储级别关键字(6个)

  • auto:指定为自动变量,由编译器自动分配及释放。通常在栈上分配
  • static:指定为静态变量,分配在静态变量区,修饰函数时,指定函数作用域为文件内部
  • register:指定为寄存器变量,建议编译器将变量存储到寄存器中使用,也可以修饰函数形参,建议编译器通过寄存器而不是堆栈传递参数
  • extern:指定对应变量为外部变量,即在另外的目标文件中定义,可以认为是约定由另外文件声明的对象的一个“引用“
  • const:与volatile合称“cv特性”,指定变量不可被当前线程/进程改变(但有可能被系统或其他线程/进程改变)
  • volatile:与const合称“cv特性”,指定变量的值有可能会被系统或其他进程/线程改变,强制编译器每次从内存中取得该变量的值

二、流程控制关键字

A跳转结构(4个)

  • return:用在函数体中,返回特定值(或者是void值,即不返回值)
  • continue:结束当前循环,开始下一轮循环
  • break:跳出当前循环或switch结构
  • goto:无条件跳转语句

B分支结构(5个)

  • if:条件语句
  • else:条件语句否定分支(与if连用)
  • switch:开关语句(多重分支语句)
  • case:开关语句中的分支标记
  • default:开关语句中的“其他”分治,可选。

C循环结构(3个)

  • for:for循环结构,for(1;2;3)4;的执行顺序为1->2->4->3->2…循环,其中2为循环条件
  • do:do循环结构,do 1 while(2);的执行顺序是1->2->1…循环,2为循环条件
  • while:while循环结构,while(1) 2;的执行顺序是1->2->1…循环,1为循环条件

以上循环语句,当循环条件表达式为真则继续循环,为假则跳出循环。

关键字 typedef

typedef 顾名思义是类型定义,这里应该理解为类型重命名。比如:

//将unsigned int 重命名为uint_32, 所以uint_32也是一个类型名
typedef unsigned int uint_32;
int main()
{
    //观察num1和num2,这两个变量的类型是一样的
    unsigned int num1 = 0;
    uint_32 num2 = 0;
    return 0; }

关键字static

在C语言中:
static是用来修饰变量和函数的

  1. 修饰局部变量-称为静态局部变量
  2. 修饰全局变量-称为静态全局变量
  3. 修饰函数-称为静态函数
1 修饰局部变量

对比代码1和代码2的效果理解static修饰局部变量的意义。

//代码1
#include <stdio.h>
void test()
{
    int i = 0;
    i++;
    printf("%d ", i);  //每次函数执行结束,i值都会被释放掉
}
int main()
{
 int i = 0;
    for(i=0; i<10; i++)
   {
        test(); //每次有打印1
   }
    return 0; }
12345678910111213141516
//代码2
#include <stdio.h>
void test()
{
    //static修饰局部变量
    static int i = 0;  //test()执行结束后,保留i值
    i++;
    printf("%d ", i);
}
int main()
{
 int i = 0;
    for(i=0; i<10; i++)
   {
        test(); //每次有打印1到10
   }
    return 0; }
1234567891011121314151617

结论:static修饰局部变量改变了变量的生命周期,让静态局部变量出了作用域依然存在,到程序结束,生命周期才结束。

2 修饰全局变量

代码1正常,代码2在编译的时候会出现连接性错误。

//代码1
//add.c
int g_val = 2018;
//test.c
int main()
{
    printf("%d\n", g_val);
    return 0; }

//代码2
//add.c   static 全局变量
static int g_val = 2018;
//test.c
int main()
{
    printf("%d\n", g_val);
    return 0; }
1234567891011121314151617

结论:一个全局变量被static修饰,使得这个全局变量只能在本源文件内使用,不能在其他源文件内使用。

3 修饰函数

代码1正常,代码2在编译的时候会出现连接性错误,和修饰全局变量一样。

//代码1
//add.c
int Add(int x, int y) {
    return x+y; }
//test.c
int main()
{
    printf("%d\n", Add(2, 3));
    return 0; }

//代码2
//add.c   static 修饰函数
static int Add(int x, int y) {
    return c+y; }
//test.c
int main()
{
    printf("%d\n", Add(2, 3));
    return 0; }
12345678910111213141516171819

结论:一个函数被static修饰,使得这个函数只能在本源文件内使用,不能在其他源文件内使用。

2.基础数据类型 和 尺寸

char //字符数据类型

short //短整型

int //整形

long //长整型

long //更长的整形

float //单精度浮点数

double //双精度浮点数

//C语言有没有字符串类型? 答案是没有。c语言中没有string类型,string是用char型数组来构造的。

每种数据类型占据的字节大小:

#include <stdio.h>  
int main()
{
		//以下是在win10的vs2017显示的结果
    printf("%d\n", sizeof(char));  // 1
    printf("%d\n", sizeof(short));  // 2
    printf("%d\n", sizeof(int));  // 4
    printf("%d\n", sizeof(long));  // 4
    printf("%d\n", sizeof(long long));  //8
    printf("%d\n", sizeof(float));  //4
    printf("%d\n", sizeof(double));  // 8
    printf("%d\n", sizeof(long double));  //8
    return 0; }
12345678910111213

存在这么多的类型,其实是为了更加丰富的表达生活中的各种值。
类型的使用:

char ch = 'w';  //字符型
int weight = 120;  //整型
float salary = 20000.0f;  //单精度浮点型

3. 变量、常量

生活中的有些值是不变的(比如:圆周率,性别,身份证号码,血型等等)
有些值是可变的(比如:年龄,体重,薪资)。
不变的值,C语言中用常量的概念来表示,变得值C语言中用变量来表示。

3.1 定义变量的方法

语法:变量类型 变量名称 = 初始值;

int age = 150;
float weight = 45.5f;
char ch = 'w';
123

3.2 变量的分类

1)局部变量
2)全局变量
#include <stdio.h>
int global = 2019;//全局变量
int main()
{
    int local = 2018;//局部变量
    //下面定义的global会不会有问题?没有问题
    int global = 2020;//局部变量,当局部变量和全局变量同名时,优先使用局部变量,这个人感觉和搜索路径相关!
    printf("global = %d\n", global);
    return 0; }

3.3 变量的使用(使用 scanf如何 接收数据)

#include <stdio.h>
int main()
{
    int num1 = 0;
    int num2 = 0;
    int sum = 0;
    printf("输入两个操作数:>");
    scanf("%d %d", &num1, &num2);  //int -- %d  float -- %f  double -- %lf  char -- %c
    sum = num1 + num2;
    printf("sum = %d\n", sum);
    return 0; }

在C语言中,scanf函数可以用来输入longlong longshort类型的数据。下面是一些示例:

#include <stdio.h>

int main() {
    long a;
    long long b;
    short c;

    printf("请输入一个long类型的数:");
    scanf("%ld", &a);

    printf("请输入一个long long类型的数:");
    scanf("%lld", &b);

    printf("请输入一个short类型的数:");
    scanf("%hd", &c);

    printf("你输入的long类型的数是:%ld\n", a);
    printf("你输入的long long类型的数是:%lld\n", b);
    printf("你输入的short类型的数是:%hd\n", c);

    return 0;
}

在这个代码中,%ld%lld%hd分别用于输入longlong longshort类型的数据。&符号是取地址运算符,它的作用是获取变量的内存地址。scanf函数需要这个地址来改变变量的值。注意,scanf函数的使用需要谨慎,因为它可能会导致缓冲区溢出等问题。在实际编程中,建议使用更安全的输入函数,如fgetssscanf


3.4 变量的作用域和生命周期

1)作用域:作用域(scope)是程序设计概念,通常来说,一段程序代码中所用到的名字并不总是有效/可用
的。而限定这个名字的可用性的代码范围就是这个名字的作用域。
(1)局部变量的作用域是变量所在的局部范围。比如:{int a = 10;},{}内就是局部变量a的作用域。
(2)全局变量的作用域是整个工程。比如:int b =100;int main(){};,在main()函数{}之外也不在其他范围内的变量。

2)生命周期:变量的生命周期指的是变量的创建到变量的销毁之间的一个时间段
(1)局部变量的生命周期是:进入作用域生命周期开始,出作用域生命周期结束。比如函数的形参。
(2)全局变量的生命周期是:整个程序的生命周期。程序结束,全局变量生命周期才结束。

3.5 常量

C语言中的常量和变量的定义的形式有所差异。

C语言中的常量分为以下以下几种:

1)字面常量

2)const 修饰的常变量 语法: const 变量类型

3)#define 定义的标识符常量 语法:#define 常量标识符 常量值

4)枚举常量 语法:enum 枚举常量名 {常量1, 常量2,…}; //这里记得加上分号!常量面前没有常量类型

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
//举例
enum Sex
{
	MALE,
	//MALE=2,  //可指定默认值,后面的值在2的基础上,递增+1
	FEMALE,
	SECRET
};  //这里要加上分号;
//括号中的MALE,FEMALE,SECRET是枚举常量


int main()
{
	//字面常量演示
	3.14;//字面常量
	1000;//字面常量

	//const 修饰的常变量
	const float pai = 3.14f; //这里的pai是const修饰的 常变量
	//pai = 5.14;//是不能直接修改的!vs2017会提示表达式必须是可修改的左值

	//#define的标识符常量 演示
	#define MAX 100  //这里没有分号”;“
	printf("max = %d\n", MAX);  // 100

	//枚举常量演示
	printf("%d\n", MALE);  // 0
	printf("%d\n", FEMALE);  // 1
	printf("%d\n", SECRET);  // 2
	//注:枚举常量的默认是从0开始,依次向下递增1的
	return 0;
}

注(常变量):
上面例子上的 pai 被称为 const 修饰的常变量, const 修饰的常变量在C语言中只是在语法层面限制了变量 pai 不能直接被改变,但是 pai 本质上还是一个变量的,所以叫常变量。

4. 字符串+转义字符

4.1 字符串

"hello bit.\n"

这种由双引号(Double Quote)引起来的一串字符称为字符串字面值(String Literal),或者简称字符串。
注:字符串的结束标志是一个 \0 的转义字符。在计算字符串长度的时候 \0 是结束标志,不算作字符串内容。

#include <stdio.h>
//下面代码,打印结果是什么?为什么?(突出'\0'的重要性)
int main()
{
	char arr1[] = "bit";  
	char arr2[] = { 'b', 'i', 't' };
	char arr3[] = { 'b', 'i', 't', '\0' };  
	printf("%s\n", arr1);  //bit  默认包含了'\0'
	printf("%d\n", sizeof(arr1)/sizeof(char)); //4, 但是在计算长度的时候是包括的!!!
	printf("%s\n", arr2);  //bit烫烫烫烫蘠it 随机值
	//printf("%d\n", sizeof(arr2) / sizeof(char));
	printf("%s\n", arr3);  //bit 显示写出'\0'
	printf("%d\n", sizeof(arr3) / sizeof(char));  // 4  

	return 0;
}
 

4.2 转义字符

加入我们要在屏幕上打印一个目录: c:\code\test.c
我们该如何写代码?

#include <stdio.h>
int main()
{
 printf("c:\code\test.c\n");
    return 0; }
12345

实际上程序运行的结果是这样的:
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
这里就不得不提一下转义字符了。转义字符顾名思义就是转变意思。
下面看一些转义字符。
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>
int main()
{
	//问题1:在屏幕上打印一个单引号',怎么做?
	//问题2:在屏幕上打印一个字符串,字符串的内容是一个双引号“,怎么做?
	printf("%c\n", '\'');  //注意:单引号''括起来的对应%c,双引号括起来的对应%s
	printf("%s\n", "\'"); 
	printf("%s\n", "\"");
	printf("%c\n", '\"');
	return 0;
}
12345678910111213
//程序输出什么?
#include <stdio.h>
int main()
{
	//strlen()求字符串长度
	printf("%d\n", strlen("abcdef"));  // 6
	// \62被解析成一个转义字符
	printf("%d\n", strlen("c:\test\628\test.c"));  //14 = 11 + 3个转义字符
	
	printf("%c\n", '\62');  //2  ???

	return 0;
}

    
    

4.3字符指针和字符数组 和字符串的区别

在C语言中,字符指针、字符数组和字符串是三个不同的概念,虽然它们在许多情况下可以互换使用,但它们之间还是存在一些关键的区别123456

字符指针是一个指针,它存储的是地址,而不是将字符串放到字符指针变量中123456。例如:

char *p = "Hello World!";

在这个例子中,p是一个字符指针,它指向的是字符串"Hello World!"的首地址123456

字符数组是一个存储字符的数组,其长度是固定的,其中任何一个数组元素都可以为 null 字符。因此,字符数组不一定是字符串123456。例如:

char cArr[] = {'H', 'e', 'l', 'l', 'o'};

在这个例子中,cArr是一个字符数组,它包含5个字符,但并没有以null字符(‘\0’)结束,所以它不是一个字符串123456

字符串,它必须以 null 字符结束,其后的字符不属于该字符串123456。字符串一定是字符数组,它是最后一个字符为 null 字符的字符数组123456。例如:

char sArr[] = "Hello";

在这个例子中,sArr是一个字符串,它包含5个字符和一个结束的null字符(‘\0’),所以它是一个字符串123456

需要注意的是,字符串是一个只读型字符型数组,不能够通过指针更改字符串内部数据2。而字符数组可以修改其内部的数据123456

总的来说,所有的字符串都是字符数组,但并非所有的字符数组都是字符串123456。这是因为字符串有一个额外的约束,即必须以null字符结束123456。这个约束使得字符串可以被一些特定的函数(如printfstrlen)用于特定的目的123456

进阶

1 字符函数和字符串函数

**1.1 **strlen (重要)

函数用于获取字符串的长度:

size_t strlen ( const char * str );
1

字符串已 ‘\0’ 作为结束标志,strlen函数返回的是在字符串中 ‘\0’ 前面出现的字符个数(*不包含 ‘\0’* )。
参数指向的字符串必须要以 ‘\0’ 结束。
没有’\0’,的出来的结果就是随机值
注意函数的返回值为size_t,是无符号的( 易错 )
学会strlen函数的模拟实现

#include <stdio.h>

int main()
{
	const char*str1 = "abcdef";
	const char*str2 = "bbb";
	if(strlen(str2)-strlen(str1)>0)  // 返回无符号的,就一直是>0
	{
		printf("str2>str1\n");
	} 
	else
	{
		printf("srt1>str2\n");
	}
	
	return 0; 
}
 

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这段代码中的问题在于 strlen 函数返回的是 size_t 类型,这是一个无符号整数类型。当你从一个较大的无符号数中减去一个较小的无符号数时,结果仍然是一个无符号数。在你的例子中,strlen(str2) 是3,strlen(str1) 是6,所以 strlen(str2) - strlen(str1) 的结果是一个非常大的无符号数,而不是一个负数。这就是为什么 if(strlen(str2)-strlen(str1)>0) 总是为真的原因。

为了避免这个问题,你可以先将 strlen 的结果存储在 int 类型的变量中,然后再进行比较,就像你在第二段代码中所做的那样。这样,b - a 的结果就会是一个有符号的整数,可以正确地表示负数。这就是为什么第二段代码能够正确地比较两个字符串的长度。希望这个解释对你有所帮助!


#include <stdio.h>

int main()
{
	const char*str1 = "abcdef";
	const char*str2 = "bbb";
	printf("%d\n", strlen(str1));  // 6
	printf("%d\n", strlen(str2));  // 3
	
	int a = strlen(str1);
	int b = strlen(str2);
	int c = b - a;
	if (c > 0)
	{
		printf("str2>str1\n");
	}
	else
	{
		printf("srt1>str2\n");
	}
	return 0;
}

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

1.2 strcpy

函数用于复制字符串:

char * strcpy ( char * destination, const char * source );
1

Copies the C string pointed by source into the array pointed by destination, including the terminating null character (and stopping at that point).
源字符串必须以 ‘\0’ 结束。
会将源字符串中的 ‘\0’ 拷贝到目标空间。
目标空间必须足够大,以确保能存放源字符串。
目标空间必须可变。

#include <stdio.h>
#include <string.h>

int main() {
    char src[40];
    char dest[12];
   
    memset(dest, '\0', sizeof(dest));
    strcpy(src, "This is tutorialspoint.com");
    strcpy(dest, src);

    printf("Final copied string : %s\n", dest);
   
    return(0);
}
Final copied string : This is tutorialspoint.com
1.3 strcat

strcat是C语言中的一个库函数,用于将一个字符串追加到另一个字符串的末尾12345。其函数原型为:

char *strcat (char *dest, const char *src);

其中,dest是目标字符串,src是要追加的字符串4

这个函数的工作原理是将src所指向的字符串追加到dest所指向的字符串的结尾13。因此,必须确保dest有足够的内存空间来容纳两个字符串,否则可能会导致溢出错误3

以下是strcat函数的一个使用示例1

#include <stdio.h>
#include <string.h>

int main () {
    char src[50], dest[50];

    strcpy(src, "This is source");
    strcpy(dest, "This is destination");

    strcat(dest, src);

    printf("Final destination string: |%s|", dest);

    return(0);
}

在这个示例中,src字符串被追加到dest字符串的末尾,然后打印出最终的dest字符串。这将产生以下结果:

Final destination string: |This is destinationThis is source|

需要注意的是,strcat函数不会覆盖目标字符串1。而且,srcdest字符串中都必须包含字符’\0’,并且src字符串必须以’\0’结尾,否则追加过程无法顺利实现2。此外,目标字符串空间必须足够大(足够容纳追加字符串src的内容),并且目标空间必须可修改(前面不能加const并且不能说常量字符串)2

字符串自己给自己追加,如何?会报错

/* strcat example */
#include <stdio.h>
#include <string.h>

int main ()
{
  char str[80];
  strcpy(str,"these ");
  strcat(str,"strings ");
  strcat(str,"are ");
  strcat(str,"concatenated.");
  puts (str);
  
  return 0;
}

these strings are concatenated.
1.4 strcmp

strcmp 函数可以用来比较两个 C 字符串。在 C 语言中,字符串通常表示为字符数组,并以空字符 ‘\0’ 结尾。所以,如果你有两个字符数组,并且它们都以 ‘\0’ 结尾,那么你可以使用 strcmp 来比较它们。

但是,需要注意的是,strcmp 函数并不会比较两个数组的所有元素,它只会比较到遇到第一个 ‘\0’ 字符为止。所以,如果你的数组中包含 ‘\0’ 字符,或者你想比较的是非字符类型的数组,那么 strcmp 可能就不适用了。

在这种情况下,你可能需要使用其他函数,如 memcmp,它可以比较任意类型的数组,只需要指定要比较的字节数。希望这个解释对你有所帮助!

比较字符数组

当然可以。以下是一个使用 strcmp 来比较两个字符数组的例子:

#include <stdio.h>
#include <string.h>

int main() {
    char str1[] = "Hello, world!";
    char str2[] = "Hello, world!";

    if (strcmp(str1, str2) == 0) {
        printf("The two strings are equal.\n");
    } else {
        printf("The two strings are not equal.\n");
    }

    return 0;
}

在这个例子中,我们定义了两个字符数组 str1str2,然后使用 strcmp 来比较这两个数组是否相等。如果 strcmp 返回 0,那么这两个字符串就是相等的。否则,它们就不相等。希望这个例子对你有所帮助!

The two strings are equal.
比较字符串

函数用于比较两个字符串:

int strcmp ( const char * str1, const char * str2 );
1

This function starts comparing the first character of each string. If they are equal to each other, it continues with the following pairs until the characters differ or until a terminating null-character is reached.

标准规定:
第一个字符串大于第二个字符串,则返回大于0的数字
第一个字符串等于第二个字符串,则返回0
第一个字符串小于第二个字符串,则返回小于0的数字
那么如何判断两个字符串?

#include <stdio.h>
int main()
{
	const char*str1 = "abcdef";
	const char*str2 = "abcfed";
	printf("%d\n", strlen(str1));  // 6
	printf("%d\n", strlen(str2));  // 3
	int a = strcmp(str2, str1);
	printf("%d\n", a);
	
	return 0;
}

6
6
1
#include <stdio.h>
#include <string.h>

int main() {
    char str1[15];
    char str2[15];
    int ret;

    strcpy(str1, "abcdef");
    strcpy(str2, "ABCDEF");

    ret = strcmp(str1, str2);

    if(ret < 0) {
        printf("str1 is less than str2\n");
    } else if(ret > 0) {
        printf("str2 is less than str1\n");
    } else {
        printf("str1 is equal to str2\n");
    }
   
    return(0);
}
str2 is less than str1
1.5 strncpy

函数用于复制指定数量的字符:

strncpy 是 C 语言中的一个内置函数,用于将一个字符串的前 n 个字符复制到另一个字符串1。它的声明如下:

char *strncpy(char *dest, const char *src, size_t n);

这里的参数是:

strncpy 函数的返回值是指向结果字符串 dest 的指针1

如果 src 的长度小于 n,那么 dest 的剩余部分将用空字符 ‘\0’ 填充1

以下是一个 strncpy 函数的使用示例:

#include <stdio.h>
#include <string.h>

int main() {
    char src[40];
    char dest[12];

    memset(dest, '\0', sizeof(dest));
    strcpy(src, "This is tutorialspoint.com");
    strncpy(dest, src, 10);

    printf("Final copied string : %s\n", dest);

    return 0;
}

在这个例子中,我们将 src 字符串的前 10 个字符复制到 dest 字符串1

Final copied string : This is tu
/* strncpy example */
#include <stdio.h>
#include <string.h>

int main ()
{
  char str1[]= "To be or not to be";
  char str2[40];
  char str3[40];

  /* copy to sized buffer (overflow safe): */
  strncpy( str2, str1, sizeof(str2) );

  /* partial copy (only 5 chars): */
  strncpy( str3, str2, 5 );
  str3[5] = '\0';   /* null character manually added */

  puts (str1);
  puts (str2);
  puts (str3);

  return 0;
}

 
To be or not to be
To be or not to be
To be
1.6 strncat (连接)

strncat 是 C 语言中的一个内置函数,用于将一个字符串的前 n 个字符追加到另一个字符串的末尾1。它的声明如下:

char *strncat(char *dest, const char *src, size_t n);

这里的参数是:

strncat 函数的返回值是指向结果字符串 dest 的指针1

以下是一个 strncat 函数的使用示例:

#include <stdio.h>
#include <string.h>

int main() {
    char src[50], dest[50];

    strcpy(src,  "This is source");
    strcpy(dest, "This is destination");

    strncat(dest, src, 15);
    printf("Final destination string : |%s|\n", dest);
   
    return 0;
}

在这个例子中,我们将 src 字符串的前 15 个字符追加到 dest 字符串的末尾1。希望这个解释对你有所帮助!

Final destination string : |This is destinationThis is source|
1.7 strncmp (比较)

strncmp 是 C 语言中的一个内置函数,用于比较两个字符串的前 n 个字符1。它的声明如下:

int strncmp(const char *str1, const char *str2, size_t n);

这里的参数是:

strncmp 函数的返回值取决于比较的结果1

这里的 “大于” 和 “小于” 是指在比较字符串时,按照字符的 ASCII 值进行比较1

以下是一个 strncmp 函数的使用示例:

#include <stdio.h>
#include <string.h>

int main() {
    char str1[15];
    char str2[15];
    int ret;

    strcpy(str1, "abcdef");
    strcpy(str2, "ABCDEF");

    ret = strncmp(str1, str2, 4);

    if(ret < 0) {
        printf("str1 is less than str2\n");
    } else if(ret > 0) {
        printf("str2 is less than str1\n");
    } else {
        printf("str1 is equal to str2\n");
    }

    return 0;
}

在这个例子中,我们比较了 str1str2 的前 4 个字符。如果 str1 小于 str2,我们就打印 “str1 is less than str2”,如果 str1 大于 str2,我们就打印 “str2 is less than str1”,否则我们就打印 "str1 is equal to str2"1

str2 is less than str1
1.8 strstr (查找)

函数用于在一个字符串中查找另一个字符串:

const char * strstr ( const char * str1, const char * str2 );
      char * strstr (       char * str1, const char * str2 );
12

Returns a pointer to the first occurrence of str2 in str1, or a null pointer if str2 is not part of str1.

/* strstr example */
#include <stdio.h>
#include <string.h>

int main ()
{
  char str[] ="This is a simple string";
  char * pch;
  pch = strstr (str, "simple");
  strncpy (pch, "sample", 6);
  puts (str);
  return 0;
}
 
This is a sample string
#include <stdio.h>
#include <string.h>

int main() {
    const char haystack[20] = "TutorialsPoint";
    const char needle[10] = "Point";
    char *ret;

    ret = strstr(haystack, needle);

    printf("The substring is: %s\n", ret);
   
    return(0);
}
The substring is: Point
1.9 strtok (分割)

函数用于分割字符串

char * strtok ( char * str, const char * sep );
1

sep参数是个字符串,定义了用作分隔符的字符集合
第一个参数指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标记。
strtok函数找到str中的下一个标记,并将其用 \0 结尾,返回一个指向这个标记的指针。(注:strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容并且可修改。)
strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串中的位置。
strtok函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标记。
如果字符串中不存在更多的标记,则返回 NULL 指针。

/* strtok example */
#include <stdio.h>
#include <string.h>

int main ()
{
	char str[] ="- This, a sample string.";
	char * pch;
	printf ("Splitting string \"%s\" into tokens:\n",str);
	pch = strtok (str," ,.-");
	while (pch != NULL)
	{
		printf ("%s\n",pch);
		pch = strtok (NULL, " ,.-");
	}
 
	return 0; 
}

Splitting string "- This, a sample string." into tokens:
This
a
sample
string

#include <stdio.h>
#include<cstring>
int main()
{
	const char *p = "zhangpengwei@bitedu.tech";
	const char* sep = ".@";
	char arr[30];
	char *str = NULL;
	strcpy(arr, p);//将数据拷贝一份,处理arr数组的内容
	for(str=strtok(arr, sep); str != NULL; str=strtok(NULL, sep))
	{
		printf("%s\n", str);
	}
}

zhangpengwei
bitedu
tech
#include <string.h>
#include <stdio.h>

int main() {
    char str[80] = "This is - www.tutorialspoint.com - website";
    const char s[2] = "-";
    char *token;
   
    /* 获取第一个子字符串 */
    token = strtok(str, s);
   
    /* 继续获取其他的子字符串 */
    while( token != NULL ) {
        printf( " %s\n", token );
    
        token = strtok(NULL, s);
    }
   
    return(0);
}
 This is
  www.tutorialspoint.com
  website
1.10 strerror
char * strerror ( int errnum );
1

返回错误码,所对应的错误信息。

/* strerror example : error list */
#include <stdio.h>
#include <string.h>
#include <errno.h>//必须包含的头文件

int main ()
{
	FILE * pFile;
	pFile = fopen ("unexist.ent","r");
	if (pFile == NULL)
	printf ("Error opening file unexist.ent: %s\n",strerror(errno));
	//errno: Last error number
	return 0; 
}

Error opening file unexist.ent: No such file or directory
字符分类函数:

函数 如果他的参数符合下列条件就返回真
iscntrl 任何控制字符
isspace 空白字符:空格‘ ’,换页‘\f’,换行’\n’,回车‘\r’,制表符’\t’或者垂直制表符’\v’
isdigit 十进制数字 0~9
isxdigit 十六进制数字,包括所有十进制数字,小写字母af,大写字母AF
islower 小写字母a~z
isupper 大写字母A~Z
isalpha 字母az或AZ
isalnum 字母或者数字,az,AZ,0~9
ispunct 标点符号,任何不属于数字或者字母的图形字符(可打印)
isgraph 任何图形字符
isprint 任何可打印字符,包括图形字符和空白字符

字符转换:
int tolower ( int c );
int toupper ( int c );

/* isupper example */
#include <stdio.h>
#include <ctype.h>
int main ()
{
	int i=0;
	char str[]="Test String.\n";
	char c;
	while (str[i])
	{
		c=str[i];
		if (isupper(c)) 
		    c=tolower(c);
		putchar (c);
		i++;
	}
	
	return 0;
}
 
test string.

在C语言中,字符串是由字符组成的数组,以空字符(‘\0’)结束。while(str[i]) 这个循环会一直执行,直到遇到字符串 str 的结束标志 ‘\0’。在ASCII编码中,‘\0’ 对应的整数值是0,所以当 str[i] 是 ‘\0’ 时,while(str[i]) 的条件就不满足,循环就会结束。这就是 while(str[i]) 的含义。希望这个解释对你有所帮助!

1.11 memcpy
void * memcpy ( void * destination, const void * source, size_t num );
1

函数memcpy从source的位置开始向后复制num个字节的数据到destination的内存位置。
这个函数在遇到 ‘\0’ 的时候并不会停下来。

memcpy 拷贝字符串和数组
/* memcpy example */
#include <stdio.h>
#include <string.h>

struct {
	char name[40];
	int age;
} person, person_copy;

int main ()
{
	char myname[] = "Pierre de Fermat";
	/* using memcpy to copy string: */
	memcpy ( person.name, myname, strlen(myname)+1 );
	person.age = 46;
	
	/* using memcpy to copy structure: */
	memcpy ( &person_copy, &person, sizeof(person) );
	printf ("person_copy: %s, %d \n", person_copy.name, person_copy.age );
	
	return 0; 
}
 
注意事项
不适用于重叠复制

如果source和destination有任何的重叠,复制的结果都是未定义的

#include <stdio.h>
#include <string.h>

int main() {
    char str[] = "Hello, world!";
    
    // 尝试将 str 的前 5 个字符复制到 str 的第 3 个字符开始的位置
    memcpy(&str[2], str, 5);
    printf("%s\n", str);
    
    return 0;
}

在这个例子中,我们尝试将 str 的前 5 个字符(“Hello”)复制到 str 的第 3 个字符开始的位置。由于源地址和目标地址有重叠,所以复制的结果可能会出现错误。

当你运行这段代码时,可能会发现输出的字符串并不是你期望的 “HeHello, world!”,而是 “HeHeHo, world!”。这就是因为在复制过程中,源地址和目标地址的重叠部分被提前覆盖了。

为了避免这种情况,你可以使用 memmove 函数来代替 memcpymemmove 函数会正确处理源地址和目标地址的重叠,即使源地址和目标地址有重叠,它也能正确地复制数据。希望这个解释对你有所帮助!


为什么需要使用 memmove

“如果source和destination有任何的重叠,复制的结果都是未定义的”,这句话的意思是,如果你在使用 memcpy 函数时,源地址和目标地址有重叠的部分,那么复制的结果可能会出现错误。

这是因为 memcpy 在复制数据时,可能会先复制源地址的开始部分到目标地址,然后再复制源地址的结束部分。如果源地址和目标地址有重叠,那么在复制源地址的结束部分时,可能会覆盖已经复制到目标地址的数据,导致数据错误。

为了避免这种情况,你可以使用 memmove 函数来代替 memcpymemmove 函数会检查源地址和目标地址是否有重叠,如果有,它会从源地址的结束部分开始复制,这样就可以避免数据错误。希望这个解释对你有所帮助!


必须复制源字符串的 ‘\0’

需要注意的是,当使用 memcpy 复制字符串时,需要确保目标字符串有足够的空间来存储源字符串,并且在复制时要包括源字符串的结束符 ‘\0’。

在C语言中,字符串是由字符数组表示的,这个数组以特殊的字符 ‘\0’ 结束。‘\0’ 是一个空字符,用于标记字符串的结束。当我们使用字符串处理函数(如 printfstrcpy 等)时,这些函数会依赖 ‘\0’ 来确定字符串何时结束。

因此,当我们使用 memcpy 复制字符串时,我们需要包括源字符串的 ‘\0’ 结束符,以确保目标字符串也有一个正确的结束标记。如果不复制 ‘\0’,那么目标字符串可能就没有正确的结束标记,这可能会导致未定义的行为,比如打印字符串时打印出预期之外的字符,或者在处理字符串时访问到不应该访问的内存区域。

所以,为了确保字符串的正确处理,我们在复制字符串时通常会包括 ‘\0’ 结束符。希望这个解释对你有所帮助!

1.12 memmove

memmove是C语言中的一个库函数,用于在内存中移动数据1234。其函数原型为:

void *memmove (void *dest, const void *src, size_t n);

其中,dest是目标内存区域,src是源内存区域,n是要复制的字节数1234

这个函数的工作原理是将src所指向的内存区域的n个字节复制到dest所指向的内存区域1234。如果目标区域和源区域有重叠的话,memmove能够保证源串在被覆盖之前将重叠区域的字节拷贝到目标区域中1234。复制后源区域的内容会被更改1。如果目标区域与源区域没有重叠,则和memcpy函数功能相同1

以下是memmove函数的一个使用示例1

#include <stdio.h>
#include <string.h>

int main () {
    const char src[] = "newstring";
    char dest[] = "oldstring";

    printf("Before memmove dest = %s, src = %s\n", dest, src);
    memmove(dest, src, 9);
    printf("After memmove dest = %s, src = %s\n", dest, src);

    return 0;
}

在这个示例中,src字符串被复制到dest字符串的位置,然后打印出最终的dest字符串。这将产生以下结果:

Before memmove dest = oldstring, src = newstring
After memmove dest = newstring, src = newstring

需要注意的是,memmove函数不会覆盖目标字符串1。此外,目标字符串空间必须足够大(足够容纳追加字符串src的内容),并且目标空间必须可修改(前面不能加const并且不能说常量字符串)2

和memcpy的差别 : 就是memmove函数处理的源内存块和目标内存块是可以重叠的。
如果源空间和目标空间出现重叠,就得使用memmove函数处理。

1.13 memcmp
int memcmp ( const void * ptr1, const void * ptr2, size_t num );
1

比较从ptr1和ptr2指针开始的num个字节
返回值如下:
<0 the first byte that does not match in both memory blocks has a lower value in ptr1 than in ptr2 (if evaluated as unsigned char values)

0 the contents of both memory blocks are equal

0 the first byte that does not match in both memory blocks has a greater value in ptr1 than in ptr2 (if evaluated as unsigned char values)

/* memcmp example */
#include <stdio.h>
#include <string.h>

int main()
{
	char buffer1[] = "DWgaOtP12df0";
	char buffer2[] = "DWGAOTP12DF0";
	int n;
	
	n = memcmp(buffer1, buffer2, sizeof(buffer1));
	if (n > 0) 
	{
		printf("'%s' is greater than '%s'.\n", buffer1, buffer2);
	}
	else if (n < 0) 
	{
		printf("'%s' is less than '%s'.\n", buffer1, buffer2);
	}
	else 
	{
		printf("'%s' is the same as '%s'.\n", buffer1, buffer2);
	}
	
	return 0;
}
 
memcmp 比较数组
#include <stdio.h>
#include <string.h>

int main() {
    int arr1[] = {1, 2, 3, 4, 5};
    int arr2[] = {1, 2, 3, 4, 5};

    if (memcmp(arr1, arr2, sizeof(arr1)) == 0) {
        printf("The two arrays are equal.\n");
    } else {
        printf("The two arrays are not equal.\n");
    }

    return 0;
}
memcmp 比较结构体
#include <stdio.h>
#include <string.h>

typedef struct {
    int id;
    char name[50];
} Person;

int main() {
    Person p1 = {123, "Alice"};
    Person p2 = {123, "Alice"};

    if (memcmp(&p1, &p2, sizeof(Person)) == 0) {
        printf("The two persons are equal.\n");
    } else {
        printf("The two persons are not equal.\n");
    }

    return 0;
}
strcmp 和 memcmp 的区别

strcmpmemcmp 是两个用于比较数据的 C 语言函数,但它们的用途和行为有所不同1

总的来说,strcmpmemcmp 的主要区别在于,strcmp 是用于比较字符串的,而 memcmp 是用于比较任意类型的数据的1。希望这个解释对你有所帮助!

2. 库函数的模拟实现 (暂略)

2.1 模拟实现strlen

方式1:

//计数器方式
int my_strlen(const char * str) 
{
	int count = 0;
	while(*str)
	{
		count++;
		str++;
	}
	
	return count; 
}

方式2:

//不能创建临时变量计数器
int my_strlen(const char * str) 
{
	if(*str == '\0')
		return 0;
	else
		return 1+my_strlen(str+1);//递归
}

方式3:

//指针-指针的方式
int my_strlen(char *s) 
{
     char *p = s;
     while(*p != ‘\0)
            p++;
     return p-s; 
}

2.2 模拟实现strcpy

参考代码:

//1.参数顺序
//2.函数的功能,停止条件
//3.assert
//4.const修饰指针
//5.函数返回值
//6.题目出自《高质量C/C++编程》书籍最后的试题部分

char *my_strcpy(char *dest, const char*src)
{ 
	 char *ret = dest;//这里传进来的是字符串的首地址,即指针
	 assert(dest != NULL);
	 assert(src != NULL);
 
	 while((*dest++ = *src++))
	 {
	 	;
	 }
	 return ret; 
}

2.3 模拟实现strcat

参考代码:

char *my_strcat(char *dest, const char*src) 
{
	char *ret = dest;//同上,记住 不能返回局部变量的地址
	assert(dest != NULL);
	assert(src != NULL);
	while(*dest)
	{
		dest++;
	}
	while((*dest++ = *src++))
	{
	 ;
	}
	return ret; 
}

2.4 模拟实现strstr
char *strstr (const char * str1, const char * str2) 
{
	char *cp = (char *) str1;
	char *s1, *s2;
	if ( !*str2 ){
		return((char *)str1);
		}
	while (*cp)
	{
		s1 = cp;
		s2 = (char *) str2;
		while ( *s1 && *s2 && !(*s1-*s2) ){
		       s1++, s2++;
		      }
		if (!*s2)
		       return(cp);
		cp++;
	}
	
	return(NULL);
}


/stackoverflow.com/questions/13095513/what-is-the-difference-between-memcmp-strcmp-and-strncmp-in-c)1。希望这个解释对你有所帮助!

2. 库函数的模拟实现 (暂略)

2.1 模拟实现strlen

方式1:

//计数器方式
int my_strlen(const char * str) 
{
	int count = 0;
	while(*str)
	{
		count++;
		str++;
	}
	
	return count; 
}

方式2:

//不能创建临时变量计数器
int my_strlen(const char * str) 
{
	if(*str == '\0')
		return 0;
	else
		return 1+my_strlen(str+1);//递归
}

方式3:

//指针-指针的方式
int my_strlen(char *s) 
{
     char *p = s;
     while(*p != ‘\0)
            p++;
     return p-s; 
}

2.2 模拟实现strcpy

参考代码:

//1.参数顺序
//2.函数的功能,停止条件
//3.assert
//4.const修饰指针
//5.函数返回值
//6.题目出自《高质量C/C++编程》书籍最后的试题部分

char *my_strcpy(char *dest, const char*src)
{ 
	 char *ret = dest;//这里传进来的是字符串的首地址,即指针
	 assert(dest != NULL);
	 assert(src != NULL);
 
	 while((*dest++ = *src++))
	 {
	 	;
	 }
	 return ret; 
}

2.3 模拟实现strcat

参考代码:

char *my_strcat(char *dest, const char*src) 
{
	char *ret = dest;//同上,记住 不能返回局部变量的地址
	assert(dest != NULL);
	assert(src != NULL);
	while(*dest)
	{
		dest++;
	}
	while((*dest++ = *src++))
	{
	 ;
	}
	return ret; 
}

2.4 模拟实现strstr
char *strstr (const char * str1, const char * str2) 
{
	char *cp = (char *) str1;
	char *s1, *s2;
	if ( !*str2 ){
		return((char *)str1);
		}
	while (*cp)
	{
		s1 = cp;
		s2 = (char *) str2;
		while ( *s1 && *s2 && !(*s1-*s2) ){
		       s1++, s2++;
		      }
		if (!*s2)
		       return(cp);
		cp++;
	}
	
	return(NULL);
}