《你必须知道的495个C语言问题》学习笔记
1.声明和定义全局变量和函数
一个全局变量或函数可以(在多个编译单元中)有多处的声明,但是定义却最多只能允许出现一次。对于全局变量,定义是真正分配空间并赋初始值(如果有)的声明。
static修饰的在C是不能被其他文件访问调用的。
2.typedef和#define 有什么区别
先看实例:
typedef char *string_t;
#define string_d char *;
string_t s1,s2;
string_d s3,s4;
s1,s2,s3都被定义成char *,但s4却被定义成char型,这里就可以看出typedef和#define的区别,当然#define也有优点,因为可以在#define中使用#ifdef。另一方面typedef具有遵守作用域的规则的优点(可以在函数和块内声明)。
3.定义一个链表
typedef struct{
char* item;
node_ptr next;
}*node_ptr;
编译会报错,因为不能在定义typedef类型之前使用它,下面附上修正版本:
typedef struct node{
char *item;
struct node *next;
}*node_ptr;
或者
struct node;
typedef struct node *node_ptr;
struct node{
char *item;
node_ptr next;
};
以及
struct node{
char *item;
struct node *next;
}
typedef struct node *node_ptr;
其中在C++编译器环境中使用可以使用node直接表示struct node,C不是C++,不能用结构标签自动生成类型定义名。
4.sizeof
在文件file1.c中定义数组
int array[]={1,2,3};
在文件file2.c中使用
extern int array[];
但在file2.c中sizeof取不到array的大小?
未指定大小的extern数组是不完全类型。不能对它使用sizeof,因为sizeof在编译时发生作用,它不能获得定义在另一个文件中的数组大小。
解决方法:1.在file1.c中定义一个变量保存数组的大小:int array_size=sizeof(array);然后在file2.c中使用array_size即可。2.为数组定义明白无误的常量(数组维度大小),以便在定义和extern声明中都可以一起地使用:在file1.h中 #define array_size 3; 。3.在数组最后一个元素放入一个值(通常是-1,0或NULL)这样不需要数组大小也可以确定数组的长度:file1.c:
int array[]={1,2,3,-1}; file2.c: extern int array[];
sizeof操作符如果能判断数组的大小,它就会返回数组的大小。如果数组的大小位置或者数组已经退化为指针,则它不能提供数组的大小。
sizeof返回的大小是以字节计算的,怎样才能判断数组中有多少个元素?
只需要用一个元素的大小去除整个数组的大小即可:
int array[]={1,2,3};
int array_size=sizeof(array)/sizeof(array[0]);
5.函数只定义了一次,调用了一次,但编译器提示非法重声明了。
在作用域内没有声明就调用的函数被认为声明为:
extern int f();
即未声明的函数被认为返回int型且参数个数未知(其参数数量必须固定,且不能有“窄”类型)。如果之后函数定义不用,则编译器就会警告类型不符。返回非int 型,接受任何“窄”类型参数或可变参数的函数都必须在调用前声明。
6.没有显式初始化的变量初始值怎样假定
具有static生存期的为初始化变量(包括数组和结构)——即在函数外声明的变量和具有静态存储类型的变量可以确保初始化值为零,如果是指针就会初始化为正确类型的空指针。
非静态类型的局部变量如果没有显式地初始化,则包含的是垃圾内容。对垃圾内容不能作任何有用的假定。
7.下面代码为什么不能编译?
int f()
{
char a[]=”Hello, World”;
}
可能使用的是ANSI前的编译器,还不支持自动聚集(非静态局部数组、结构和数组)的初始化。
解决方法:1.使用指针代替它;2.使用strcopy函数手工初始化 char a[14]; strcopy(a,”hello,world!”);3.当然换一个兼容ANSI的容器。
8.下面初始化有什么问题?(这个的意思应该是不能在main函数调用之前调用malloc函数)
char *p=malloc(10);
这个声明是静态或非局部变量吗?函数调用只能出现在自动变量(局部非静态变量)的初始化中。从这我们可以猜测:全局变量是在main函数调用之前就初始化了。
9.以下初始化有什么区别?
char a[]=”string”;
char *p=”string”;
当向p[i]赋值的时候,程序就崩溃了。
字符串字面量——C语言源程序中用双引号包含的字符串的正式名称,有两种稍有区别的用法:
- 用作数组初始值,它指明该数组中字符的初始值。
- 其他情况下,它会转化为一个无名的静态字符数组,可能会存储在只读内存中,这就导致它不能被修改。
10.为什么这样的代码不行?
a[i]=i++;
子表达式i++有一个副作用——改变i的值——由于i在同一个表达式的其他地方被引用,因此这会导致未定义的行为。无从判断该引用(左边的a[i]中)是旧值还是新值。
怎样才能理解复杂表达式并避免写出未定义的表达式?
ANSI/ISO C标准这样描述:
在上一个序列点和下一个序列点之间(同一个语句内),一个对象所保存的值至多只能被表达式的求值修改一次。而且只有在确定将要保存的值的时候才能访问前一个值。
第一句话就排除了两个例子i++*i++;(或((i++)*(i++)这里的括号是没有用的)和i=i++;这两个表达式的i的值都被修改了两次。
第二句话要表达的意思:如果某个对象需要写入一个完整表达式中,则在同一表达式中对该对象的访问应该只局限于用了计算将要写入的值。a[i]=i++;非法是因为对i的一处访问(a[i])与最终存储在i中的值毫无关系。这样导致没有什么好办法来决定这次访问是应该放到i的自增之前还是之后来进行。可移植程序中就不应该使用这样的语句。
11.空指针到底是什么?
空指针不会指向任何地方,它不是任何对象或函数的地址。取地址操作符&永远也不会返回空指针。同样对malloc的成功调用也不会返回空指针。
空指针在概念上不同于未初始化的指针。空指针可以确保不指向任何对象或函数,而未初始化的指针则可能指向任何地方。每种指针类型都有一个空指针。
NULL可以合法地用作函数指针。
有两条简单规则必须遵循:
- 当在源码中需要空指针常量时,用“0”或“NULL”;
- 如果在函数调用“0”或“NULL”用作参数,把它转换成被调用函数需要的指针类型。
12.在一个源文件中定义了char a[6];,在另一个源文件中声明了 extern char * a;,为什么不行?
类型T的指针和类型T的数组并非同种类型。使用extern char a[];即可。
一个T数组类型的对象如果出现在表达式中会退化为一个指向数组第一个元素的指针(有3种情况例外),指针的类型是指向T的指针。
下面代码是可以的:
int f(char str[])
{
if(str[]==’\0’)
str=”none”;
}
在这段代码中,str是个函数参数,它的声明被编译器重写了,换言之,str是个char *的指针。
左值并不完全表示“能赋值的东西”,更好的定义应该是“在内存中有特定位置的东西”。
13.数组和指针的区别是什么?
数组是一个由(同一类型)连续元素组成的预先分配的内存块,指针是一个对任何位置的元素的引用。
数组自动分配空间,但不能重分配或改变大小。指针必须被赋值以指向分配的空间,可以随意重新赋值。
指针可以模拟数组;几乎没有所谓数组的东西,实际是个指针操作符。
14.为什么这段代码不行?
char *answer; //全局变量也不行
printf(“Type something:\n”);
gets(answer);
printf(“You typed \”%s\”\n”,answer);
传入gets()的指针变量answer,意在指向保存得到应答的位置,但它没有指向任何合法的位置。它是个未初始化的变量。可以使用局部数组,让编译器去操心内存分配问题。
15.我的strcat()不行,我试下下面的代码:
char *s1=”Hello, “;
char *s2=”world”;
char *s3=strcat(s1,s2);
strcat不进行内存分配,所以s1的内存空间不足,还有s1指向字符串字面量,不一定可写。
我试了这样的代码:
char *p;
strcpy(p,”abc”);
能正常运行,怎么回事?
这说明运气好,未初始化的指针p所指向的随机地址恰好是可写的,而且显然没有什么关键的数据。
还看下面代码:
char linebuf[90];
char *lines[100];
int I;
for(i=0;i<100;i++)
{
char *p=fgets(linebuf,90,fp);
if(p==NULL)break;
lines[i]=p;
}
为什么读入的每一行都是最后一行的内容?
其实仔细分析就可以知道lines[i]始终指向linebuf的内容,linebuf只有最后一行的内容。
16.在C语言中的字符常量是int型,所以sizeof(‘a”)是sizeof(int),这是另一个与C++不同的地方,但sizeof(char)严格是1。
sizeof不能用在#if预处理指令中,预处理在编译过程之前进行,此时尚未对类型名称进行分析。
17.为什么不能在初始化和数组的维度中使用const值?
const int n=5;
int a[n];
const限定词真正含义是“只读”,用它限定对象通常是运行时不能被赋值的对象。因为用const限定词的对象的值并不完全是一个真正的常量,不能用作数组维度、case行标或类似环境。这点C和C++不一样。如果需要真正的编译环境时,使用预处理宏#define(或enum)。
typedef char *char_ptr;
const char_ptr p; //不同于 const char * p;
p指针本身是const,typedef的替换并不完全基于文本的。
#pragram once指令会确保内容只被处理一次。
char a[3]=”abc”;合法吗?
尽管在极其有限的环境下有用,可它在ANSI C中是合法的。因为它没有终止的’\0’字符,不是一个真正的C字符串,从而不能在strcpy,sprintf %s等语句中。
多数时候,应该让编译器计算数组初始化的初始值个数,在初始值”abc”中,计算得到长度当然应该是4。
18.为什么%f可以在printf参数中同时表示float和double?难道它们是同一个类型吗?
可变参数的可变部分使用“默认参数提升”:char和short int 提升到int,float提升到double。同样的提升也适用于作用域中没有原型说明的函数调用。
与printf不同,scanf用%lf代表double型,用%f代表float型。
19.怎样写一个接受可变参数的函数?
用<stdarg.h>提供的辅助机制