c语言知识点2

54 阅读10分钟

C语言知识点2

1.字符串结束标志

在C语言中,字符串总是以'\0'作为结尾,所以'\0'也被称为字符串结束标志,或者字符串结束符。

当用字符数组存储字符串时,要特别注意'\0',要为'\0'留个位置;这意味着,字符数组的长度至少要比字符串的长度大 1。请看下面的例子:

char str[7] = "abc123";

" "包围的字符串会自动在末尾添加'\0'。例如"abc123"从表面看起来只包含了 6 个字符,其实不然,C语言会在最后隐式地添加一个'\0',这个过程是在后台默默地进行的,所以我们感受不到。 "abc123"看起来只包含了 6 个字符,我们却将 str 的长度定义为 7,就是为了能够容纳最后的'\0'。如果将 str 的长度定义为 6,它就无法容纳'\0'了。

2.冒泡排序

#include <stdio.h>

int main(){
    int num[10]={1,3,5,3,6,7,34,8,9,4}
    int i, j, temp;
    //执行n-1轮比较
    for(i = 0, i < 9; i++){
        //每一轮执行n-1-i次比较,已经排序好的最后i个不用比较
        for(j = 0, j < 9-i; j++){
            if(num[j] > num[j+1]){
            temp = num[j];
            num[j] = num[j+1];
            num[j+1] = temp;
            }
        }
    }
//输出排序后的数组
for(i = 0; i < 10; i++ ){
    printf("%d", num[i]);
}
printf("\n");
return 0;
}

3.不同的平台下引入不同的头文件

不同的平台下必须调用不同的函数,并引入不同的头文件,否则就会导致编译错误,因为 Windows 平台下没有 sleep() 函数,也没有 <unistd.h> 头文件,反之亦然。这就要求我们在编译之前,也就是预处理阶段来解决这个问题。请看下面的代码:

#include <stdio.h>
//不同的平台下引入不同的头文件#if _WIN32  
//识别windows平台#include <windows.h>#elif __linux__  
//识别linux平台
#include <unistd.h>
#endif
int main() {    
    //不同的平台下调用不同的函数    
    #if _WIN32  //识别windows平台    
    Sleep(5000);    
    #elif __linux__  //识别linux平台    
    sleep(5);    
    #endif    
    puts("http://c.biancheng.net/");    
    return 0;
}

#if、#elif、#endif 就是预处理命令,它们都是在编译之前由预处理程序来执行的。这里我们不讨论细节,只从整体上来理解。

define与typedef区别

应注意用宏定义define表示数据类型和用typedef定义数据说明符的区别。宏定义只是简单的字符串替换,由预处理器来处理;而 typedef 是在编译阶段由编译器处理的,它并不是简单的字符串替换,而给原有的数据类型起一个新的名字,将它作为一种新的数据类型。

请看下面的例子:

#define PIN1 int *
typedef int *PIN2;  //也可以写作typedef int (*PIN2);

从形式上看这两者相似, 但在实际使用中却不相同。

下面用 PIN1,PIN2 说明变量时就可以看出它们的区别:

PIN1 a, b;

在宏代换后变成:

int * a, b;

表示 a 是指向整型的指针变量,而 b 是整型变量。然而:

PIN2 a,b;

表示 a、b 都是指向整型的指针变量。因为 PIN2 是一个新的、完整的数据类型。由这个例子可见,宏定义虽然也可表示数据类型, 但毕竟只是简单的字符串替换。

4.指针

定义指针变量必须带*,给指针变量赋值时不能带*。 :

//定义普通变量
float a = 99.5, b = 10.6;
char c = '@', d = '#';
//定义指针变量
float *p1 = &a;
char *p2 = &c;
//修改指针变量的值
p1 = &b;
p2 = &d;

*是一个特殊符号,表明一个变量是指针变量,定义 p1、p2 时必须带*。而给 p1、p2 赋值时,因为已经知道了它是一个指针变量,就没必要多此一举再带上*,后边可以像使用普通变量一样来使用指针变量。也就是说,定义指针变量时必须带*,给指针变量赋值时不能带*

5.C语言表示字符串的两种方法(字符数组与字符常量)

使用指针的方式来输出字符串:

#include <stdio.h>
#include <string.h>
int main(){    
    char str[] = "http://c.biancheng.net";    
    char *pstr = str;    
    int len = strlen(str), i;    

    //使用*(pstr+i)  字符数组  
    for(i=0; i<len; i++){        
    printf("%c", *(pstr+i));    
    }    
    printf("\n");    

    //使用pstr[i]    字符常量
    for(i=0; i<len; i++){        
    printf("%c", pstr[i]);    
    }    
    printf("\n");    
    //使用*(str+i)    
    for(i=0; i<len; i++){        
    printf("%c", *(str+i));    
    }    
    printf("\n");    
    return 0;
}

