指针

1、概述

1.1、内存
  • 存储器:用来存储程序,辅助CPU进行运算处理的重要部分
  • 内存:内部存储器,暂存程序、数据,掉电丢失,SRAM,DRAM,DDR,DDR2,DDR3……
  • 外存:外部存储器,长时间保存程序、数据,掉电不丢,ROM,ERROM,FLASH,硬盘,光盘……

内存是沟通CPU与硬盘的桥梁:

  • 暂存方CPU的运算数据;
  • 暂存与硬盘等外部存储器交换的数据。
1.2、物理存储器和存储地址空间

存储地址空间:对存储器编码的范围

  • 编码:对每个物理存储单元(一个字节)分配一个编号;
  • 寻址:可以根据分配的号码找到相应的存储单元,完成数据的读写。
1.2、内存地址
  • 将内存抽象成一个很大的一维字符数组;
  • 编码就是对内存的每一个字节分配一个32位或64位的编号;
  • 这个内存编号我们称为内存地址;
  • char:占一个字节,分配一个地址;
  • int:占四个字节分配四个地址,通常使用首地址;
  • float,struct,函数,数组等。
//int a=10;
int a =0xaabbccdd;

printf("%p\n",&a);
getchar();
  • 在 Windows 中,进行数据存储的时候,采用小端对齐
int a=10;
//定义指针变量存储地址
int* p;
p=&a;

printf("%p\n",&a);
printf("%p\n",p);

// 通过指针 p 也可以间接改变变量 a 的值
*p=100;



//指针在内存中大小
//指针的大小:4字节(32位),8字节(64位)
printf("%d\n",sizeof(int*));

//指针类型应该和数据类型对应
char ch='a';
char* p1=&ch;

//指针大小:4字节(32位),8字节(64位)
printf("%d\n",sizeof(p1));

  • 通过变量 a 可以改变值,通过指针 p 也可以间接改变变量的值;
  • 指针类型:数据类型*
  • 指针类型应该和数据类型对应
  • &是取地址符号,是升维度的;*是取值符号,是降维度的
1.3、指针大小
  • 所有的指针类型存储的都是内存地址
  • 指针大小:4字节(32位),8字节(64位)
char ch='a';
int* p=&ch;
//上面的写法没问题,&ch和p存储的都是 ch 的地址
//但是此时 *p 无法取到 ch 的值
//也无法通过*p来修改值
*p=123456;

//在定义指针的时候,滋指针类型一定要和变量类型对应上
//例如,通过智者间接地改变变量的值,int* 指针是改变四个字节内存储的内容,而 char* 指针改变的是一个字节内存储的内容,会出错
1.4、野指针和空指针
//野指针:指针变量指向一个未知的空间(未定义的空间)
int *p=10;		//内存地址编号为100的内存地址赋值给 p
printf("%d\n",*p);		//这里会报错,可能是这个内存无法被访问
  • 野指针:指针变量指向一个未知的空间(未定义的空间)
  • 任意数值赋值给指针变量是没有意义的,这样的指针会成为野指针,因为这样的指针指向的内存是未知的
  • 操作野指针对应的内存空间可能报错
  • 操作系统将编号为 0-255 的内存作为系统占用,不允许访问操作
  • 程序中允许存在野指针,但是操作野指针可能报错
  • 不建议将一个变量的值直接赋值给指针
  • C语言中,可以把 NULL 赋值给指针,标志此指针为空指针,不指向任何空间
int* p=NULL;
//此时无法对p进行读写操作


//NULL 是一个值为 0 的宏常量
#define NULL ((void*)0)
  • 空指针是指内存地址编号为 0 的内存空间
  • 操作空指针对应的空间一定会报错
  • 空指针可以用作条件判断
1.5、万能指针 void*
  • void* 指针可以指向任意类型变量的内存空间
void* p=NULL;

int a=10;
p=(void*)&a;	//指向变量时,最好转换为 void*

//使用指针变量指向的内存时,转换为 int*
*((int*)p)=11;
  • 万能指针可以接收任意类型变量的内存地址
  • 在通过万能指针修改变量的值的时候,需要将其转换为变量类型对应的指针类型才可以进行修改
1.5、const 修饰的指针类型
//常量
const int a=10;
a=100;		//在这里修改不了

int *p=&a;
*p=100;		//但是可以通过指针间接修改常量的值

1)const 修饰指针类型(只读指针)

  • 可以修改指针变量 p 的值
  • 不可以修改指针指向内存空间的值
int  a=10;
int b=20;
// const 修饰指针类型
const int* p=&a;

p=&b;		//可以修改指针变量 p 的值

*p=100;		//err,不可以修改指针指向内存空间的值

