以这一段代码为例
int main(int argc, char *argv[])
{
int a = 10, b = 20, c = 30;
printf("%d\n", printf("%d ", a+b+c));
return 0;
}
仔细一看,这个程序特殊的地方就是在printf函数里加了一个printf函数。那么,这个程序会输出什么呢?
答案就是60 3
让我们来解读一下
第一步:第一个printf的输出结果是60,这很好理解。
第二布:第二个printf的输出结果为什么会是3呢?这是因为printf函数是有返回值的,它返回一个int值,表示被打印的字符数,因此返回的是3。(60有两个字符加上一个空格)
int main(int argc, char *argv[])
{
int t = 4;
printf("%lu\n", sizeof(t--));
printf("%d\n", t);
printf("%lu\n", sizeof("ab c\nt\012\xa1*2"));
printf("%lu\n",strlen("ab c\nt\012\xa1*2"));
return 0;
}
这段代码的输出结果分别是4 4 11 10。
因为sizeof操作符在编译时就已经起作用。所以sizeof后括号内t的自减为无效操作,因此前两个均输出4。
而后面字符串ab c\nt\012\xa1*2之所以长度为10的原因是所有的ASCII码都可以用“\”加数字(一般是8进制数字)来表示。而C中定义了一些字母前加“\”来表示常见的那些不能显示的ASCII字符,如\0,\t,\n等,就称为转义字符,因为后面的字符,都不是它本来的ASCII字符意思了。而在\之后三个数字若均为阿拉伯数字则用8进制数字表示,之后为x与两个字符的组合则用16进制数字来表示一个字符,例如\101表示为A,\x41也表示为A。
static在c语言中有三个用法
1.static 函数类型 函数名(函数参数表){……}
此用法可定义内部函数,内部函数的作用域仅局限于本源文件。
使用内部函数的好处是:不同的人编写不同的函数时,不用担心自己定义的函数,是否会与其它文件中的函数同名,因为同名也没有关系。
2.static 全局变量
此用法可定义静态全局变量,与第一种用法的作用大同小异,作用域仅局限于本源文件。
好处也是不用担心同名。
3.static 局部变量
此用法可定义静态局部变量,静态变量只初始化一次,作用域与局部变量作用域相同,但是生存周期同全局变量相同。
最后对static的三条作用做一句话总结。首先static的最主要功能是隐藏,其次因为static变量存放在静态存储区,所以它具备持久性和默认值0。
const char *p;
char const *p;
char *const p;
以上三个表达式1与2的意思是相同的,表示指针p所指变量的值不能被改变。
式子3的意思是指针p所值的变量不能被改变,即指针变量p的值为一个const值。
我们可以这样理解*p可以表示为p所值的变量,前面加上const,则 *p的值是一个const值,即p所指变量的值为const。const加在指针p之前,则表示p的值为一个const的值,即p只能指向当前所指向的变量。
unsigned int a = 10;
int b = -20;
if (a + b > 0)
{
printf("a+b = %d\n", a+b);
}
else
{
printf("a = %d b = %d\n", a, b);
}
以上代码输出结果是a+b=-10,之所以会有这样的输出结果是因为不同类型的数据在进行运算时发生了隐式自动类型转换。
隐式自动转换规则
1、执行算术运算时,低类型(短字节)可以转换为高类型(长字节);例如: int型转换成double型,int转为unsigned int型,char型转换成int型等等;
2、赋值表达式中,等号右边表达式的值的类型自动隐式地转换为左边变量的类型,并赋值给它;
3、函数调用时,将实参的值传递给形参,系统首先会自动隐式地把实参的值的类型转换为形参的类型,然后再赋值给形参;
4、函数有返回值时,系统首先会自动隐式地将返回表达式的值的类型转换为函数的返回类型,然后再赋值给调用函数返回;
struct icd
{
int x;
char y;
double z;
};
struct idc
{
int x;
double y;
char z;
};
以上代码中两个结构的所占字节大小分别是16和24。
这里牵扯到结构在内存中的字节对齐,字节对齐的细节和具体编译器实现相关,但一般而言,满足三个准则:
1.结构体变量的首地址能够被其最宽基本类型成员的大小所整除;
2.结构体每个成员相对于结构体首地址的偏移量都是成员大小的整数倍,如有需要编译器会在成员之间加上填充字节。
3.结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要编译器会在最末一个成员之后加上填充字节。
大端模式,是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中,这样的存储模式有点儿类似于把数据当作字符串顺序处理:地址由小向大增加,而数据从高位往低位放;这和我们的阅读习惯一致。
小端模式,是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中,这种存储模式将地址的高低和数据位权有效地结合起来,高地址部分权值高,低地址部分权值低。
从图中可以看到,虽然这三个表达式的值在数值上是一样的,但是在它们分别作+1运算后,所得到的值是完全不同的。
我们知道指针是一个值为地址的变量,所以我们可以把这些值认为是指针的值,而&a+1,&a[0]+1,&a[0][0]+1则可以认为是指针作加一的运算。指针加一的运算规则就是在指针的值上加一个指针所指变量的单位,比如有一个int数组,指针p指向这个数组的首元素,指针p的值是数组的第一个int变量的地址,则p+1的结果就是数组的第二个int变量的地址。
我们再说明一个知识,一维数组可以malloc一段内存然后赋值给一个指针实现,而二维数组可以以二级指针的方式实现,就是一个二级指针指向一级指针,多个一级指针分别指向一个元素。 因此我们可以以指针的方式来理解数组。
a是一个二维数组,可以理解为a是一个二级指针,而&a是这个二级指针的地址,则可以理解为是一个指向它的一个三级指针的值。因此再上述例子中&a值的类型为int***,它所指变量的类型int**,因此&a+1就是下一个int**的地址,也就是整个二维数组之后的那个地址。
同样的,a[0]是二维数组的首行,可以理解为一个一级指针,因此&a[0]的值是一个指向它的二级指针的值,则&a[0]值的类型为int**,所指变量类型为int*,所以&a[0]+1就是下一个int*的地址,也就是二维数组的第二行的地址。
a[0][0]是一个int类型的元素,&a[0][0]就是一个指向它的一级指针的值,则&a[0][0]值的类型为int*,所指变量类型是int,&a[0][0]+1就是下一个int的地址,也就是二维数组首行第二个变量的地址。
1.预处理
在这一阶段,源码中的所有预处理语句得到处理,例如#include语句所包含的文件内容替换掉语句本身,所有已定义的宏被展开,根据#ifdef,#if等语句的条件是否成立取舍相应的部分,预处理之后源码中不再包含任何预处理语句,生成.i文件。
2.编译
这一阶段,编译器对源码进行词法分析、语法分析、优化等操作,最后生成汇编代码.s文件。这是整个过程中最重要的一步,因此也常把整个过程称为编译。
3.汇编
这一阶段使用汇编器对汇编代码进行处理,生成机器语言代码,保存在后缀为.o的目标文件中。
4.链接
链接的主要内容是把各个模块之间相互引用的部分处理好,将各个目标文件链接生成.exe可执行文件。
int main(int argc, char *argv[])
{
printf("%lu\n", sizeof("abc"));
printf("%lu\n",strlen("abc"));
return 0;
}
以上代码的输出值分别为4与3。
sizeof的返回值是字符串的字节大小,字符串均是由’\0’结尾(系统会自动在字符串末尾加上’\0’),因此字符串的字节大小应该是字符串长度加上末尾的’\0’,也就是字符串长度加一。
strlen返回值是字符串的长度,因此不会加上末尾的’\0’。
我们先来了解一下函数指针。
函数指针是指向函数的指针,定义的一般形式如下
类型名 (*指针变量名) ();
例如:int (*p)(int a);
即指针p指向一个含有一个int的参数,且返回值为int值的函数。
size_t q(size_t b)
{
return b;
}
size_t (*p(char *str))(size_t a)
{
printf("%s\n", str);
return q;
}
int main(int argc, char *argv[])
{
char str[] = "XiyouLinuxGroup";
printf("%lu\n", p(str)(strlen(str)));
return 0;
}
我们再来分析以下以上程序
(1)程序先初始化一个字符数组str。
(2)我们先来分析以下size_t ( *p(char *str))(size_t a)这个函数,这是一个返回函数指针的函数,这个可能比较难以理解,我们首先抓住p,因为运算符()的优先级比 *高,所以p先与(char *str)结合。p(char *str)是含有一个char *类型参数的函数,这个函数的返回值比较特殊,它的返回值类型为size_t ( *)(size_t a),也就是一个函数指针, 该指针指向的函数含有size_t参数 。
(3)函数的调用,调用函数p并以str为实参,函数p返回指向函数q的指针,再通过指向函数q的指针调用函数q,调用完成后打印返回值。