运行结果:
c.biancheng.net
c.biancheng.net
c.biancheng.net

5.1字符串常量

意思很明显,常量只能读取不能写入。请看下面的演示:

#include <stdio.h>
int main(){    
    char *str = "Hello World!";    
    str = "I love C!";  //正确    
    str[3] = 'P';  //错误    
    return 0;
}

这段代码能够正常编译和链接,但在运行时会出现段错误(Segment Fault)或者写入位置错误。

第4行代码是正确的,可以更改指针变量本身的指向;第5行代码是错误的,不能修改字符串中的字符。

在编程过程中如果只涉及到对字符串的读取,那么字符数组和字符串常量都能够满足要求;如果有写入(修改)操作,那么只能使用字符数组,不能使用字符串常量。 字符串常量只能读取,字符串数组既能读取也能写入。

6.指针变量作为函数参数

有些初学者可能会使用下面的方法来交换两个变量的值:

#include <stdio.h>
void swap(int a, int b){    
    int temp;  //临时变量    
    temp = a;    
    a = b;    
    b = temp;
}

int main(){
    int a = 66, b = 99;
    swap(a, b);
    printf("a = %d, b = %d\n", a, b);    
    return 0;
}

运行结果:
a = 66, b = 99

从结果可以看出,a、b 的值并没有发生改变,交换失败。这是因为 swap() 函数内部的 a、b 和 main() 函数内部的 a、b 是不同的变量,占用不同的内存,它们除了名字一样,没有其他任何关系,swap() 交换的是它内部 a、b 的值,不会影响它外部(main() 内部) a、b 的值。

改用指针变量后很容易解决上面的问题

#include <stdio.h>

void swap(int *pa, int *pb){
    int temp;
    temp = *pa;
    *pa = *pb;
    *pb = temp;
}

int main(){
    int a = 3, b = 4;
    swap(&a, &b);
    printf("a = %d, b = %d\n", a, b);
    return 0;
}

运行结果:
a = 99, b = 66

调用 swap() 函数时,将变量 a、b 的地址分别赋值给 p1、p2,这样 *p1、*p2 代表的就是变量 a、b 本身,交换 *p1、*p2 的值也就是交换 a、b 的值。函数运行结束后虽然会将 p1、p2 销毁,但它对外部 a、b 造成的影响是“持久化”的,不会随着函数的结束而“恢复原样”。

6.1用数组做函数参数

数组是一系列数据的集合,无法通过参数将他们一次性传递到函数内部,如果希望在函数内部操作数组,必须传递数组指针。下面定义了一个函数max,用来查找数组中值最大的元素:

#include <stdio.h>

int max(int *intArr, int len){
    int i, maxValue = intArray[0];//假设第零个元素是最大值
    for(i=1,i<len; i++){
        if(maxValue < intArr[i]){
        maxValue = intArray[i];
        }
        
     int main(){
         int nums[6], i;
         int len = sizeof(nums)/sizeof(int);
         
         //读取输入的数据并赋值给数组元素
         for(i = 0; i <len; i++){
             scanf("%d", nums+i);
         }
         printf("max value is %d\n", max(nums, len));
     return 0;
     }
    }
}

运行结果:
12 55 30 8 93 27↙
Max value is 93!

参数 intArr 仅仅是一个数组指针,在函数内部无法通过这个指针获得数组长度,必须将数组长度作为函数参数传递到函数内部。 数组 nums 的每个元素都是整数,scanf() 在读取用户输入的整数时,要求给出存储它的内存的地址,nums+i就是第 i 个数组元素的地址。

7.c语言指针作为函数返回值

c语言允许函数的返回值是一个指针(地址),我们将这样的函数称为指针函数。 下面的例子定义了一个函数 strlong(),用来返回两个字符串中较长的一个:

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

char *strlong(char *str1, char *str2){    if(strlen(str1) >= strlen(str2)){        
    return str1;    
    }else{        
    return str2;    
    }
}
int main(){    
    char str1[30], str2[30], *str;    
    gets(str1);    
    gets(str2);    
    str = strlong(str1, str2);    
    printf("Longer string: %s\n", str);    
    return 0;
}

运行结果:

C Language↙
c.biancheng.net↙
Longer string: c.biancheng.net

用指针作为函数返回值时需要注意的一点是,函数运行结束后会销毁在它内部定义的所有局部数据,包括局部变量、局部数组和形式参数,函数返回的指针请尽量不要指向这些数据,C语言没有任何机制来保证这些数据会一直有效,它们在后续使用过程中可能会引发运行时错误。

8.二维数组指针(指向二维数组的指针)

