C语言-回顾篇(下)

简介: C语言-回顾篇

八、字符串处理函数    


C语言的字符串处理函数在C函数库中提供了一些用来专门处理字符串的函数,几乎所有C语言编译系统都提供这些函数,有下面这几种。处了输入输出函数含有以下函数

8.1 strcat函数——字符串连接函数    

一般形式为:strcat(字符数组1,字符数组2)这个函数顾名思义,就说把字符数组2连在字符数组1的尾部,结果放在字符数组1中,然后返回字符数组1的地址。

char str[100]={"monkey"};              
char str1[]={" like coding"};              
printf("%s",strcat(str,str1));

输入:”monkey like coding“

这个函数有几点需要注意:

在将字符数组2连接到字符数组1时,字符数组1的容量必须足够大,这样函数才能执行成功,否则会因为长度不够而出现问题。

连接前两个字符串的后面都有’\0’,连接时将字符串1后面的’\0‘取消,只在新串 最后保留‘\0’。


8.2 strcpy和strncpy函数 字符串复制函数  

 

一般形式为:strcpy(字符数组1,字符串2)它表示“字符串复制函数”,作用是将字符串 2复制到字符数组1中去。例如:

char strl[10] ,str2[] = "China”;            
strcpy(strl ・str2);            

执行后,str1的状态就是:

             

字符数组1的长度不 应小于字符串2的长度。

“字符数组1”必须写成数组名形式(如strl),“字符串2"可以是字符数组名,也可 以是一个字符串常量。例如:strcpy(strl ,“China”);

如果在复制前未对strl数组初始化或赋值,则strl各字节中的内容是无法预知 的,将str2的复制到str1后,最后4个字符并不一定是‘\0‘,而是strl中原有的最后4个字节的内容。

不能直接使用数组地址名进行赋值,例如:str1=str2 是不合法的。只能通过strcpy来进行复制操作。

可以用strncpy函数将字符串2中前面n个字符复制到字符数组1中去。

例如:

strncpy(strl ,str2,2);            

作用是将str2中最前面2个字符复制到strl中,取代strl中原有的最前面2个字符。但复制的字符个数n不应多于strl中原有的字符(不包括’\0’)。


8.3 strcmp函数——字符串比较函数  

 

其一般形式为:strcmp(字符串1,字符串2)它的作用是比较字符串1和字符串2。例如;

