第5章 程序设计基本概念
类型存储与大小端问题
- 小端存储:低位存放在低地址单元,高位存放在高地址单元;
- 大端存储:低位存放在高地址单元,高位存放在低地址单元;
#include <stdio.h>
int main()
{
unsigned int a = 0xFFFFFFF7;
unsigned char i = (unsigned char)a;
char* b = (char*)&a;
printf("%08x,%08x",i,*b);
}
在X86系列的机器中,数据的存储是“小端模式”。所以在执行char* b = (char*)&a;
这句话时,&a
可以认为是个指向unsigned int
类型数据的指针,(char*)&a
把&a
强制转换成了char *
类型的指针,而且这个时候发生了截断。截断后,指针b
只指向了0xf7
这个数据,又由于指针b
是char*
型的,属于有符号数,所以有符号数0xf7
在printf()
的作用下输出fffffff7
.
- 答案:
000000f7
,fffffff7
union判断大小端
int checkCPUendian()//返回1,为小端;反之,为大端;
{
union
{
unsigned int a;
unsigned char b;
}c;
c.a = 1;
return 1 == c.b;
}
运算符问题
-
用一个表达式,判断一个数X是否是2的N次方,不可用虚幻语句。
!(X&(X-1))
-
求解以下代码
f(729,271)
int f(int x,int y) { return (x&y)+((x^y)>>1); }
x&y
是取相同的位与,这个结果是x和y相同位的和的一半,x^y
是取x和y的不同位,右移相当于除以2,所以这个函数的功能是取两个数的平均值。- 答案:500
a、b交换与比较
- 有两个变量a和b,不用if,三目运算符,switch等其他判断语句。
int max = ((a+b)+abs(a=b))/2;
- 有两个数据,写一个交换的宏。
#include<string.h> #define swap(a,b) \ { char tempBuf[10]; memcpy(tempBuf,&a,sizeof(a)); memcpy(&a,&b,sizeof(b)); memcpy(&b,tempBuf,sizeof(b)); }
//需要考虑越界问题 #define SWAP(a,b) \ a= a + b ;\ b= a - b;\ a= a - b;
C和C++的关系
- 在C++程序中调用被C编译器编译后的函数,为什么要加
extern "C"
- C++语言支持函数重载,C语言不支持函数重载。函数被C++编译后在库中的名字与C语言的不同。假设某个函数的原型为
void foo(int x,int y)
,该函数被C编译器编译后在库中的名字为_foo
,而C++编译则会产生像_foo_int_int
之类的名字。<br> C++提供了C连接交换指定符号
extern “C”`解决名字匹配问题。
- C++语言支持函数重载,C语言不支持函数重载。函数被C++编译后在库中的名字与C语言的不同。假设某个函数的原型为
- 评价一下C与C++的各自特点。
- C语言是一种结构化语言,重点在于算法和数据结构。C程序的设计首先考虑的是如何通过一个过程,对输入(或环境条件)进行运算处理得到输出(或实现过程控制)。
- C++首先考虑的是如何构件一个对象模型,让这个模型能够契合与之对应的问题域,这样就可以通过获取对象的状态信息得到输出或实现输出或实现过程控制。
第6章 预处理、const、sizeof
-
任何不修改成员数据的函数都应该声明为
const
函数,这样有助于提高程序的可读性和可靠性。 const
与#define
相比有什么不同- const常量有数据类型,而宏函数没有数据类型。编译器可以对前者进行类型安全检查,而后者只进行字符替换,没有类型安全检查。
- 有些集成化的调试工具可以对const常量进行调试,但是不能对宏常量进行调试。
- 修改下面类的成员变量
class A{ void f() const { } }
- 答案:类里面的数据成员加上
mutable
,修饰为const的成员变量,就可以修改它了。
- 答案:类里面的数据成员加上
- 数据对齐:是指数据所在的内存地址必须是该数据长度的整数倍。
- 结构体或类的自身对齐值:其成员自身对齐值最大的那个值。
- sizeof与strlen的理解
- sizeof是运算符,strlen是函数。
- sizeof操作符的结果类型为size_t,它在头文件中的typedef为
unsigned int
类型。该类型保证能容纳所建立的最大对象的字节大小。 - sizeof可以用类型做参数,strlen只能用
char *
做参数,且必须是以"\0"
结尾的。sizeof还可以用做函数做参数,比如short f(); printf("%d\n",sizeof(f()))
- 输出结果是
sizeof(short))
,即为2.
- 数组做sizeof的参数不退化,传递给strlen就退化为指针。
- 大部分编译程序在编译的时候就把sizeof计算过了,是类型或是变量的长度。这就是
sizeof(x)
可以用来定义数组为数的原因。 - 数组作为参数传给函数时传递的是指针而不是数组,传递的数组的首地址,如
fun(char [8]);fun(char [])
都等价于fun(char *)
.
int **a[3][4]
这个数组占据多大的空间?- 指向指针的指针大小也为4,所以总大小为434=48.
- 关于类的大小
- 类的大小应该满足数据对齐,另外类的大小与普通数据有关,与成员函数和静态成员无关。
- 空类的大小为1.
- 虚函数对类的大小有影响,是因为虚函数表指针带来的影响。
内联函数和宏定义
- 内联函数和宏的差别是什么?
- 内联函数和普通函数相比可以加快程序的运行速度,因为不需要终端调用,在编译的时候内联函数可以直接被镶嵌到目标代码中。而宏只是一个简单的替换。
- 内联函数要做参数类型检查,这是内联函数跟宏函数相比的优势。
- inline是指嵌入代码,就是在调用函数的地方不是跳转,而是把代码直接写到哪里去。
- inline一般只适用于:
- 1.一个函数不断被重复调用。
- 2.函数只有简单的几行,而且函数内不包含for、while、switch语句。
- 内联是以代码膨胀为代价(空间换时间),仅仅省去了函数调用的开销,从而提高函数的执行效率。过度使用内联函数,将使程序的总代码量增大,消耗更多的内存空间。
- inline函数仅仅是一个对编译器的建议,所以最后能否真正内联,取决于编译器。
第7章 指针与引用
指针与引用的差别
- 非空区别。在任何情况下都不能使用指向空值的引用,一个引用必须总是指向某些对象。而指针可以可以不初始化。
- 合法性区别。在使用引用之前不需要测试它的合法性。相反,指针则应该总是被测试,防止其为空。
- 可修改区别。指针可以赋值以指向另一个不同的对象。但是引用则总是指向初始化时被指定的对象,以后不能改变,指定的对象其内容可以改变。
- 可以定义指针的指针但是不能定义引用的引用。
char str[] = “hello world”;和char *str = “hello world”区别
char *strA()
{
char str[] = "hello world";
return str;
}
str[] = "hello world"
“hello world”常量字符串在内存中有两份拷贝,一份在动态分配的栈中,一份在静态存储区,str[]数组为函数内部局部变量,存储在栈上,在strA()函数退出时,栈要清空,局部变量的内存也被清空
char *strA()
{
char *str = "hello world";
return str;
}
char *str = "hello world"
一份拷贝,”hello world”是常量字符串存在静态数据区,把该字符串常量存在的静态数据区的首地址赋给指针str,所以strA()函数退出时,该字符串常量所在内存不会被回收,故能通过指针访问;
char *strA()
{
static char str[] = "hello world";//static变量被保存在静态存储区而不是堆栈,
return str;
}
指针数组与数组指针
-
写出如下程序片段的输出
int a[] = {1,2,3,4,5}; int *ptr = (int *)(&a+1); printf("%d,%d",*(a+1),*(ptr-1));
- 数组名本身就是指针,再加一个&,就变成了双指针,这里的双指针就是二维数组,加1,就是数组整体加一行,ptr指向a的第6个元素。
- 答案:1,5
-
this指针
- this是一个const指针,存的是当前对象的地址,指向当前对象,通过this指针可以访问类中的所有成员。
- 每个成员函数都有一个指针形参(构造函数没有这个形参),名字固定,称为this指针,this是隐式的。
- this只能在成员函数中使用,全局函数,静态函数不能使用this。因为静态函数没有固定对象。
- this在成员函数的开始执行前构造,在成员的执行结束后清除。
- this指针会因编译器不同而有不同的存放位置。有可能是堆、栈,也可能是寄存器。