2)const 修饰指针变量

  • 不可以修改指针变量 p 的值
  • 可以修改指针指向内存空间的值
int  a=10;
int b=20;
//const 修饰指针变量的值
int* const p=&a;

p=&b;		//err,不可以修改指针变量 p 的值
*p=200;		//可以修改指针指向内存空间的值

3)const 修饰指针类型和指针变量

int a=10;
int b=20;
//const 修饰指针类型和指针变量
const int* const p=&a;

p=&b;		//err,不可以修改指针变量 p 的值
*p=100//err,不可以修改指针指向内存空间的值
    
//二级指针的操作
int** pp=&p;
//*pp是一级指针的值
*pp=&b;		//这样就可以修改指针变量p的值了

**pp==100;		//这样就可以修改变量的值了(指针指向内存空间的值)
  • 不可以修改指针变量 p 的值
  • 不可以修改指针指向内存空间的值
  • 但是,可以通过二级指针对指针变量 p 和指针指向内存空间的值进行修改

2、指针和数组

2.1、数组名
  • 数组名是数组的首元素地址,是一个常量。
//指针和数组
int arr[]={1,2,3,4,5,6,7,8,'a','b','c'};

arr=100;	//err,数组名是一个常量,不允许赋值

//数组名数数组首元素地址
int* p;		//指向数组的指针
p=arr;
printf("%p\n",p);
printf("%p\n",arr);

for(int i=0;i<10;i++)
{
    //下面几种写法等价,都可以达到访问数组元素的目的
    printf("%d\n",arr[i]);
    printf("%d\n",p[i]);
    printf("%p\n",*(arr+i));
    printf("%p\n",*(p+i));
    printf("%p\n",*p);
}

//两个指针相减,得到的结果是两个指针的偏移量
int step=p-arr;
printf("%d\n",step);		// 10

printf("%d\n",sizeof(p));	//4
printf("%d\n",sizeof(arr));		//40
2.2、指针加减运算
  • 指针变量 +1,等同于内存地址+sizeof(数据类型)

  • 两个指针相减,得到的结果是两个指针的偏移量

  • 所有类型的指针相减,结果都是整型数据,表示偏移的该类型的数据的个数

  • 指向数组的指针操作数组:

    • 方法一:p[i]
    • 方法二:*(p+i)
  • 指向数组的指针 p 和数组名的区别:

    • p 是变量,arr 是常量
    • 在上面的代码中:sizeof§=4,sizeof(arr)=40

数组在作为函数参数时,会退化为指针,丢失了数组的精度(数组的元素个数信息)

void BubbleSort(int *arr,int len)
{
	for(int i=0;i<len-1;i++)
    {
        for(int j=0;j<len-1-i;j++)
        {
            if(*(arr+j)>*(arr+j+1))
            {
                int temp=*(arr+j);
                *(arr+j)=*(arr+j+1);
                *(arr+j+1)=temp;
            }
        }
    }
}

1)字符串拷贝:

//下面几个 函数功能相同
void myStrCopy01(char* dest,char* ch)
{
    int i=0;
    while(ch[i])
    {
        dest[i]=ch[i];
        i++;
    }
    dest[i]=0;
}

void meStrCopy02(char* dest,char* ch)
{
    int i=0;
    while(*(ch+i))
    {
        *(dest+i)=*(ch+i);
        i++;
    }
    *(dest+i)=0;
}

void myStrCopy03(char* dest,char* ch)
{
    while(*ch)
    {
        *dest=*ch;
    	dest++;		//指针 +1,相当于指向数组下一个元素,内存地址变化了sizeof(数据类型)
    	ch++;
    }
    *dest=0;
}

void myStrCopy04(char* dest,char* ch)
{
    while(*dest++=*ch++);		
}

//字符串拷贝
char ch[]="hello world";
char dest[100];
myStrCopy(dest,ch);
printf("%s\n",dest);

2)指针加减:

int arr[]={1,2,3,4,5,6,7,8,9,10};
int *p=arr;

arr[-1];	//err,数组下标越界
p=&arr[3];

//指针操作数组时,允许下标为负数
printf("%d\n",p[-2]);		//相当于是 *(p-2)

/*
p--;		//指针的加减运算和指针的类型有关
p--;
p--;		//此时p和arr代表的地址一样了
*/

int step=p-arr;		//step=3

3)指针和运算符的操作

  • 乘、除、取余不能用于指针的运算
  • 指针指针不可以直接相加
  • 指针之间可以进行比较运算:>, >=, <, <=, ==, !=
  • 指针可以进行逻辑判断:逻辑与、逻辑或
int arr[]={1,2,3,4,5,6,7,8,9,10};
int *p=arr;

p=&arr[3];
if(p>arr)
{
    printf("真\n");
}

if(p&&arr)
{
    
}

