1.用预处理指令#define 声明一个常数,用以表明 1 年中有多少秒(忽略闰年问题)
#define SECONDS_PER_YEAR (60 * 60 * 24 * 365)UL
//1.括号的使用 2.无需写出具体数值,反而代码更清晰 3.UL表示无符号长整型,防止在16位机上溢出
2 . 写一个"标准"宏 MIN ,这个宏输入两个参数并返回较小的一个
#define MIN(A,B) ((A) <= (B) ? (A) : (B))//整个替换,外层加个大括号
//每一部分都括起来,并且不要传入a++这种参数,会出问题
//其实用inline内联函数更好,无调用开销,安全
3.关键字 static 的作用是什么?
1.在函数体内,一个被声明为静态的变量在这一函数被调用过程中维持其值不变
2.在模块内(但在函数体外),一个被声明为静态的变量可以被模块内所用函数访问
3.在模块内,一个被声明为静态的函数只可被这一模块内的其它函数调用
void DisplayTime(…)
{
static BYTE byHour,byMinute,bySecond;
BYTE byNewHour, byNewMinute, byNewSecond;
byNewHour = GetSysHour();
byNewMinute = GetSysMinute();
byNewSecond = GetSysSecond();
if(byNewHour!= byHour)
{
… /* 显示小时 */
byHour = byNewHour;
}
if(byNewMinute!= byMinute)
{
… /* 显示分钟 */
byMinute = byNewMinute;
}
if(byNewSecond!= bySecond)
{
… /* 显示秒钟 */
bySecond = byNewSecond;
}
}
4.关键字 const 有什么含意?
1.意味着是只读变量,其实不是常数,定义的时候就要初始化,不能赋值,可以通过指针去修改的
2.可以修饰函数参数,防止被修改
5.关键字 volatile 有什么含意?并给出三个不同的例子
并行设备的硬件寄存器(如:状态寄存器)
一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
多线程应用中被几个任务共享的变量
一个参数既可以是 const 还可以是 volatile 吗?解释为什么
答:是的。一个例子是只读的状态寄存器。它是 volatile 因为它可能被意想不到地改变。它是 const 因为程序不应该试图去修改它
一个指针可以是 volatile 吗?解释为什么
答:是的。尽管这并不很常见。一个例子是当一个中服务子程序修该一个指向一个 buffer 的指针时
6.define与typedef
#define dPS struct s *
typedef struct s * tPS;
dPS p1,p2;
tPS p3,p4;
第一个展开:struct s * p1, p2;//p2就不是指针了
7.内嵌汇编
//采用如下方法实现执行汇编指令WFI
void WFI_SET(void)
{
__ASM volatile("wfi");
}
//关闭所有中断
void INTX_DISABLE(void)
{
__ASM volatile("cpsid i");
}
//开启所有中断
void INTX_ENABLE(void)
{
__ASM volatile("cpsie i");
}
//设置栈顶地址
//addr:栈顶地址
__asm void MSR_MSP(u32 addr)
{
MSR MSP, r0 //set Main Stack value
BX r14
}
void Add(long a, long *b)
{
_asm
{
MOV AX, a
MOV BX, b
ADD AX, [BX]
MOV result, AX
}
}
8.自己实现strcpy,strlen函数
char *strcpy(char *strDest, const char *strSrc)
{
if ((strDest == NULL) || (strSrc == NULL))//assert( (strDest != NULL) && (strSrc != NULL) );
{
return NULL;
}
char *address = strDest; //返回首地址,做一个备份
while ((*strDest++ = *strSrc++) != ‘\0’ );
/*等价于以下代码
while(*strSrc!='\0')
{
*strDest=*strSrc;//赋值
strDest++;//指针后移
strSrc++;
}*/
*strDest ='\0';//最后补上字符串结束标志位
return address;
}
int strlen(const char *str) //输入参数 const
{
assert(strt != NULL); //断言字符串地址非 0
int len;
while ((*str++) != '\0')
{
len++;
}
return len;
}
//或者return ('\0' != *str)?(1+strlen(str+1)):0;//地址+1,偏移。递归方法
9.一般的,如果想让 if 判断一个变量的“真”、“假”,应直接使用 if(var)、if(!var)
,表明其为“逻辑”判断;如果用 if 判断一个数值型变量(short、int、long 等),应该用if(var==0)
,表明是与 0进行“数值”上的比较;而判断指针则适宜用 if(var==NULL)
,这是一种很好的编程习惯。
10.C++函数实现
//类包含指针变量,必须(重载)实现以下几个函数
class String
{
public:
String(const char *str = NULL); // 普通构造函数
String(const String &other); // 拷贝构造函数
~ String(void); // 析构函数
String & operate =(const String &other); // 赋值函数
private:
char *m_data; // 用于保存字符串
};
//普通构造函数
String::String(const char *str)
{
if(str==NULL)
{
m_data = new char[1]; // 得分点:对空字符串自动申请存放结束标志'\0'的空
//加分点:对 m_data 加 NULL 判断
*m_data = '\0';
}
else
{
int length = strlen(str);
m_data = new char[length+1]; // 若能加 NULL 判断则更好
strcpy(m_data, str);
}
}
// String 的析构函数
String::~String(void)
{
delete [] m_data; // 或 delete m_data;
}
//拷贝构造函数
String::String(const String &other) // 得分点:输入参数为 const 型
{
int length = strlen(other.m_data);
m_data = new char[length+1]; //加分点:对 m_data 加 NULL 判断
strcpy(m_data, other.m_data);
}
//赋值函数
String & String::operate =(const String &other) // 得分点:输入参数为 const 型
{
if(this == &other) //得分点:检查自赋值
return *this;
delete [] m_data; //得分点:释放原有的内存资源
int length = strlen( other.m_data );
m_data = new char[length+1]; //加分点:对 m_data 加 NULL 判断
strcpy( m_data, other.m_data );
return *this; //得分点:返回本对象的引用
}
11.对于一个字节(8bit)的数据,求其中“1”的个数,要求算法的执行效率尽可能地高
#include<stdio.h>
#define BYTE unsigned char
int main(int argc, char *argv[])
{
int i, num = 0;
BYTE a;
/* 接收用户输入 */
printf("\nPlease Input a BYTE(0~255):");
scanf("%d", &a);
/* 计算 1 的个数 */
for (i = 0; i < 8; i++)
{
/***
if (a % 2 == 1)
{
num++;
}
a = a / 2;
******/
num+=(a>>i)&0x01;
}
printf("\nthe num of 1 in the BYTE is %d", num);
return 0;
}
或者查表法,定义一个256大小的数组,初始化为0-255包含的1的个数
int main()
{
int x=999;
int i=0;//计数
while(x)
{
i++;
x=x&(x-1);
}
}
12.关于函数返回值的总结
1)子函数返回值为数值时,返回局部变量可以,函数在返回参数的时候是这样的,先把要返回的数放在寄存器eax中(也就是说返回的是该变量的副本),然后回到主函数中取出eax中的数值放在变量里,所以这样是不涉及函数中变量地址的
2)如果要返回引用,也就是变量地址,那么它会把这个变量的地址放在eax中,(注意这个地址是位于函数的栈空间里的,出了这个函数,这块内存就会被系统标记为可占用(就是其它程序可以占用)),回到主函数后系统会把这个地址赋值给主函数中的指针变量。此时主函数中的指针变量就指向了一个已经被标记为可占用的内存空间。如果你在不同的时刻输出这个指针所指地址的值会输出不同的结果
3)判断指针函数返回值是否合法,应该首先看看该返回指针变量指向的对象的存储区域,即该指针指向的区域。透过现象看本质,不同区域的对象本质区别在于 其的生存周期的有效性不同,判断返回的指针值是否有效合法,最本质应该看看该指针指向的对象的生存周期在函数结束后是否有效。如果该对象的生存周期长于指 针函数的生存周期,则该指针返回值合法,否则,该指针的值为非法地址。即使该指针指向堆区域的地址但在指针函数结束时,堆已释放,则该函数的返回地址仍为非法。
int * fun()
{
int p=100;
return &p;
}
void main()
{
int *p=NULL;
p = fun();
printf("%d\n" , *p);//p是局部变量,退出子函数时其地址被回收了。期待输出100,实际上不行,如果改为static int p就可以
return;
}
4)如果函数的参数是一个指针,不要指望用该指针去申请动态内存。Test函数的语句GetMemory(str, 200)并没有使str获得期望的内存,str依旧是NULL,为什么?
void GetMemory(char *p, int num)
{
p = (char *)malloc(sizeof(char) * num);
}
void Test(void)
{
char *str = NULL;
GetMemory(str, 100); // str 仍然为 NULL
strcpy(str, "hello"); // 运行错误
}
//问题出在函数GetMemory 中。编译器总是要为函数的每个参数制作临时副本,指针参数p的副本是 _p,编译器使 _p = p。如果函数体内的程序修改了_p的内容,就导致参数p的内容作相应的修改。这就是指针可以用作输出参数的原因。在本例中,_p申请了新的内存,只是把 _p所指的内存地址改变了(并没有去修改内容),但是p丝毫未变。所以函数GetMemory并不能输出任何东西。事实上,每执行一次GetMemory就会泄露一块内存,因 为没有用free释放内存。
如果非得要用指针参数去申请内存,那么应该改用“指向指针的指针”,如下:
void GetMemory2(char **p, int num)
{
*p = (char *)malloc(sizeof(char) * num);
}
void Test2(void)
{
char *str = NULL;
GetMemory2(&str, 100); // 注意参数是 &str,而不是str
strcpy(str, "hello");
cout<< str << endl;
free(str);
}
或者
char *GetMemory3(int num)
{
char *p = (char *)malloc(sizeof(char) * num);//动态申请的,所以可以
return p;
}
void Test3(void)
{
char *str = NULL;
str = GetMemory3(100);
strcpy(str, "hello");
cout<< str << endl;
free(str);
}
char *GetString(void)
{
char p[] = "hello world";
return p; // 编译器将提出警告,加static就可以
}
void Test4(void)
{
char *str = NULL;
str = GetString(); // str 的内容是垃圾,新内容不可知
cout<< str << endl;
}
//p表示数组的首地址,局部变量
char *GetString2(void)
{
char *p = "hello world";
return p;
}
void Test5(void)
{
char *str = NULL;
str = GetString2();
cout<< str << endl;
}
//函数Test5运行虽然不会出错,但是函数GetString2的设计概念却是错误的。因为GetString2内的“hello world”是常量字符串,位于静态存储区,它在程序生命期内恒定不变。无论什么时候调用GetString2,它返回的始终是同一个“只读”的内存块
13.空结构体大小是多少
struct _ss{
};
其长度为多少?可能为0(gcc),可能为1(g++)。现代编译器更倾向1,如果为0的话,会存在多个结构体变量位于同一个地址。C规范没有规定
14.整个数组地址,数组元素地址,与指针
#include <stdio.h>
int main(int argc, char **argv) {
int a[5]={1,2,3,4,5};
int *ptr1=(int *)(&a+1);//&a表示整个数组的首地址,+1则表示偏移了一个数组的大小,即指针指向5后面的元素,越界了。如果把&a改为a或者&a[0]则偏移一个int大小
int *ptr2=(int *)((int)a+1);//这里加了int强转则不表示指向a[1]元素,去掉就是表示a[1]
printf("%d,%d\r\n",ptr1[-1],*ptr2);
return 0;
}
答案:5,33554432
15.无符号和有符号的差别
//答案255
int main(int argc, char **argv) {
char a[1000];
int i;
for(i=0; i<1000; i++)
{
a[i] = -1-i;
}
printf("%d",strlen(a));
return 0;
}
//我们知道在计算机系统中,数值一律用补码来表示(存储)。
按照负数补码的规则,可以知道-1 的补码为 0xff, -2 的补码为 0xfe……当 i 的值为 127时, a[127]的值为-128,而-128 是 char 类型数据能表示的最小的负数。当 i 继续增加, a[128]的值肯定不能是-129。因为这时候发生了溢出, -129 需要 9 位才能存储下来,而 char 类型数据只有 8 位,所以最高位被丢弃。剩下的 8 位是原来 9 位补码的低 8 位的值,即 0x7f。当 i 继续增加到 255 的时候, -256 的补码的低 8 位为 0。然后当 i 增加到 256 时, -257 的补码的低 8 位全为 1,即低八位的补码为 0xff,如此又开始一轮新的循环……
按照上面的分析, a[0]到 a[254]里面的值都不为 0,而 a[255]的值为 0。
这个问题的关键就是要明白 char类型默认情况下是有符号的,其表示的值的范围为[-128,127],超出这个范围的值会产生溢出。 另外还要清楚的就是负数的补码怎么表示。
16.结构体内部含有指针成员
struct student
{
char *name;
int score;
}stu,*pstu;
int main()
{
strcpy(stu.name,"Jimy");//name这个成员没有指向合法地址,malloc分配
stu.score = 99;
return 0;
}
17.如下代码并不能正确执行
char *name;
printf("请输入数据。。。。");
scanf("%s",name);
printf("%s",name);
//如果声明了一个指针,没有分配内存,内存一般都是垃圾,这往往会导致一个无效的内存引用错误。
//如果指针初始化为NULL,这段程序在vs2013里面可以编译但不能执行,因为不能往0地址写入数据,必须初始化一片为可读写的内存地址,比如用malloc分配内存。
//以上代码可以在linux下运行
//char *name;改为char name[20]数组就可以
18.输出整数二进制补码
可以用printf("%x",value)查看value在内存里面如何存储的(补码)
//共享内存,建立动态数组去实现。也可以采用移位去实现
typedef struct {
unsigned char bit0 : 1;
unsigned char bit1 : 1;
unsigned char bit2 : 1;
unsigned char bit3 : 1;
unsigned char bit4 : 1;
unsigned char bit5 : 1;
unsigned char bit6 : 1;
unsigned char bit7 : 1;
}bits;
int main()
{
int num = 1;
bits* pbits =(bits*) malloc(sizeof(bits) * 4);
pbits=&num ;//pbits=(bits*)&num ;
for (int i = 3; i >= 0; i--)
{
printf("%d%d%d%d%d%d%d%d",pbits[i].bit7,pbits[i].bit6,
pbits[i].bit5,pbits[i].bit4,pbits[i].bit3,
pbits[i].bit2,pbits[i].bit1, pbits[i].bit0);
}
}
//浮点数
int main()
{
float f = 1.234f;
unsigned char* p = (unsigned char*)&f;
for (int i = 3; i >= 0; i--)
{
unsigned char chs = p[i];
for (int j = 7; j >= 0; j--)
{
if (chs & (1 << j))
{
printf("1");
}
else
{
printf("0");
}
}
printf(" ");
}
}
19 两段代码优缺点
21.找错误