strcmp(strl,str2);            
strcmp("China","America");            
strcmp(strl,Beijing");            

字符串比较的规则是:将两个字符串自左至右逐个字符相比(按ASCII码值 大小比较),直到出现不同的字符或遇到’\0‘为止。(1)如全部字符相同,则认为两个字符串相等;(2)若出现不相同的字符,则以第1对不相同的字符的比较结果为准.注意:如果参加比较的两个字符串都由英文字母组成,我们需要关注字母在英文字典中的顺序。例如:computer在字典中的位置在compare之后,所以 “computer”>“compare”。注意小写字母比大写字母"大”・所以"DOG”<“cat”。比较的结果由函数值带回。如果字符串1与字符串2相同,则函数值为0如果字符串1>字符串2,则函数值为一个正整数如果字符串1<字符串2,则函数值为一个负整数。


8.4 strlen函数——测字符串长度的函数  

 

其一般形式为:strlen(字符数组)它是测试字符串长度的函数。函数的值为字符串中的实际长度(不包括’\0’在内)。例如:

char str[10]={"shanghai"};              
printf("%d",strlen(str));              
printf("%d",strlen("shanghai"));//也可以直接测试字符串常量。            

输出结果是8。


8.5 strlwr函数和strupr函数——大小写转换函数

 

其一般形式为:strlwr(字符数组)strupr(字符数组)strlwr函数的作用是将字符串中大写字母换成小写字母。strupr函数的作用是将字符串中小写字母换成大写字母。

char str[10]={"shanghai"};              
printf("%s",strupr(str));              
printf("%s",strlwr(str));              

结果是:SHANGHAIshanghai

九、函数    


9.1 函数的定义

   

函数是一个完成特定工作且包装起来的独立程序模块。函数的种类包括库函数和自创函数。

库函数包括scanf(),printf()函数等。这两个函数包含在stdio.h函数库内。

自创函数即为自己根据题目要求来设计的函数,然后对函数进行封装,使用时直接调用即可。我们主要学习自创函数的结构与用法。


9.2 C语言中常见函数    


9.2.1 从函数定义角度(分为两个)  
 

1.库函数

2.自定义函数


9.2.2 库函数的定义    


库函数是什么呢?

库函数(Library function)是将函数封装入库,供用户使用的一种方式。方法是把一些常用到的函数编完放到一个文件里,供不同的人进行调用。调用的时候把它所在的文件名用#include<>加到里面就可以了。


9.2.3 为什么会有库函数呢?  
 

1.我们知道在我们学习C语言编程的时候,总是在一个代码编写完成之后迫不及待的想知道结果,想把这个结果打印到我们的屏幕上看看。这个时候我们会频繁的使用一个功能:将信息按照一定的格式打印到屏幕上(printf)。

2.在编程的过程中我们会频繁的做一些字符串的拷贝工作(strcpy)。

3.在编程的过程中我们会频繁的做一些字符串的比较工作(strcmp)

为了支持可移植性和提高代码的效率,所以C语言的基础库中提供了一系列类似的库函数。


9.2.4 C语言常用的库函数都有:    


IO函数

字符串操作函数

字符操作函数

内存操作函数

时间/日期函数

数学函数

其他库函数


9.3.4 自定义函数    


自定义函数和库函数一样,有函数名,返回类型和函数参数。不一样的是自定义函数是我们自己设计的,发挥空间很大。

写一个函数找出两个数的较大值

#include
//函数的实现        
int get_max(int x,int y)        
{//返回类型//函数名//传入参数类型        
    return x>y?x:y;//返回较大值        
}        
int main()        
{        
    int a=10;        
    int b=20;        
    //函数的调用        
    int max=get_max(a,b);//将返回的较大值放入max中        
       printf("%d\n",max);        
    return 0;        
}


写一个函数将两个数值交换

#include
//函数返回类型的地方写出:void,表示这个函数不返回任何值,也不需要返回        
void Swap1(int x,int y)        
{        
    int temp=0;        
    temp=x;        
    x=y;        
    y=temp;        
}        
int main()        
{        
    int a=10;        
    int b=20;        
    //写一个函数交换2个整形变量的值        
    printf("交换前:a=%d b=%d\n",a,b);        
    Swap1(a,b);        
    printf("交换后:a=%d b=%d",a,b);        
    return 0;        
}


 

实践上a和b在传进函数时,创建了一份新的空间x和y,这里只是将x和y的值交换了,而没有操作a和b的权利,a和b与x和y是独立的空间,**下图时调试监视的值。**可以看到a与x的地址不一样,b与y的地址不一样。

 

那么怎么才能完成交换呢?


在上面代码中我们可以通过*pa找到a,将a的值改为20,那么我们交换函数就可以这样写:

#include
void Swap2(int* pa,int* pb)        
{        
    int temp=0;        
    temp=*pa;//*pa是通过pa找到a        
    *pa=*pb;        
    *pb=temp;        
}        
int main()        
{        
    int a=10;        
    int b=20;        
    //写一个函数交换2个整形变量的值        
    printf("交换前:a=%d b=%d",a,b);        
    Swap2(&a,&b);        
    printf("交换后:a=%d b=%d",a,b);        
    return 0;        
}

 

可以看到此时将地址传进去,pa和a的地址一样,pb和b的地址一样

 


9.3 函数定义、函数声明、函数原型  

 

函数定义、函数声明、函数原型;变量声明、变量定义;类型声明、类型定义

1.函数定义、函数声明、函数原型

2.变量声明、变量定义

3.类型声明、类型定义

1.函数定义、函数声明、函数原型

函数声明中不但包含“函数原型声明”,也包含“函数定义”和老式的“函数类型声明”。

函数类型声明(Function type declaration)。声明了函数名是一个函数及其返回值的类型。(没对参数进行描述)

函数原型式的函数类型声明:声明了函数名、返回值的类型、参数的类型(和个数)

函数定义:写了函数体的函数声明。不过函数体结束不带;号。

函数定义带有函数体,函数声明≈函数原型

当函数声明宣示了函数的名字、参数类型和个数、返回值类型,它就叫函数原型式声明,即函数原型。

只有带函数体的声明才叫定义。

函数定义本身就是一种函数声明,只不过其函数体后不加;号

不带函数体的函数声明`应该如何称呼呢?在C语言中,它们叫被做“函数类型声明”(Function type declaration)。`函数类型声明`最主要的特点是声明了

函数名`是一个函数及其`返回值的类型`,如果也声明了参数的类型,则是

函数原型`式的函数类型声明。现代的C语言的函数定义和函数类型声明

都采用函数原型式的风格,C99把旧的非原型形式视为过时。

函数原型后面由分号;结尾。

由于有 Old Style C 语法的存在,并非所有函数声明都包含完整的函数原型 如 void threeline();这个声明并没有明确指出参数类型和个数,所以不算函数原型。

函数原型式声明:

int fun(int a, int b); // 函数名字fun,返回类型 int,有2个int 参数

函数原型式声明:

int fun(int, int); // 函数名字fun,返回类型 int,有2个int 参数

函数原型式声明:

fun(int, int); // 函数名字fun,默认返回类型(其实也是int),有2个int 参数

例如函数声明和定义:

int fun(int a, int b)              
{              
   return a+b;              
}// 花括号里是函数体,结束处没有分号,有形参名。            

             

知识点:函数定义也具有对函数名的类型加以说明的效果,因此从这个意义上来说,函数定义也是一种对函数类型的说明。这种办法可以检查出函数调用时在参数个数和类型方面的错误。

 

但是,用这种办法说明函数名并不好,因为这样做在编程时还需要考虑应该把哪个函数定义写在前面,哪个写在后面的问题。假如函数A调用函数B,函数B调用函数C,函数C又调用函数A,究竟如何安排函数定义的顺序就会让人感到无所适从。此外这种办法也不利于代码的组织,在由多个源文件组成的源程序时,这种写法就更会捉襟见肘、漏洞百出。因此,在1990年,C标准借鉴C++语言规定了一种新的说明函数名的方法,这就是函数原型(Function Propotype)式说明函数类型的方法。

2.变量声明、变量定义

只有分配存储空间的变量声明才叫变量定义,

其实函数也是一样,编译器只有见到函数定义才会生成指令,而指令在程序运行时当然也要占存储空间。那么没有函数体的函数声明有什么用呢?它为编译器提供了有用的信息,编译器在翻译代码的过程中,只有见到函数原型(不管带不带函数体)之后才知道这个函数的名字、参数类型和返回值,这样碰到函数调用时才知道怎么生成相应的指令,所以函数原型必须出现在函数调用之前,这也是遵循“先声明后使用”的原则。

3.类型声明、类型定义

声明一个类型是不分配存储空间的,

类型声明==“类型定义”


9.4 函数的参数  

 

函数调用:    

函数定义:    

 

在Swap1和Swap2函数调用中的a和b为实际参数,在Swap1和Swap2函数定义中的x,y,pa,pb都为形式参数。

实际参数    

真实传给函数的参数,叫实参。实参可以是:常量、变量、表达式、函数等。无论实参是何种类

型的量,在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形参。

例:

int a=10;              
    int b=20;              
    int max=get_max(a,get_max(3,5));//将返回的较大值放入max中

get_max(3,5)获得5,然后int max=get_max(a,5)。

形式参数    

形式参数是指函数名后括号中的变量,因为形式参数只有在函数被调用的过程中才实例化(分配

内存单元),所以叫形式参数。形式参数当函数调用完成之后就自动销毁了。因此形式参数只在

函数中有效。形参实例化之后相当于是实参的一份临时拷贝。


9.5 函数调用  

 

传值调用    

函数的形参和实参分别占有不同的内存块,对形参的修改不会影响实参。下图是传值调用:

传址调用    

传址调用是把函数外部创建变量的内存地址传递给函数参数的一种调用函数的方式。

这种传参方式可以让函数和函数外边的变量建立起正真的联系,也就是函数内部可以直接操

作函数外部的变量。下图是传址调用:

练习

写一个判断素数的函数    
#include
int is_prime(int n)        
{        
    int j=0;        
    for(j=2;j        <n;j++)        
    {        
        if(n%j==0)        
        {        
            return 0;        
        }        
    }        
    return 1;        
}        
int main()        
{        
    //100-200之间的素数        
    int i=0;        
    for(i=100;i<=200;i++)        
    {        
        //判断i是否为素数        
        if(is_prime(i)==1)        
        {        
            printf("%d ",i);        
        }        
    }        
    return 0;        
}        
       </n;j++)

 

写一个判断闰年的函数    
#include
//is_leap_year        
//如果是闰年返回1        
//不是闰年返回0        
int is_leap_year(n)        
{        
    //return ((y%4==0&&y%100!=0)||(y%400==0))        
   if((n%4==0&&n%100!=0)||(n%400==0))        
   {        
       return 1;        
   }        
    return 0;        
}        
int main()        
{        
    int y=0;        
    for(y=1000;y<=2000;y++)        
    {        
        if(is_leap_year(y)==1)        
        {        
            printf("%d \n",y);        
        }        
    }        
    return 0;        
}


   

 

写一个函数,实现二分有序查找    
#include
//int binary_search(int *a,int k,int s)        
int binary_search(int a[],int k,int s)        
{        
    int left=0;        
    int right=s-1;        
    while(left<=right)        
    {        
        int mid=(left+right)/2;        
        if(a[mid]>k)        
        {        
            right=mid-1;        
        }        
        else if(a[mid]        <k)        
        {        
            left=mid+1;        
        }        
        else        
        {        
            return mid;        
        }        
    }        
    return -1;//找不到了        
}        
int main()        
{        
    int arr[]={1,2,3,4,5,6,7,8,9,10};        
    int key=7;        
    int sz=sizeof(arr)/sizeof(arr[0]);        
    //找到了就返回位置        
    //找不到返回-1        
    //数组arr传参实际上传递的不是数组本身        
    //仅仅传过去了数组首元素的地址        
       int ret = binary_search(arr,key,sz);        
    if(-1==ret)        
    {        
        printf("找不到\n");        
    }        
    else        
    {        
        printf("找到了,下标是:%d\n",ret);        
    }        
    return 0;        
}        
       </k)

 

数组arr传参实际上传递的不是数组本身

仅仅传过去了数组首元素的地址

注意:数组传参时不能在函数里面计算数组的元素个数。

写一个函数,每调用一次这个函数,就会将num的值增加1    
#include
void Add(int *p)        
{        
    (*p)++;        
}        
int main()        
{        
    int num = 0;        
    //每调用一次这个函数,就会将num的值增加1        
    Add(&num);        
    printf("%d\n",num);//1        
    Add(&num);        
    printf("%d\n",num);//2        
       Add(&num);        
    printf("%d\n",num);//3        
    return 0;        
}



9.6 函数的嵌套调用和递归调用    


函数的嵌套:    

在调用一个函数的过程中,又调用另一个函数。这就是函数的嵌套。注意,是调用另一个函数。

图解:

             

在嵌套调用过程中,若被调函数有返回值,则返回运算的值,若无则执行完后返回主调函数或进行进行下一个被调函数的运行。最终均在main函数中结束

在进行调用f函数的过程中,需要调用f1函数;而在调用f1函数过程中,又要调用f函数,互相被其调用。在了解了嵌套递归的区别之后,接下来看代码:嵌套:实现4个数中的最大值输出

#include              //实现最大值的交换,进行函数的嵌套调用        
#include
int main()        
{        
int max1(int a,int b,int c,int d);        
int a,b,c,d,max;        
printf("Please enter 2 interger numbers:");        
scanf("%d,%d,%d,%d",&a,&b,&c,&d);        
max=max1(a,b,c,d);        
printf("max=%d \n",max);        
system("pause");        
return 0;        
}        
int max1(int x,int y,int z,int k)        
{        
    int max2(int i,int j);        
    int m;        
    m=max2(x,y);        
    m=max2(m,z);        
    m=max2(m,k);        
    return m;        
}        
int max2(int i,int j)        
{        
if(i>=j)        
return i;        
else        
return j;        
}

C语言中所有代码执行过程,都是在main函数中开始,也是在main函数中结束。我们来看该函数的执行过程,进入main函数往下,输入数据之后,进行max1的函数调用,进行实参与形参传值,进入max1函数主体,此时max1又调用了max2函数,又进入max2函数。进行值的比较,从函数功能上看,返回的值是较大的那个值。返回值之后,又进入max1函数,继续往下进行,当所有嵌套调用执行完之后,返回最大值给main函数并赋值给max变量输出。函数结束!

可见,函数之间的嵌套还是很简单的,过程很容易分析。

函数的递归:    

函数自己调用自己就是递归,它通常把一个大型复杂的问题层层转换为一个与原问题相似的规模较小的问题来求解,递归思想只需要少量程序

图解:

             

递归的两个必要条件

1.存在限制条件,当满足这个限制条件的时候,递归便不再继续。2.每次递归调用之后越来越接近这个限制条件


9.7 变量作用域和生命周期    


作用域顾名思义就是一个变量的使用范围有多大,而生命周期意思就是一个变量在什么时候被释放。

变量在C语言中分为全局变量和局部变量,先来讨论全局变量的作用域。

一、作用域

1、作用域问题:在一个函数中定义的变量,在其他函数中能否被引用?在不同位置定义的变量,在什么范围内有效?

2、定义变量可能有3种情况

(1)在函数的开头定义;

(2)在函数内的复合语句内定义;

(3)在函数的外部定义。

3、局部变量

(1)在函数内部定义的变量,只有在本函数内才能引用它们,也只有在本函数范围内有效;

(2)在复合语句内定义的变量,只有在本复合语句内才能引用它们,也只有在本复合语句范围内有效;

(3)举例如下,两个函数中的tmp只作用域在自己的函数体内。

#include
void cpri()        
{        
    char tmp= '6';        
    printf("cpri: %c\n", tmp);        
}        
void ipri()        
{        
    int tmp = 666;        
    printf("ipri: %d\n", tmp);        
}        
int main()        
{        
    cpri();        
    ipri();        
    return 0;        
}        
运行结果:
cpri: 6
ipri: 666

4、全局变量

(1)在函数之外定义的变量是全局变量。

(2)C程序设计中,一般习惯将全局变量名的首字母大写。

(3)尽力减少使用全局变量的原因

a、内存开销大,全局变量在程序整个执行过程中都占有存储单元;

b、降低函数的通用性,不利于函数作为一个功能模块拷贝到别的文件中复用;

c、代码的可阅读性降低,人们难以清楚的判断出每个瞬时,各个外部变量的值。

二、变量的存储方式和生命周期

1、变量的存储方式有两种

(1)静态存储方式

A、静态存储方式是指在程序运行期间由系统分配固定的存储空间的方式。

B、全局变量全部存放在静态存储区中,在程序开始执行时给全局变量分配存储区,程序执行完毕就释放。整个执行过程中它们占据着固定的存储单元。

(2)动态存储方式

A、动态存储方式是指在程序运行的期间根据需求进行动态的分配存储空间的方式。

B、动态存储区中存放以下数据:

① 函数形式参数。在调用函数时给形参分配存储空间。

② 函数中定义的没有用关键字static声明的变量。即自动变量。

③ 函数调用时的现场保护和返回地址等。

注意:上述的分配和释放时动态的,如果一个程序中两次调用同一函数,而在此函数种定义了局部变量,在2次调用时分配该局部变量获得的存储空间的地址可能是不同的。

2、变量的存储类别

(1)C语言中,每一个变量和函数都有2个属性:数据类型和数据的存储类别。C的存储类别有4种:自动的(auto)、静态的(static)、寄存器的(register)、外部的(extern)。变量的存储类别对应变量的作用域与生命周期。

3、局部变量的存储类别

(1)自动变量(auto变量)

函数中的局部变量,如果不专门声明是static存储类别,都是动态地分配存储空间的。函数中的形参和在函数中定义的局部变量(包括符合语句中的局部变量)都属于此类。如 函数中定义变量 int a; 和 auto int a; 是等价的,关键字“auto”是默认省略的。

(2)静态局部变量(static局部变量)

有时希望函数中的局部变量的值在函数调用结束后不消失,继续保留原值,即其占用的存储单元不释放,在下一次再调用该函数时,该变量已有值。此时应该用static声明该变量为“静态局部变量”。

说明:

A、静态局部变量属于静态存储类别,在静态存储区内分配存储单元。自动变量(即动态局部变量)属于动态存储类别,分配在动态存储空间中。

B、静态局部变量实在编译时赋初始值的,即只赋值一次。

C、如果在定义局部变量时不赋初值的话,则对静态局部变量来说,编译时自动赋初值0(对数值型变量)或空字符‘\0’(对字符变量)。而对自动变量来说,它的值是一个不确定的值。这是由于每次函数调用结束后存储单元已经释放,下次调用时又重新另分配存储单元,所分配的单元中的内容时不可知的。

D、虽然静态局部变量在函数调用结束后仍然存在,但别的函数不能引用它。因为它是局部变量。

(3)寄存器变量(register变量)

A、如果一个变量频繁使用,可以声明为寄存器变量。由于寄存器的读写速度远快于内存的读写速度。所以能提高执行效率。

B、由于现在的计算机的速度愈来愈快,性能愈来愈高,优化的编译系统能够识别出使用频繁的变量,从而自动地将这些变量放在寄存器中,而不需要程序设计者指定。因此,现在实际上用register声明变量的必要性不大。

4、全局变量的存储类别

全局变量都是存放在静态存储区中的。

(1)在一个文件内扩展外部变量的作用域,应在引用前用关键字extern对该变量作“外部变量声明”,表示把该外部变量的作用域扩展到此位置。

#include
int test()        
{        
    extern num;        
    printf("The num is : %d\n", num);        
    return 0;        
}        
int num = 666;        
int main()        
{        
    test();        
    return 0;        
}        
//运行的结果时  The num is : 666


   

注意:用extern声明外部变量时,类型名可写可不写,如"extern int num;"也可以写成"extern num;"。因为它不是定义变量,可以不指定类型,只许写出外部变量名即可。

(2)将外部变量的作用域扩展到其他文件

A、如在file1.c中定义 int num; 在file2.c中 加上#include "file1.c" ,然后在需要引用的地方前面加上 extern int num; 即可。

注:extern即可以用来扩展外部变量在本文件中的作用域,又可以使外部变量的作用域从一个文件扩展到程序中的其他文件。编译器区别处理原则是:遇到extern时,现在本文件中找外部变量的定义,如果找到,就在本文件中扩展作用域;如果找不到,就在连接时从其他文件中找外部变量的定义。如果找到就将作用域扩展到本文件;如果没有就报错。

(3)将外部变量的作用域限制在本文件中

A、有时在程序设计中希望某些外部变量只限制被本文件引用,而不被其他文件引用,这时可以在定义外部变量时加一个static声明。

B、加上 static 声明、只能用于本文件的外部变量称为静态外部变量。

1、for中初始化语句定义变量

#include
int main()        
{        
    for (int i = 0; i < 3; i++) {        
        printf("Hello\n");        
    }        
    /* printf("%p\n", &i); */        
    for (int i = 0; i < 3; i++) {        
        printf("World\n");        
    }        
    return 0;        
}        
结果输出:
Hello
Hello
Hello
World
World
World

代码中两和for 循环初始化语句定义的变量一样,运行没有问题,新版本语法规范规定,for循环中,局部变量内存的分配在循环开始时,释放在循环结束时。


9.8 内部函数和外部函数  

 

内部函数与外部函数的区分标准是,该函数能否被其他源文件调用。

内部函数  

如果一个函数只能被本文件中的其他函数调用,称为内部函数。在定义内部函数的时候,在函数名和函数类型的前面加static,即:

static 类型名 函数名(形参列表)

例如函数的首行:static int function(int a)

表示该函数function是一个内部函数,只能被本文件中的函数调用,而不能被其他文件调用。

外部函数  

与内部函数相反,外部函数则是可以被其他文件调用的函数。在函数声明时,加上extern关键字,则声明为外部函数,可供其他文件调用。

如:函数首部可以是:extern int fun (int a,int b)

C语言规定如果在定义函数时省略extern,则默认为外部函数。所以除非声明为static,我们常写的都是外部函数。

9.9 库函数    

请参考c语言函数库手册

十、指针    


10.1定义:    


指针定义:通俗的说就是一个地址(常量)。

书中定义:根据内存单元的编号或地址找到相应的内存单元

“看图识字”:设有字符变量 C,其内容为“K”(ASCII 码为十进制数 75),C 占用了011A 号单元(地址用十六进数表示)。设有指针变量 P,内容为 011A,这种情况我们称为 P 指向变量 C,或说 P 是指向变量 C 的指针

定义指针的目的:

通过指针去访问内存单元。

变量的指针就是变量的地址。存放变量地址的变量是指针变量。即在C语言中,允许用一个变量来存放指针,这种变量称为指针变量。因此,一个指针变量的值就是某个变量的地址或称为某变量的指针。

为了表示指针变量和它所指向的变量之间的关系,在程序中用“*”符号表示“指向”, eg:i_pointer 代表指针变量,而*i_pointer是i_pointer 所指向的变量。

             

i=3; *i_pointer=3;

第二个语句的含义是将 3 赋给指针变量 i_pointer 所指向的变量。


10.2 定义一个指针变量    


格式:类型说明符 *变量名;

*表示这是一个指针变量,变量名即为定义的指针变量名,类型说明符表示本指针变量所指向的变量的数据类型。

例如:int *p1;

表示 p1 是一个指针变量, 它的值是某个整型变量的地址。或者说 p1 指向一个整型变量。至于 p1 究竟指向哪一个整型变量,应由向 p1 赋予的地址来决定。

10.3 指针变量的引用    

注意:

1、指针不仅要先定义说明,而且必须赋予具体的值 。

2、指针变量的赋值只能赋予地址,决不能赋予任何其它数据。

3、不允许把一个数赋予指针变量 eg:int *p; p=1000;//错误

4、被赋值的指针变量前不能再加“*”说明符 eg:*p=&a//错误 正确:p=&a

正确的初始化赋值方式:int a;int *p=&a;OR int a;int *p; p=&a;

 

注意:程序第 5、6 行的“pointer_1=&a”和 “pointer_2=&b”不能写成“*pointer_1=&a”和 “*pointer_2=&b”。

请对下面再的关于“&”和“*”的问题进行考虑:1) 如果已经执行了“pointer_1=&a;”语句,则&*pointer_1 是什么含义?等价&a2) *&a 含义是什么?等价*pointer_a3) (pointer_1)++和pointer_1++的区别?

经运行得到的值如下图:

             


10.4 指针变量作为函数参数

 

swap(int *p1,int *p2)              
{              
      int temp;              
      temp=*p1;              
      *p1=*p2;              
      *p2=temp;              
}              
main()              
{              
         int a,b;              
     int *pointer_1,*pointer_2;              
     scanf("%d,%d",&a,&b);              
     pointer_1=&a;pointer_2=&b;              
     if(a                    printf("\n%d,%d\n",a,b);        
}


结果就是用户输入的值比较大小,当然了要求是int类型你要是输入其他类型那么对不起咱得到的结果可能就是不是你所期望的。。。

书上的解释“对程序的说明:swap 是用户定义的函数,它的作用是交换两个变量(a 和 b)的值。swap 函数的形参p1、p2 是指针变量。程序运行时,先执行 main函数,输入 a 和b 的值。然后将 a 和b 的地址分别赋给指针变量 pointer_1和 pointer_2,使pointer_1 指向 a,pointer_2 指向b。接着执行 if 语句,由于 a〈b,因此执行 swap 函数。注意实参 pointer_1 和 pointer_2 是指针变量,在函数调用时,将实参变量的值传递给形参变量。采取的依然是“值传递”方式。因此虚实结合后形参 p1 的值为&a,p2 的值为&b。这时 p1 和 pointer_1 指向变量 a,p2 和pointer_2 指向变量b。”

上面我们有提供指针不但要定义还要初始化,看看下面例子

swap2(int *p1,int *p2)              
{              
    int *temp;              
    *temp=*p1;              
    *p1=*p2;              
    *p2=*temp;              
}

     

看看运行结果:

             

原因就是指针temp没有赋值。。赋一个值 “ int i=0 ; int *temp=&i;” 问题解决……

注意:不能企图通过改变指针形参的值而使指针实参的值改变

解释:其中的问题在于不能实现如图所示的第四步(d)

             

10.5 指针运算  

 

1. 指针运算符

1、指针变量只能进行赋值运算和部分算术运算及关系运算

2、取地址运算符&:取地址运算符&是单目运算符,其结合性为自右至左,其功能是取变量的地址

3、取内容运算符*:取内容运算符*是单目运算符,其结合性为自右至左,用来表示指针变量所指的变量

4、指针运算符*和指针变量说明中的指针说明符*不是一回事

指针变量说明中,“*”是类型说明符,表示其后的变量是指针类型

表达式中出现的“*”则是一个运算符用以表示指针变量所指的变量

             

2. 指针变量的运算

1、指针变量初始化赋值

2、把一个变量的地址赋予指向相同数据类型的指针变量

例如:  int a,*pa;  pa=&a; /*把整型变量a 的地址赋予整型指针变量 pa*/

3、把一个指针变量的值赋予指向相同类型变量的另一个指针变量

例如:  int a,*pa=&a,*pb;  pb=pa; /*把a 的地址赋予指针变量 pb*/{由于 pa,pb均为指向整型变量的指针变量,因此可以相互赋值}

4、把数组的首地址赋予指向数组的指针变量

例如:  int a[5],*pa;  pa=a;  (数组名表示数组的首地址,故可赋予指向数组的指针变量 pa)  也可写为:当然你也可以赋值&a[1]、&a[2]…… pa=&a[0]; /* 数组第一个元素的地址也是整个数组的首地址,也可赋予 pa */

当然也可采取初始化赋值的方法:  int a[5],*pa=a;

5、把字符串的首地址赋予指向字符类型的指针变量

例如:  char *pc;  pc="C Language";  或用初始化赋值的方法写为:  char *pc="C Language";

注意:不是把整个字符串装入指针变量,而是把存放该字符串的字符数组的首地址装入指针变量

6、把函数的入口地址赋予指向函数的指针变量 例如:  int (*pf)();  pf=f; /*f 为函数名*/


10.6 指针和数组和多维数组    


数组:一块连续的空间,存放相同数据类型的数据。

指针数组:就是一个数组,数组里的元素是指针。

多维数组:也是一个数组,数组里的元素是数组。

char * str = "hello";

这里用的是64位操作系统:

str是一个指针,指向一个字符串,字符串里面的内容是hello。

一个指针所占内存是8个char类型的大小,也就是8个字节(Byte)。

"hello"字符串有5个字母,每个字母占一个char,另外末尾其实有一个’\0’的结尾符,也在一个char。所以一般字符串大小是字符串的能看到的长度(ASCII码里的字符)+ 一个’\0’的大小。这里就是6个字节(Byte)。

用sizeof运算符

#include
int main()        
{        
    char* str = "hello";        
    printf("str pointer length : %d\nstr length:%d\n", sizeof(str), sizeof("hello"));        
    printf("other pointer length :%d ,%d\n", sizeof(int*), sizeof(double*));        
}        
结果输出:
str pointer length : 8
str length:6
other pointer length :8 ,8
一个char* p可以存一个字符串,那么需要更多的字符串,就要声明更多的指针。
char * str = "hello";              
char * str2 = "nice";              
char * str3 = "ok";              
...              
这样很累
所以就有了指针数组。
char* str_arr[]={              
    "hello",              
    "nice",              
    "ok"              
};              
当然也可以这样
char* p = "uu";              
char* str_arr[]={              
    p,              
    "hello",              
    "nice",              
    "ok"              
};              
怎么访问呢?
#include
int main()        
{        
    char* p = "ux";        
    char* str_arr[] = {        
        p,        
        "hello",        
        "nice",        
        "ok"        
    };        
    // 下标方式访问 数组名[下标]        
    printf("%s\n", str_arr[0]);        
    printf("%s\n", str_arr[1]);        
    printf("%s\n", str_arr[2]);        
    // 指针方式访问 *(数组名+指针移动量)        
    printf("%s\n", *str_arr);        
    printf("%s\n", *(str_arr+3));        
}        
输出结果:
ux
hello
nice
ux
ok
对应字符串里的字符怎么访问呢?
一般访问方式有四种,如下:
// 方式1 数组+数组              
printf("%c\n", str_arr[0][0]);              
printf("%c\n", str_arr[0][1]);              
printf("========\n");              
// 方式2 数组+指针              
printf("%c\n", *str_arr[0]);              
printf("%c\n", *(str_arr[0]+1));              
printf("========\n");              
// 方式3 指针+数组              
printf("%c\n", (*(str_arr + 2))[0]);              
printf("%c\n", (*(str_arr + 2))[1]);              
printf("========\n");              
// 方式4 指针+指针              
printf("%c\n", *(*(str_arr + 3)));              
printf("%c\n", *((*(str_arr + 3))+1));              
printf("========\n");

一般来说指针数组的大小等于,数组里元素的个数x指针的大小(8Byte)

每个指针指向的空间大小不一样,加起来就是

             

int m1[3][3] = {              
{1, 2, 3},              
{4, 5, 6},              
{7, 8, 9}              
};

怎么访问呢?

如下六种方式:

// 第一种              
printf("%d\n", m1[1][1]);    // 5              
// 第二种              
printf("%d\n", m1[0][4]);    // 5              
// 第三种              
printf("%d\n", (*m1)[4]);    // 5              
// 第四种              
printf("%d\n", *(*m1+4));    // 5              
// 第五种              
printf("%d\n", *(m1[0])+4);    // 5              
printf("%d\n", *(m1[2])-2);    // 5              
// 第六种              
printf("%d\n",*(*m1+4));    // 5

         

10.7 指针和字符串    

字符串和指针    

我们先来看一段代码:

#include
int main()        
{        
    char *s1="abcde";        
    char s2[]={"abcde"};        
    printf("%s,%c%s,%c\n",s1,*s1,s1+1,s1[1]);        
    printf("%s,%c,%s,%c\n",s2,*s2,s2+1,s2[1]);        
    return 0;        
}


运行结果为:

abcde,abcde,b
abcde,a,bcde,b

注意:

输出一个字符和输出字符串的区别。

当用指针时,如s1,s1+1,s2等表示一个字符串,该字符串从指针所指字符开始直至字符串结束标志’\0’;而当用*s1,s1[1],*(s1+1),s2[0]等时,表示的是一个字符,即指针所指的字符或位于该下标的字符元素。由此可见,字符数组和字符指针在使用上是相似的。但是两者又是有区别的

字符指针    

定义

字符串指针变量本身是一个变量,用于存放字符串的首地址。而字符串本身是存放在以该首地址为首的一块连续的内存空间中并以 \0 作为串的结束。

char *ps="C Language";

顺序是:

1.分配内存给字符指针;

2.分配内存给字符串;

3.将字符串首地址赋值给字符指针;

char *ps;  // ps 字符串指针,是指针,是一个变量              
ps="C Language"; /* ps 为字符串的首地址,利用 ps++ 可遍历字符串,字符串存储              
在以 ps 为开始地址的地段连续的内存空间中,并以 \0 作为字符串的结束。*/              

这里有两点需要考虑清楚的地方:

1.*a只是指向一个字符

#include
#include
int main(void){         
    char *a= "bcd" ;         
    printf("输出字符:%c \n", *a);  /*输出字符,使用"%c"*/        
    printf("输出字符:%c \n", *(a+1) );  /*输出字符,使用"%c"*/        
    printf("输出字符串:%s \n", a); /*输出字符串,使用"%s";而且a之前不能有星号"*"  */        
    system("pause");  /*为了能看到输出结果*/        
}


     

运行结果:

输出字符:b

输出字符:c

输出字符串:bcd

2.若字符串常量出现在表达式中,代表的值为该字符串常量的第一个字符的地址。所以"hello"仅仅代表的是其地址。原声明方式相当于一下声明方式:

char *a;              
a="hello";/"hello"仅代表第一个字符的地址
字符数组    

字符数组是由若干个数组元素组成的,它可用来存放整个字符串(即用字符数组来存放字符串)。在C语言中,将字符串作为字符数组来处理(c++中不是)。

(1)可以用字符串常量来初始化字符数组:

char str[]={"hello"};

也可以省略花括号:

char str[]="hello"; /系统自动加入\0

注意:上述这种字符数组的整体赋值只能出现在字符数组初始化时使用,不能用于字符数组的赋值,字符数组的赋值只能对其元素一一赋值。

下面的赋值方法是错误的:

char str[20];
str="hello";

在C语言中,可以用两种方法表示和存放字符串:

char a[]="hello"; /用字符数组存放一个字符串
char *a="hello"; /用字符指针指向一个字符串

两种表示方式的字符串输出都用:printf("%s",a);

%s表示输出一个字符串,给出字符指针变量a(对于第一种表示方法,字符数组名即是字符数组的首地址,与第二种中的指针意义是一致的),则系统先输出它所指向的一个字符,然后使a自动加1,使之指向下一个字符,纸到遇到字符串结束标志符\0。

字符串指针    
string* str可以赋值:
string* str = {"hello", "world"};              
//  对比与 char *name = "wang" = {'w','a','n','g'}              
//  *(str) = "hello", *(str+1) = "world"              
//  *(*(str)+1) = 'e'

也就是说每个元素都是string类型的,跟char是不一样的,不过string可以用char**来代替:

string=char*;              
string*=char**;

           

(字符串)指针数组,实例:    

1、

#include
void main()        
{        
    char *str[] = {"Hello", "C++", "World"}; //char (*str)[] = ...        
    int i;        
    for(i=0; i<3; i++)        
        printf("%s\n", str[i]);        
}        
// str[0]字符串"hello"的首地址,str[0]+1:字符串"hello"第二个字符'e'的地址,str[2]=str+2:第三个字符串"world"的首地址        
// str[1]字符串"C++"的首地址        
// str[2]字符串"world"的首地址        
或者:
#include
#include
int main()        
{        
    char *str[3]={"Hello","C++","World"};        
    printf("%s,%s,%c",str[0],str[0]+1,*(*(str+2)+1));        
}

结果:

Hello ello o

char *a[]:表示哦a是数组,数组中的元素是指针,指向char类型,(数组里面所有的元素是连续的内存存放的),数组名是数组第一个字节的内存地址,并且数组名a也表示指针。所以a并不表示a地址存储的内容,而是a地址本身。

a+1:表示a的第二个元素的内存地址,所以是加8字节。(因为a的元素是char指针,所需要的空间为8字节(64位))

*(a+1):表示a这个数组的第二个元素的内容(是个char类型的指针,本例表示world字符串的地址)

*(*(a+1)):表示a这个数组的第二个元素的内容(char指针)所指向的内容(w字符)

char * a[10]:表示限定这个数组最多可存放10个元素(char指针),也就是说这个数组占用10*8=80个字节

     

a+1

=>

*(a+1)

=>

*(a+1)[0]

指针(地址)

                     

指针内容(字符串)

                     

字符

char *argv:理解为字符串

char **argv:理解为字符串指针

char *argv[]:字符串指针数组

C语言有两种表示字符串的方法,一种是字符数组,另一种是字符串常量,它们在内存中的存储位置不同,使得字符数组可以读取和修改,而字符串常量只能读取不能修改。


10.8 指针数组和多级指针  

 

定义:    
int *p[5];

定义了一个指针数组,每一个元素均为一个指向整型值的指针。如果没有进行初始化,则每个元素均为无效指针。

可以在定义的时候对指针进行初始化,也可以通过以下循环的方式进行一一配对。

int a[10],i,*p[10];              
for(i=0;i<10;i++)              
    p[i] = &a[i];

 

例:

不改变数组中各个元素的顺序,按照从小到大的次序来输出数组值。

#include
#define n 10        
int main()        
{        
    int a[n],*p[n],i,j,*pt = a;        
    for(i=0;i        <n-1;i++)        
    {        
        scanf("%d",pt);        
        p[i] = pt++;        
    }        
    for(i=0;i         <n;i++)        
        for(j=0;j          <n-i-1;j++)        
            if(*p[j]>*p[j+1])        
                pt = p[j],p[j] = p[j+1],p[j+1] = pt;        
    for(i=0;i           <n;i++)        
        printf("%d\t",*p[i]);        
    return 0;        
}        
          </n;i++)        
         </n-i-1;j++)        
        </n;i++)        
       </n-1;i++)
二级指针与多级指针    

二级指针举例

我们对一个变量i = 4,可以直接通过i来访问这个变量,也可以通过指针*pi = 4,也可以通过二级指针**pi来访问

int i=4;              
int *pi = &i;              
int **ppi = π

上面这三种定义都可以访问到i这个变量。

二级数组用于操作指针数组十分方便。

相关文章
|
10月前
|
C语言
C语言练习
C语言练习。
54 1
|
2月前
|
自然语言处理 算法 Linux
C语言:深入探索与实际应用
C语言,作为计算机科学基石,以其通用性、过程式编程和底层访问能力在系统编程、嵌入式开发等领域广泛应用。其支持结构化编程、变量作用域和递归,提供高效代码编写方式。数据类型(如整型、浮点型、字符型)、控制结构(如if-else、循环)、函数和指针是C语言的关键特性。指针允许直接操作内存,增强了灵活性。C语言在操作系统(如Linux内核)、嵌入式系统、游戏开发及数据结构与算法实现中扮演重要角色,是理解和学习其他编程语言的基础。通过深入学习C语言,开发者可以提升技能,适应各种编程挑战。
|
2月前
|
C语言
C语言中%c,%d,%x,%f,%4.2f分别是什么
C语言中%c,%d,%x,%f,%4.2f分别是什么
|
2月前
|
存储 编译器 C语言
C语言:练习4
C语言:练习4
35 3
C语言:练习4
|
2月前
|
双11 C语言
C语言:练习2
C语言:练习2
41 0
|
11月前
|
编译器 C语言 C++
初识c语言
一个程序运行起来如果报错或者结果是上一次运行程序的结果那么就是你写的程序有问题,可以用cursor这个编码chatgpt这个软件看一下那里出现的问题,但是要保证自己的代码是完整的我们在vs2022中要创建的源文件,头文件是用来存放自定义函数的。因为我们学习的是c语言所以我们创建的文件的以.c结尾的,不能是.cpp,因为c语言的语法规则和c++的语法规则不一样,要不然写出来的程序会报错。printf()函数在程序的函数体内实现,printf(“”)是负责打印(“”)里面的字符。scanf()函数的使用。
|
2月前
|
C语言 C++
C语言集106-111
C语言集106-111
|
8月前
|
C语言 C++
C语言的初步简单认识
C语言的初步简单认识
80 1
|
8月前
|
编译器 C语言 C++
初识C语言(2)下
初识C语言(2)
104 0
|
8月前
|
自然语言处理 Java 编译器
零基础玩转C语言-初识C语言(上)
零基础玩转C语言-初识C语言(上)
102 0