p=p+arr;	//err,两个指针相加结果为野指针
p=p*arr;	//err
p=p*4;		//err
p=p/arr;	//err

3、指针数组

  • 指针数组,是数组,数组的每个元素都是指针类型
  • 数组指针,指向数组的指针(实际上应该没有这种提法)
  • 指针数组对应于二级指针
int a=10;
int b=20;
int c=30;

//定义指针数组
int* arr[3]={&a,&b,&c};
for(int i=0;i<sizeof(arr)/sizeof(arr[0]);i++)
{
    printf("%d\n",*arr[i]);
}

//sizeof(arr)	12
//sizeof(arr[0])	4

//指针数组里面的元素存储的都是指针
int a[]={1,2,3};
int b[]={4,5,6};
int c[]={7,8,9};

int* arr[]={a,b,c};

for(int i=0;i<3;i++)
{
    printf("%d\n",*arr[i]);		//输出的是:1	4	7
}

//下面三个语句的输出结果是一样的
printf("%p\n",arr[0]);
printf("%p\n",a);
printf("%p\n",&a[0]);


//指针数组是一个特殊的二维数组模型
printf("%d\n",arr[0][1]);		//输出的是a[1], 也就是2


for(int i=0;i<sizeof(arr)/sizeof(arr[0]);i++)
{
    for(int j=0;j<3;j++)
    {
        //二维数组形式打印出来
        printf("%d\n",arr[i][j]);
        
        //指针偏移量形式打印出来
        printf("%d\n",*(arr[i]+j));
        
        //arr是指针数组的首地址
        printf("%d\n",*(*(arr+i)+j));		//指针数组对应于二级指针
    }
}


4、多级指针

  • 一级指针最常用
  • 二级指针就是指向一个一级指针变量地址的指针
  • 三级指针使用的比较少
//指针数组和二级指针建立关系
int a[]={1,2,3};
int b[]={4,5,6};
int c[]={7,8,9};

int* arr[]={a,b,c};

int** p=arr;

printf("%d\n",**p);		//1
//二级指针加偏移量,相当于跳过了一个一维数组
printf("%d\n",**(p+1));		//4
//一级指针加偏移量,相当于跳过了一个元素
printf("%d\n",*(*p+1));		//2

printf("%d\n",*(*(p+1)+1));		//2

for(int i=0;i<3;i++)
{
    for(int j=0;j<3;j++)
    {
        //下面单个作用是一样的
        printf("%d ",p[i][j]);
        printf("%d ",*(p[i]+j));
        printf("%d ",*(*(p+i)+j));
    }
    puts("");
}


int a=10;
int b=20;
int* p=&a;
int** pp=&p;
//pp 二级指针变量的值
//*pp 一级指针的值
//**pp 变量的值

// pp=100;		野指针
*pp=&b;		//相当于 p=&b,间接改变了 p 的值
printf("%d\n",*p);		//20

**pp=100;		//间接改变 b 的值
*pp=100;	//err 野指针

//三级指针
int*** ppp=&pp;		//*ppp==pp=&p		//**ppp==*pp==p==&a		***ppp==**pp==*p==a



5、指针和函数

5.1、值传递和引用传递

1)值传递

void swap(int a,int b)
{
    int temp=a;
    a=b;
    b=temp;
}

int a=10;
int b=20;
//值传递,形参不影响喜实参的值
swap(a,b);
printf("%d %d\n",a,b);		//10 20

2)指针作为函数参数(地址传递)

//指针作为函数参数
void swap(int* a,int* b)
{
    int temp=*a;
    *a=*b;
    *b=temp;
}

int a=10;
int b=20;
//地址传递,形参可以改变实参的值
swap(&a,&b);
printf("%d %d\n",a,b);		//20 10

5.2、数组名作为函数参数
  • 数组名作函数参数,函数的形参会退化为指针

1)数组名作函数参数(数组写法)

void myStrCat(char* ch1,char* ch2)
{
    int i=0;
    while(ch1[i++]);		//相当于i=strlen(ch1)
    int j=0;
    while(ch2[j])
    {
        ch1[i+j]=ch2[j];
        j++;
    }
}

char ch1[100]="Hello";
char ch2[]="world";
myStrCat(ch1,ch2);

2)数组名作函数参数(指针写法)

void myStrCat(char* ch1,char* ch2)
{
    int i=0;
    while(*(ch1+i)!='\0')		//相当于i=strlen(ch1)
    {
        i++
    }
    int j=0;
    while(*(ch2+j))
    {
        *(ch1+i+j)=*(ch2+j);
        j++;
    }
}

char ch1[100]="Hello";
char ch2[]="world";
myStrCat(ch1,ch2);

3)数组名作函数参数(指针写法)

