C语言基础考试整理
以下内容是我对于在第一次考试的试题中出现的知识点做的一些归纳和总结。
常量不能被修改:
变量和常量。变量是先定义后使用还可以修改的量,常量是不可修改的量。
例如:int a = 10; 在这里a是变量,10是常量,a可以作为左值被修改,10不可以被修改。如下题:
char *p = “hello”;
*( p+1) = ‘w’;
以上程序最终是会崩溃的,执行错误。“hello”是一个字符串常量,指针p指向了该字符串常量的首字符‘h’,而第二行中对(p+1)进行解引用,意图将“hello”修改为“hwllo”,这句虽然没有语法错误,编译、链接都可以通过,可是字符串常量是不能被修改的,所以最终程序会执行错误、崩溃。
注意区分语句 char*str = “abcdefg”; 和语句 char str[] = “abcdefg”;
sizeof():
sizeof()函数是用来求其操作数的所占内存的字节数大小。尤其注意操作数为指针、数组的情况。如下题:
在某32位系统下,程序如下:
char str[] = ” http://www.xxxxxxxxx.com”;
char *p = str;
sizeof(str) = ? //(1)
sizeof(p) = ? //(2)
void foo(char str[100])
{
Sizeof(str) =?; //(3)
}
Void *p = malloc(100);
Sizeof(p) = ? //(4)
(1)中的str代表的是整个字符串(因为在同一个函数下,定义了字符串str,sizeof()中
str便代表整个字符串),所以结果为25。(2)中的p是一个指针,指向的是字符串的首位字符的地址,结果为4,因为32位系统下,指针变量占字节数为4。(3)中foo函数中的形参为一字符数组,可是实际上就退化成了指针str,指向数组的首元素地址,所以结果为4。(4)中p是一个无类型的指针,所以结果为4。
宏:谨记,字符替换。>,<,<=,>=,== 是双目运算符,需要连续比较。
#define Max(x) (10>x>2) ? (x+2) : (x+4)
Int main()
{
int a = 10;
int b = 0;
b = Max(a);
printf(“%d”,b);
a = 5;
b = Max(a) + 4;
printf(“%d\n”,b);
return 0;
}
问以上程序的运行结果。首先定义了一个宏Max(x),先进行字符替换,b = (10>a>2) ? (a+2) : (a+4),b = (10>a>2) ?(a+2) : (a+4) + 4,再与原语句进行比较:b = Max(a) + 4; ,这里并不是Max(a)的值加上4,而是展开之后,(a+9)加上4,所以为避免不必要的误会,最好先将宏替换回来。
在以上的分析的基础上,第一个printf语句输出的b值为(10>a>0) ? (a+2) : (a+4)的值,由于>是双目运算符,所以先计算10>10的值,为0(假),再计算0>2的值,为0,也就是假,所以最后表达式 (10>a>0)? (a+2) : (a+4) 的值为(a+4),为14。第二个printf语句输出的b值为(10>a>0) ? (a+2) : (a+4) + 4的值,同样,先计算10>5的值,为1(真),再计算1>2的值,为0,所以最后表达式 (10>a>2)? (a+2) : (a+4) + 4 的值为(a+4) + 4,为13。
小端和大端:
小端和大端对与数据的存储方式完全不用,小端设备是将低数据放置在低地址,大端设备是将高数据放置在低地址。所以小端设备在和大端设备进行信息转换时是需要遵循一定规则的,而小端设备有个人PC(inter),大端设备有手机(ARM),两者之间标准不同、是竞争关系且互不兼容。
Int main()
{
int a[4] = {2,3,2,3};
int *ptr1 = (int *)&a+1;
int *ptr2 = (int *)((int)a+1);
printf(“%x,%x\n”,*(ptr1-1),*(ptr2));
}
求以上程序的运行结果?首先我们定义了一个一维数组a[4],定义了两个指针变量ptr1和ptr2。在语句二中,我们需要明确一个问题,+1究竟是加一个数组长度还是加一个单元格的长度?首先对a取地址,这时a确实表示的是整个数组,可是又对&a进行了类型强转转为int*,这时a表示的就是数组首元素的地址,那么(int *)&a+1之后,ptr1就指向了数组第二个元素的地址,也就是ptr1指向了a[1]的地址,所有输出*(ptr1-1)的值为a[0],即2。
在语句三当中也对a进行了类型强转,使其变为了一个int型数据,+1的结果自然就是+1,然后又将((int)a+1)强转为了int*型。也就是说,a原本指向数组的首元素的地址,在强转之后+1使得地址向后移动了一个字节,而后又转回了指针,此时指针指向的便是数组首元素地址向后移动一个字节开始的数据了,为了能够更好的理解这之中的转化过程,将辅以图片来说明。
由于电脑是小端设备,所以对上图作出调整。
语句三之后,注意指针ptr2指向的位置。
所以最后*ptr2为上图所示数据,同样由于这是小端设备,所以我们在读取数据的同时也要记得转换一下,结果为0x03000000,即30000000。
栈:
存储局部变量,大小1M,后进先出,由系统管理内存。栈底内存地址高,栈顶内存地址高。
int main()
{
int i;
int arr[2] = {0,1};
printf(“0x%x,0x%x,0x%x”,i,arr[0],arr[2]);
}
对于以上程序的执行结果,我将结合栈的特点用图形来说明。
首先,先定义局部变量i,所以先进入栈,后定义的数组arr[2],即后进栈。要注意的是,在数组arr中,arr[0]的地址低于arr[1]的地址,按理来说不应该是先进栈的arr[0]的地址更高吗?这是因为在做指针的加法p++时,地址一定是增加的,所以在栈中数组元素的存储方式是后面的元素地址越高。所以根据这几个特点,可以选出可能的结果是:0x3ffff7a0、0x3ffff798、0x3ffff79c。
时间复杂度:
执行次数和问题规模之间的函数关系,用来度量算法执行的时间长短。计算时间复杂度时有几个特点:一是只考虑告诫,低阶直接丢弃,二是省略系数,三是常数项的时间复杂度是O(1)。
int foo(int n)
{
If(n<1)
return 1;
return n*foo(n-1) ;
}
问以上程序的时间复杂度是多少?通常,求函数的时间复杂度只需要找到在程序中执
次数最多的语句,求其执行次数。到语句return n*foo(n-1) ;该语句的执行次数为n次,所以时间复杂度为O(n)。(计算比较复杂的时间复杂度时,通常会牵扯到许多的数学知识,最好有所了解)
结构体的大小:
在计算结构体的大小时,要遵循的两点规则是:向前对齐和向后对齐。一是向后对齐即数据类型较大的在后,较小的在前,前面的较小数据类型要向后面的数据类型大小对齐。如结构体:
struct A
{
char a;
int
} ;
sizeof(A) 为8,为什么是8呢?
首先,按照char型数据占1个字节,int型数据占4个字节,结构体的大小理应是5
字节数才对,见下图。
可是,这样的结果忽略了一个很重要的条件,那就是硬件在读取数据的时候只能够读取数据所占字节数的整数倍地址,即读取int型数据时,硬件只能从诸如100、104、108等等这样地址开始读。那么显然是读不了从101开始的int型数据的,所以需要调整成下图这样。
这样,sizeof(B) 为8就很合理了。
那么如果前面有两个或更多的较小数据类型,又是怎样的呢?
struct B
{
char a;
short b;
int c;
}
其实是一样的,遵循同样的对齐原则。
sizeof(B)为8。
二是向前对齐,即较大数据类型在前,较小数据类型在后,后面的较小类型需要向前面的较大数据类型对齐。如结构体:
struct C
{
int a;
char b;
}
Sizeof(C)还是为8,这又是为什么?其实,这是为了防止定义结构体数组的情况出现。见下图:
这时数据的存放是连续的,所以就有必要同时考虑两个结构体变量的存放。并且为了计算的简便,可以记住出现向前对齐的情况,sizeof()的结果就是单个最大数据类型的整数倍。如结构体:
Struct D
{
Int a;
Struct E
{
Float b;
Int c;
}
Int *p;
Short d;
}
a四个字节,E八个字节,p四个字节,d两个字节,加起来为18个字节。整体向4对齐,结果为4的倍数,即最终结果为20个字节。
那么下面两个程序的结果分别是多少呢?
32位机器上定义如下结构体:
Struct xx4
{
long long _x1;//8
char _x2;//1+7
int _x3;//4
char _x4[2];//2+2
static int _x5;//4
};
问sizeof(xx)的大小是多少?24。
Struct Date
{
Char a;//1+3
Int b;//4
Int64_t c;//8
Char d;//1+3
};
Date date[2][10]
如果Date的地址是x,那么date[1][5].c的地址是?x+368,(24*15+8)。
全局变量和局部变量:
全局变量存放在静态变量区,局部变量存放在栈中。全局变量作用域为从定义变量处到本文件结尾都能使用,局部变量的作用域是本函数内部有效。程序如下(全局变量):
Intfun(int n)
{
Static int a = 2;
a++;
returna*n;
}
Intmain()
{
Int k = 5;
Int I = 2;
K += fun(i++);
K += fun(i-1);
Printf(“%d,%d\n”,I,k);
Return 0;
}
输出结果为?此处一定要注意a是全局变量,所以a++;的作用范围在整个程序中,而不仅仅是fun()函数中。
程序如下(局部变量):
#include<stdio.h>
char*myString()
{
char buffer[6] = {0};
char *s = "Hello World!";
for(int i=0;i<sizeof(buffer)-1;i++)
{
buffer[i] = *(s+i);
}
return buffer;
}
intmain(int argc,char **argv)
{
printf("%s\n",myString());
return 0;
}
以上程序的输出结果为?首先,我们看myString()函数,其中定义了局部变量buffer[6],最后函数的返回值也是buffer,可是它的作用域只是在此函数内部,返回不出去的。
二维数组:
定义二维数组arr[3][4] = {{1,2,3,4},{5,6},{9,10,11}}。一维数组名表示的是首元素的地址,是一级指针,而二维数组名表示的也是“首元素”的地址,也是一个一级指针不过这个首元素实际上是首行一维数组的地址。见下图
这时的数组名arr实际上写成指针的形式就是:int *p[4],表示的是一个指向含有4个int型数据的数组的指针。同时注意区分int *p[4]和int *p[4]的区别,前者是一个指向数组的指针,后者是一个数组指针,数组中有四个整型指针。虽然上图将二维数组真的表示成了二维的样子,可是实际在硬件中储存时,二维数组中的数据还是连续存储的。以下引入越界的概念证明这一点。
Int a[][3] ={{1,2},{3,4,5},{6,7,8},{9}};
Printf(“%d\n”,a[1][3]);
a[1][3]越界了,实际上输出的是a[2][0]的值,就是因为在二维数组中数据的存储是连续的,所以对越界要十分小心。
字符数组和字符串:
字符数组就是一个含有的元素全是字符的数组,字符串就是在这基础上在末尾加上’\0’,即没有’\0’就不是字符串。Char str[] = “abcdfg”;定义了一个字符串,Char str[] = {‘a’,’b’,’c’,’d’,’f’,’g’}定义了一个字符数组,而不是字符串,像是Char str[] = {‘a’,’b’,’c’,’d’,’f’,’g’,’\0’}才是定义了一个字符串。
以下代码编译或是运行时会发生错误的是?
Char buf[4] = “hell”; printf(“%s”,buf);
Char buf[4]; strcpy(buf,”hell”); printf(“%s”,buf);
Char buf[4] = {0}; memcpy(buf,”hell”,strlen(“hell”)); printf(“%s”,buf);
Char buf[ ] = “hell”; printf(“%s”,buf);
上面的代码中,第一行和第二行是会运行错误的。第一行中,”hell”是一个字符串,加上’\0’一共占5个字节,4个是不够的。第二行中是同样的。
再看,以下不能成功将”HELLO!”赋给数组b的语句是?
Char b[10] = {‘H’,’E’,’L’,’L’,’O’,’!’,’\0’};
Char b[10]; b = “HELLO!”;
Char b[10]; strcpy(b,”HELLO!”);
Char b[10] = “HELLO!”;
在以上的语句中,只有第二句不能成功地将”HELLO!” 赋给数组b,因为聚合类型只有在初始化时才能直接全体赋值,第二句并没有初始化就想直接给数组b赋值,这时是不行的,需要用到字符串拷贝函数strcpy()。
最后,得出以下程序的运行结果。
#define FUNC(x,y) (x>y) ? ‘a’ : ‘b’
Int main()
{
Unsigned int a = 10;
Char b = 1;
Unsigned char c = -1;
Printf(“%c,%c\n”,FUNC(a,b),FUNC(a,c));
}
以上程序的运行结果为:a,b。着重看FUNC(a,c),因为c是一个无符号char型数据,所以-1是不在0~255的范围内的,所以c实际上就变成了255,10小于255,所以最后输出的就是b。
指针:
指针实际上就等同于地址,指针变量里存放的是一个地址,要与指针本身的概念加以区分。
看以下程序的运行成果:Int main()
{
Int a[] = {23,5,87,11,5};
Int *p = a;
Printf(“%d”,p[2]);
P = (int *)(&a+1);
Printf(“%d\n”,p[-2]);
Return 0;
}
定义了一个指针变量p指向了数组a的首元素的地址,p[2]等同于*(p+2),所以结果就是87。接着,&a+1中的a表示的是整个数组,指针向后移动了一个a数组的长度,p指向了数组后的第一个未知数据的地址,而p[-2]等同于*(p-2),结果为11。
Void main()
{
Unsigned char *p1 = (unsigned char *)0x801000;
Unsigned long *p2 = (unsigned long*)0x810000;
Printf(“%x,%x\n”,p1+5,p2+5);
}
这个程序牵扯到了指针的加减法,这就需要知道指针加减法之中的调整规则了。调整的权重为指针本身去掉一个*号,求sizeof()。p1的权重为sizeof(char) 为1,p2的权重为sizeof(longr) 为4,所以最后p1+5的结果为0x801005,p2+5的结果为0x810014(千万不要写成0x810020,这是十六进制的写法!记得!!!)
定义一个int类型的指针数组,数组元素个数为10个,以下定义方式中,正确的是哪个?
int a[10];
int (*a)[10];
int *a[10];
int (*a[10])(int);
第一个就是一个简单的int型数组,里面存放的是10个int型数据。第二个是一个指向含有10个int型数据的数组指针。第三个才是存放了10个int型指针的指针数组。第四个是指向函数的一个指针数组。