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 是一个函数指针,在前面加 * 就表示对它指向的函数进行调用。注意( )
的优先级高于*
,第一个括号不能省略。
-
指针变量可以进行加减运算,例如
p++
、p+i
、p-=i
。指针变量的加减运算并不是简单的加上或减去一个整数,而是跟指针指向的数据类型有关。 -
给指针变量赋值时,要将一份数据的地址赋给它,不能直接赋给一个整数,例如
int *p = 1000;
是没有意义的,使用过程中一般会导致程序崩溃。 -
使用指针变量之前一定要初始化,否则就不能确定指针指向哪里,如果它指向的内存没有使用权限,程序就崩溃了。对于暂时没有指向的指针,建议赋值
NULL
。 -
两个指针变量可以相减。如果两个指针变量指向同一个数组中的某个元素,那么相减的结果就是两个指针之间相差的元素个数。
-
数组也是有类型的,数组名的本意是表示一组类型相同的数据。在定义数组时,或者和 sizeof、& 运算符一起使用时数组名才表示整个数组,表达式中的数组名会被转换为一个指向数组的指针。
参考引用:c.biancheng.net/view/2026.h… C语言中文网c语言教程,感谢站长严长生!