void myStrCat(char* ch1,char* ch2)
{
    while(*ch1)		//相当于i=strlen(ch1)
    {
        ch1++;
    }
    while(ch2)
    {
       *ch1=*ch2;
        ch1++;
        ch2++;
    }
}

//myStrCat1作用和myStrCat一样
void myStrCat1(char* ch1,char* ch2)
{
    while(*ch1) ch1++;
    while(*ch1++=*ch2++);
}

char ch1[100]="Hello";
char ch2[]="world";
myStrCat(ch1,ch2);
5.3、字符串去空格

1)使用辅助空间

//去字符串空格
void removeSpace(char* ch)
{
    char str[100]={0};
    char* temp=str;
    int i=0;
    int j=0;
    while(ch[i])
    {
        if(ch[i]!=' ')
        {
            str[j]=str[i];
            j++
        }
        i++;
    }
    while(*ch++==*temp++);
}

char ch[]="h  e ll o   w";
removeSpace(ch);

2)不使用辅助空间

//去字符串空格
void removeSpace(char* ch)
{
    int i=0;
    int j=0;
    while(ch[i])
    {
        if(ch[i]!=' ')
        {
            ch[j]=ch[i];
            j++;
        }
        i++;
    }
    ch[j]=0;
}

char ch[]="h  e ll o   w";
removeSpace(ch);
5.4、指针作为函数的返回值

1)数组形式

char* myChr(char* str,char ch)
{
    int i=0;
    while(str[i])
    {
        if(str[i]==ch)
        {
            return &str[i];
        }
        i++;
    }
    return NULL;
}

char ch[]="hello world";
char* res=myChr(ch,l);
if(!res)
    printf("未找到\n");

2)指针形式

char* myChr(char* str,char ch)
{
    while(*str)
    {
        if(*str==ch)
        {
            return &str;
        }
        str++;
    }
    return NULL;
}

char ch[]="hello world";
char* res=myChr(ch,l);
if(!res)
    printf("未找到\n");
else
    printf("%s\n",res);
5.5、字符串查找字符串
char* myStrStr(char* src,char* dest)
{
    char* fsrc=src;		//遍历源字符串的指针
    char* rsrc=src;		//记录每次相同字符串首地址
    char* tdest=dest;
    while(*fsrc)
    {
        rsrc=fsrc;
        while(*fsrc=*tdest&&*fsrc!='\0')
        {
            fsrc++;
            tdest++;
        }
        if(*tdest=='\0')
            return rsrc;
        else		//回滚
        {
            //目标字符串回滚
            tdest=dest;
            fsrc=rsrc;
            fsrc++;
        }
    }
    return NULL;
}

char src[]="hello world";
char dest[]="llo";
char* res=myChr(src,dest);
if(res==NULL)
{
    printf("未找到\n");
}
else
{
    printf("%s\n",res);
}

6、指针和字符串

6.1、字符指针

1)常量字符串

char ch[]="hello world";
char* p=ch;
printf("%s\n",p);

// char* p="hello world";		也是合法的,可以把p当做字符数组使用
/* 但是他们存在不同之处:
1)字符数组可以修改数组元素
2)无法通过 p[i]='m' 这样的方式修改内容
*/

char* p="hello world";
char* p1="hello world";		//p 和 p1 是相同的地址

  • char p=“hello world”; 也是合法的,可以把p当做字符数组使用*
    但是他们存在不同之处:
    1)字符数组可以修改数组元素
    2)无法通过 p[i]=‘m’ 这样的方式修改内容

    3)char ch[]=“hello world”,是栈区字符串

    ​ char* p=“hello world”,是数据区常量字符串,只读的

2)字符串数组

//字符串数组
//指针数组
char ch1[]="hello";
char ch2[]="world";
char ch3[]="dabaobeier";
char* arr1[]={ch1,ch2,ch3};		

char* arr2[]={"hello","world","dabaobeier"};		//字符串数组,内容不可修改
for(int i=0;i<3;i++)
{
    printf("%s\n",arr2[i]);
}

//字符串排序
for(int i=0;i<3-1;i++)
{
    for(int j=0;j<3-1-i;j++)
    {
        if(arr2[j][0]>arr[j+1][0])		//首字符进行比较
        {
            //交换字符串
            char* temp=arr2[j];
            arr2[j]=arr2[j+1];
            arr2[j+1]=temp;
        }
    }
}

for(int i=0;i<3;i++)
{
    printf("%s\n",arr2[i]);
}
6.2、字符指针作为函数参数
  • 当作为函数的指针所指向的内容在函数内不需要被修改时,可以使用 const 修饰指针参数:

    int myStrLen(const char ch)*

//数组实现
int myStrLen(char* ch)
{
    int i=0;
    while(ch[i])
        i++return i;
}