为了更好的理解指针和二维数组的关系,我们先来定义一个指向 a 的指针变量 p:

int (*p)[4] = a;

括号中的*表明 p 是一个指针,它指向一个数组,数组的类型为int [4],这正是 a 所包含的每个一维数组的类型。

[ ]的优先级高于*( )是必须要加的,如果赤裸裸地写作int *p[4],那么应该理解为int *(p[4]),p 就成了一个指针数组 (数组每个元素都是指针),而不是二维数组指针。

数组名 a 在表达式中也会被转换为和 p 等价的指针!

下面我们就来探索一下如何使用指针 p 来访问二维数组中的每个元素。按照上面的定义:
1) p指向数组 a 的开头,也即第 0 行;p+1前进一行,指向第 1 行。

2) *(p+1)表示取地址上的数据,也就是整个第 1 行数据。注意是一行数据,是多个数据,不是第 1 行中的第 0 个元素,下面的运行结果有力地证明了这一点:

#include <stdio.h>int main(){    int a[3][4] = { {0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11} };    int (*p)[4] = a;    printf("%d\n", sizeof(*(p+1)));    return 0;}

运行结果:
16

3) *(p+1)+1表示第 1 行第 1 个元素的地址。如何理解呢?

*(p+1)单独使用时表示的是第 1 行数据,放在表达式中会被转换为第 1 行数据的首地址,也就是第 1 行第 0 个元素的地址,因为使用整行数据没有实际的含义,编译器遇到这种情况都会转换为指向该行第 0 个元素的指针;就像一维数组的名字,在定义时或者和 sizeof、& 一起使用时才表示整个数组,出现在表达式中就会被转换为指向数组第 0 个元素的指针。

4) *(*(p+1)+1)表示第 1 行第 1 个元素的值。很明显,增加一个 * 表示取地址上的数据。

根据上面的结论,可以很容易推出以下的等价关系:

a+i == p+i
a[i] == p[i] == *(a+i) == *(p+i)
a[i][j] == p[i][j] == *(a[i]+j) == *(p[i]+j) == ((a+i)+j) == ((p+i)+j)

【实例】使用指针遍历二维数组。

#include <stdio.h>
int main(){    
    int a[3][4]={0,1,2,3,4,5,6,7,8,9,10,11};         int(*p)[4];    
    int i,j;    
    p=a;    
    for(i=0; i<3; i++){        
        for(j=0; j<4; j++) {
        printf("%2d  ",*(*(p+i)+j));
        }
        printf("\n");    
    }    
return 0;
}

运行结果:

 0   1   2   3
 4   5   6   7
 8   9  10  11

· 指针数组和二维数组指针的区别
指针数组和二维数组指针在定义时非常相似,只是括号的位置不同:

int *(p1[5]); //指针数组,也可以去掉括号int *p1[5];
int *(p2)[5]; //二维数组指针,不能去掉括号,中括号的优先级比*号高

9.函数指针(指向函数的指针)

【实例】用指针来实现对函数的调用。

#include <stdio.h>//返回两个数中较大的一个
int max(int a, int b){    
    return a>b ? a : b;
    }
int main(){    
    int x, y, maxval;    //定义函数指针
    int (*pmax)(int, int) = max;  //也可以写作int (*pmax)(int a, int b)    
    printf("Input two numbers:");    
    scanf("%d %d", &x, &y);    
    maxval = (*pmax)(x, y);    
    printf("Max value: %d\n", maxval);    
    return 0;
}

运行结果:
Input two numbers:10 50↙
Max value: 50

第 14 行代码对函数进行了调用。pmax 是一个函数指针,在前面加 * 就表示对它指向的函数进行调用。注意( )的优先级高于*,第一个括号不能省略。

image.png

  1. 指针变量可以进行加减运算,例如p++p+ip-=i。指针变量的加减运算并不是简单的加上或减去一个整数,而是跟指针指向的数据类型有关。

  2. 给指针变量赋值时,要将一份数据的地址赋给它,不能直接赋给一个整数,例如int *p = 1000;是没有意义的,使用过程中一般会导致程序崩溃。

  3. 使用指针变量之前一定要初始化,否则就不能确定指针指向哪里,如果它指向的内存没有使用权限,程序就崩溃了。对于暂时没有指向的指针,建议赋值NULL

  4. 两个指针变量可以相减。如果两个指针变量指向同一个数组中的某个元素,那么相减的结果就是两个指针之间相差的元素个数。

  5. 数组也是有类型的,数组名的本意是表示一组类型相同的数据。在定义数组时,或者和 sizeof、& 运算符一起使用时数组名才表示整个数组,表达式中的数组名会被转换为一个指向数组的指针。

参考引用:c.biancheng.net/view/2026.h… C语言中文网c语言教程,感谢站长严长生!