//指针实现
int myStrLen(const char* ch)	//在函数中,不希望指针指向的内容被修改,若被修改会报错,从而保护不希望被修改的参数
{
    char* temp=ch;
    while(*temp)	temp++;
    return temp-ch;
}

char ch[]="hello world";
int lengthCh=myStrLen(ch);		//11
6.3、const 修饰的指针变量
  • const 修饰的总是离它最近的内容

1)通过一级指针修改变量的值

const int a=10;
a=100;		//err
int* p=&a;
*p=100;		//通过一级指针修改变量的值

2)指向常量的指针

  • 指向常量的指针,可以修改指针变量的值,不能修改指针变量指向内存空间的值
char ch1[]="hello";
char ch2[]="world";

const char* p=ch1;

*p='m';		//err
p[2]='m';	//err,等同于 *(p+2)=m
p=ch2;		//没问题

3)常量指针

  • 常量指针,可以修改指针变量指向内存空间的值,不可以修改针变量的值
char ch1[]="hello";
char ch2[]="world";

//常量指针
char* const p=ch1;

p=ch2;	//err,常量指针不可以修改
p[2]='m';	//没问题
*(p+2)='m';		//没问题

4)只读指针

  • 只读指针,不可以修改指针变量指向内存空间的值,不可以修改针变量的值
  • 只读指针,可以通过二级指针修改只读指针指向的内存空间的内容
  • 只读指针,可以通过二级指针修改只读指针指向的内存空间
  • 由此可见,通过 const 修饰也并不是安全的,可以通过高级指针进行修改
char ch1[]="hello";
char ch2[]="world";

const char* const p=ch1;
p=ch2;		//err
*p='m';		//err
p[2]="m";	//err

char** pp=&p;
*(*pp+1)='m';	//可以通过二级指针修改只读指针指向的内存空间的内容
*pp=ch2;	//可以通过二级指针修改只读指针指向的内存空间

//由此可见,通过 const 修饰也并不是安全的,可以通过高级指针进行修改
6.4、主函数参数
int main(int argc,char* argv[]);
  • main 函数是由操作系统调用的,第一个参数 argc 标明 argv 数组的成员数量,argv 数组的每个成员都是 char* 类型;
  • argv 是命令行参数的字符串数组
  • argc 代表命令行参数的数量,程序名字本身算一个参数。
//主函数的形参
//gcc -o hello.out hello.c	4个参数:gcc,-o,hello.out,hello.c 
//int argc	传递参数的个数
//char* argv[]	参数:char* argv[]={"gcc","-o","hello.out","hello.c"},表示参数的具体内容
int main(int argc,char* argv[])
{
    //参数个数判断
    if(argc<3)
    {
        printf("缺少参数\n");	//在命令行运行程序时,若参数个数不一致,会报错
        return -1;
    }
	for(int i=0;i<argc;i++)
    {
        printf("%s\n",argv[i]);			
    }
    return 0;
}
6.5、字符串中某个字符串出现的次数

1)while 模型

char* myStrStr(char* src,char* dest)
{
    char* fsrc=src;		//遍历源字符串的指针
    char* rsrc=src;		//记录每次相同字符串首地址
    char* tdest=dest;
    while(*fsrc)
    {
        rsrc=fsrc;
        while(*fsrc=*tdest&&*fsrc!='\0')
        {
            fsrc++;
            tdest++;
        }
        if(*tdest=='\0')
            return rsrc;
        else		//回滚
        {
            //目标字符串回滚
            tdest=dest;
            fsrc=rsrc;
            fsrc++;
        }
    }
    return NULL;
}

char *str="hello world";
char ch[]="llo";
char* p=myStrStr(str,ch);
int cnt=0;		//记录出现的次数
while(p)
{
    cut++;
    p=p+strlen(ch);
    p=myStrStr(p,ch);
}

printf("%s 在 %s 中出现的次数:%d\n",ch,str,cnt);

2)do-while 模型

char* myStrStr(char* src,char* dest)
{
    char* fsrc=src;		//遍历源字符串的指针
    char* rsrc=src;		//记录每次相同字符串首地址
    char* tdest=dest;
    while(*fsrc)
    {
        rsrc=fsrc;
        while(*fsrc=*tdest&&*fsrc!='\0')
        {
            fsrc++;
            tdest++;
        }
        if(*tdest=='\0')
            return rsrc;
        else		//回滚
        {
            //目标字符串回滚
            tdest=dest;
            fsrc=rsrc;
            fsrc++;
        }
    }
    return NULL;
}

char *str="hello world";
char ch[]="llo";
char* p=myStrStr(str,ch);
int cnt=0;		//记录出现的次数
do
{
    if(p)
    {
        cut++;
        p+=strlen(ch);
        p=myStrStr(p,ch);
    }
}while(p);

printf("%s 在 %s 中出现的次数:%d\n",ch,str,cnt);

6.6、统计非空格字符个数

1)数组格式

int getStrCnt(char* ch)
{
    int i=0;
    int cnt=0;
    while(ch[i])
    {
        if(ch[i]!=' ')
            cnt++;
        i++;
    }
    return cnt;
}

char ch[]="h  e l  lo  wo   rld ";
int res=getStrCnt(ch);

2)指针格式

int getStrCnt(char* ch)
{
    int cnt=0;
    while(*ch)
    {
        if(*ch!=' ')
            cnt++;
        ch++;
    }
    return cnt;
}

char ch[]="h  e l  lo  wo   rld ";
int res=getStrCnt(ch);

3)统计各个字符出现的次数

//hash表
char ch[]="h  e l  lo  wo   rld nichousha chounizadi zaichuyigeshishi";
int hash[27]={0};
while(*ch)
{
    if(*ch==' ')
    {
        hash[26]++;
    }
    else
    {
        hash[*ch-'a']++;
    }
    ch++;
}

printf("空格出现了 %d 次\n",hash[26]);
for(int i=0;i<26;i++)
{
    printf("字符 %c 出现的次数为 %d\n",i+'a',hash[i]);
}

7、常用的字符串操作

7.1、字符串逆置

1)数组形式

void inverse(char* ch)
{
    int i=0;
    int j=strlen(ch)-1;
    while(i<j)
    {
        char temp=ch[i];
        ch[i]=ch[j];
        ch[j]=temp;
        i++;
        j--;
    }
}


//字符串逆置
char ch[]="hello world";
inverse(ch);

2)指针形式

void inverse(char* ch)
{
    char* ftemp=ch;
    char* btemp=ch+srtlen(ch)-1;
    while(ftemp<btemp)
    {
        char temp=*ftemp;
        *ftemp=*btemp;
        *btemp=*ftemp;
        ftemp++;
        btemp--;
    }
}


//字符串逆置
char ch[]="hello world";
inverse(ch);

7.2、字符串拷贝

1)strcopy()

#include<string.h>
char* strcopy(char* dest,const char* rsc);
  • 功能:把 src 所指向的字符串复制到 dest 所指向的空间,‘\0’ 也会拷贝过去
  • 参数:
    • dest:目的字符串首地址
    • src:源字符串首地址
  • 返回值:
    • 成功:返回 dest 字符串的首地址
    • 失败:NULL

注:如果 dest 所指向的内存空间不够大,可能会造成缓冲溢出的错误

#include<string.h>
char ch[]="hello world";
char str[100];
strcopy(str,ch);

strcpy()

void myStrCpy(char* dest,const char* src)
{
	while(*dest++=*src++);
}

2)strncopy()

#include<string.h>
char* strncopy(char* dest,const char* src,size_t n);
  • 功能:把 src 所指向的字符串的前 n 个字符复制到 dest 所指向的空间,'是否拷贝结束符取决于指定的长度是否包含结束符
  • 参数:
    • dest:目的字符串首地址
    • src:源字符串首地址
    • n:指定需要拷贝字符个数
  • 返回值:
    • 成功:返回 dest 字符串的首地址
    • 失败:NULL

注:当指定长度不包括字符串结束符时, dest 内不包含结束符,如果直接打印 dest 会出现越界现象

//字符串有限拷贝
#include<string.h>
char ch[]="hello world";
char str[100];
strcopy(str,ch,5);
7.3、字符串追加

1)strcat()

#include<string.h>
char* strcat(char* dest, const char* src);
  • 功能:把 src 字符串连接到 dest 的尾部,'\0’也会追加过去
  • 参数:
    • dest:目的字符串首地址
    • src:源字符串首地址
  • 返回值:
    • 成功:返回 dest 字符串的首地址
    • 失败:返回 NULL

注:如果 dest 所指向的内存空间不够大,可能会造成缓冲溢出的错误

#include<string.h>
char* ch[]="hello";
char* src[]="world";

strcat(ch,src);
void myStrCat(char* dest,const char* src)
{
    while(*dest++);
    while(*dest++=*src++);
}

2)strncat()

#include<string.h>
char* strcat(char* dest, const char* src,size_t n);
  • 功能:把 src 字符串连接到 dest 的尾部,'\0’也会追加过去
  • 参数:
    • dest:目的字符串首地址
    • src:源字符串首地址
    • n:指定需要追加字符的个数
  • 返回值:
    • 成功:返回 dest 字符串的首地址
    • 失败:返回 NULL

注:如果 dest 所指向的内存空间不够大,可能会造成缓冲溢出的错误

如果指定长度大于源字符串的长度,会将整个源字符串追加过去,不会报错

#include<string.h>
char* ch[]="hello";
char* src[]="world";

strncat(ch,src,n);
void myStrCat(char* dest,const char* src)
{
    while(*dest++);
    while(*dest++=*src++&&n--);
}
7.4、字符串比较

1)strcmp()

#include<string.h>
int strcmp(const char* s1,const char* s2);
  • 功能:比较 s1 和 s2 的大小,比较的是字符 ASCII 码的大小
  • 参数:
    • s1:字符串1的首地址
    • s2:字符串2的首地址
  • 返回值:
    • 相等:0
    • 大于:>0
    • 小于:<0

不相等的时候,有的操作系统返回 ASCII 码的差值,有的操作系统返回1 或者 -1

//字符串比较
#include<string.h>
char ch1[]="hello";
char ch2[]="world";

int value=strcmp(ch1,ch2);

2)strncmp()

#include<string.h>
int strncmp(const char* s1,const char* s2,size_t n);
  • 功能:比较 s1 和 s2 前 n 个字符的大小,比较的是字符 ASCII 码的大小
  • 参数:
    • s1:字符串1的首地址
    • s2:字符串2的首地址
    • n:指定比较字符的数量
  • 返回值:
    • 相等:0
    • 大于:>0
    • 小于:<0

不相等的时候,有的操作系统返回 ASCII 码的差值,有的操作系统返回1 或者 -1

//字符串比较
#include<string.h>
char ch1[]="hello";
char ch2[]="world";

int value=strcmp(ch1,ch2,5);
int myStrCmp(const char* s1,const char* s2)
{
    while(*s1==*s2)
    {
        if(!*s1)
            return 0;
        s1++;
        s2++;
    }
    return *s1>*s2?1:-1;
}
//这个版本不如上面的版本明朗
int myStrCmp(const char* s1,const char* s2)
{
    while(*s1++==*s2++ && !*s1)
    {
        if(!*--s1 && !*--s2)
            return 0;
    }
    return *s1>*s2?1:-1;
}
int myNStrCmp(const char* s1,const char* s2,size_t n)
{
    for(int i=0;i<n && s1[i] && s2[i];i++)
    {
        if(s1[i]!=s2[i])
        {
            return s1[i]>s2[i]?1:-1;
        }
    }
}
7.5、字符串格式化

1)sprintf() :格式化输出

#include<stdio.h>
int sprintf(char* str,const char* format,...);
  • 功能:根据参数 format 来转换并格式化数据,然后将结果 str 输出到指定的空间中,直到出现字符串结束符为止。实际就是将format 的内容存放到 str 中,并输出
  • 参数:
    • str:字符串首地址
    • format:字符串格式,用法与 printf 相同
  • 返回值:
    • 成功:实际格式化的字符个数
    • 失败:-1

注:format 也可以使用各种格式控制

#include<stdio.h>
char ch[100];
char ch2[100];
sprintf(ch,"hello world");		//输出:hello world
printf("%s\n",ch);				//输出:hello world

//format 也可以使用各种格式控制
sprintf(ch2,"%d+%d=%d\n",1,2,3);	//输出:1+1=2
printf("%s\n",ch1);					//输出:1+1=2

2)sscanf():格式化读入

#include<stdio.h>
int sscanf(const char* str,const char* format,...);
  • 功能:从 str 指定的字符串读取数据,并根据参数 format 字符串的格式来进行格式转换并格式化数据。实际就是将 str 按照 format 的格式进行格式转化
  • 参数:
    • str:指定的字符串首地址
    • format:字符串样式,用法和 sacnf 相同
  • 返回值:
    • 成功:参数数目,成功转换的值的个数
    • 失败:-1
char ch[]="1+2=3";
int a,b,c;
sscanf(ch,"%d+%d=%d",&a,&b,&c);		//把 ch 按照 "%d+%d=%d",&a,&b,&c 的格式分别存入 a、b、c 中

printf("%d\n",a);		//1
printf("%d\n",b);		//2
printf("%d\n",c);		//3
char ch[]="hello world";
char str[100];
char str1[100];

sscanf(ch,"%s",str);	//把 ch 中的内容按照字符串的格式读取到 ch 中,实际读取到的是 hello
printf("%s\n",str);		//输出:hello

sscanf(ch,"%[^\n]s",str1);	//把 ch 中的内容按照字符串的格式读取到 ch 中,实际读取到的是 hello world
printf("%s\n",str1);		//输出:hello world

char ch1[]="helloworld";
char str2[100];

sscanf(ch1,"%5s",str2);	//把 ch 中的前五个字符按照字符串的格式读取到 ch 中,实际读取到的是 hello
printf("%s\n",str2);		//输出:hello

char ch1[]="helloworld";
char str2[100];
char str3[100];

sscanf(ch1,"%5s%s",str2,str3);	
printf("%s\n",str2);		//输出:hello
printf("%s\n",str3);		//输出:world
7.6、字符串查找

1)strchr()

#include<string.h>
char* strchr(const char* s,int c);
  • 功能:在字符串 s 中查找字符 c 出现的位置
  • 参数:
    • s:字符串首地址
    • c:匹配字符
  • 返回值:
    • 成功:返回第一次出现 c 的地址
    • 失败:NULL
char* strchr(const char* ch,int c)
{
    while(*ch)
    {
        if(*ch==c)
            return ch;
        ch++;
    }
    return NULL;
}

char ch[]="hello world";
char c='l';
char* p=strchr(ch,c);

printf("%s\n",p);

2)strstr()

#include<string.h>
char* strstr(const char* haystack,const char* needle);
  • 功能:在字符串 haystack 中查找字符串 needle 出现的位置
  • 参数:
    • haystack:源字符串的首地址
    • needle:匹配字符串的地址
  • 返回值:
    • 成功:返回第一次出现 needle 的地址
    • 失败:NULL
char ch[]="hello world";
char str[]="llo";
char* p=strstr(ch,str);
printf("%s\n",p);
7.7、字符串分割

1)strtok()

#include<string.h>
char* strtok(char* str,const char* delim);
  • 功能:将字符串分割成一个个片段,当 strtok() 在参数 str 的字符串中发现参数 delim 中包含的分割字符时,则会将该字符改为 0 字符,当连续出现多个时,只替换第一个为 0
  • 参数:
    • str:指向欲分割的字符串
    • delim:为分割字符串中包含的所有字符
  • 返回值:
    • 成功:分割后字符串首地址
    • 失败:NULL

在第一次调用时,strtok() 必须基于参数 str 字符串

在往后的调用,则将参数 str 设置成 NULL,每次调用成功则返回指向被分割出片段的指针

char ch[]="www.baidu.com";

//第一次截取
char* p=strtok(ch,".");
printf("%s\n",p);		//输出:www
printf("%s\n",ch);		//输出:www	此时 ch 中的内容为:www\0baidu.com

printf("%p\n",p);		//输出:0019F7E4
printf("%p\n",ch);		//输出:0019F7E4

//第二次截取
p=strtok(NULL,".");
printf("%s\n",p);		//输出:baidu	此时,ch 中的内容为:www\0baidu\0com

//第三次截取
p=strtok(NULL,".");
printf("%s\n",p);		//输出:com	
  • strtok() 会破坏源字符串 str,用 \0 替换分割的标志位
char ch[]="123456789@qq.com";
char chCopy[100]={0};

//字符串备份
strcopy(chCopy,ch);

char* p=strtok(str,"@");
printf("%s\n",p);		//输出:123456789

p=strtok(NULL,".");
printf("%s\n",p);		//输出:qq


char* ch[]="nichousha\nchounizadi\nzaichouyixiashishi\nduibuqidagewocuole\nguawazi\n";
char*p=strtok(ch,"\n");
printf("%s\n",p);		//输出:nichousha

//利用循环多次截取
while(p)
{
    printf("%s\n",p);
    p=strtok(NULL,"\n");
}
/*
输出:
nichousha
chounizadi
zaichouyixiashishi
duibuqidagewocuole
guawazi
*/
7.8、字符串类型转换

1)atoi()

#include<stdlib.h>
int atoi(const char* nptr);
  • 功能:atoi() 会扫描字符串,跳过前面的空格字符,直到遇到数字或者正负号才开始做转换,而遇到非数字或字符串结束符才结束转换,并将结果返回
  • 参数:nptr,待转换的字符串
  • 返回值:成功转换后的整数

类似的函数:

  • atof():把一个小数形式的字符串转换为一个浮点数(特指双精度浮点型)
  • atol():将一个字符串转换成 long 类型
#include<stdlib.h>

/*
char ch[]="123456";

int i=atoi(ch);
printf("%d\n",i);		//输出:123456
*/

/*
char ch[]="-123456";

int i=atoi(ch);
printf("%d\n",i);		//输出:-123456
*/

/*
char ch[]="    -123456abc123";

int i=atoi(ch);
printf("%d\n",i);		//输出:-123456
*/

char ch[]="  asc   -123456abc123";

int i=atoi(ch);
printf("%d\n",i);		//输出:0


注:只能识别十进制

char ch[]="   -123.456abc123";

int i=atof(ch);
printf("%f\n",i);		//输出:-123.456
char ch[]="   -123.456abc123";

int i=atof(ch);
printf("%d\n",i);		//输出:-123
  • 26
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值