1.求一个整数二进制表示时1的个数,很巧妙 (有BUG,更好的实现见https://github.com/xiarendeniao/utils/blob/master/c/verfloat.cpp 2017.3.23)
#include<stdio.h>
//求一个整数的二进制中1的个数
int sum_of_one(int arg)
{
int count = 0;
while (arg) {
count++;
arg = arg&(arg-1);
}
return count;
}
//把一个整数转换成二进制字符串
void int2bin(const int in, char *out)
{
int i,r;
char t[32],*tp;
r=in;
tp=t;
while(r>=1) {
*tp++=r%2+48;
r/=2;
}
*tp--='\0';
while(*out++=*tp--);
*out='\0';
}
int main( int argc, char** argv)
{
int input;
char binaryInput[32] = {0};
begin:
printf("please input > ");
scanf("%d", &input);
int2bin(input, binaryInput);
printf("you input a number:%d(%s)\n", input, binaryInput);
printf("sum_of_one(%d) = %d\n", input, sum_of_one(input));
goto begin;
}
~
~
~
~
:!gcc -o sumofone sumofone.c; ./sumofone
please input > 12
you input a number:12(1100)
sum_of_one(12) = 2
please input > 23
you input a number:23(10111)
sum_of_one(23) = 4
please input > 32
you input a number:32(100000)
sum_of_one(32) = 1
please input > 999
you input a number:999(1111100111)
sum_of_one(999) = 8
please input > 9999
you input a number:9999(10011100001111)
sum_of_one(9999) = 8
please input > ^C
命令已结束
2.c中的字符常量是int型的,因此c里面sizeof('a')跟sizeof(int)结果相等。另,sizeof是宏、不是普通函数,所以可以这样用sizeof'a'。
3.c里面没有bool类型,c++有
4.置位和清除
#include <iostream>
#include <assert.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
using namespace std;
//index>>3 求的是index除以8得到的整数
//index&0x7 求的是index除以8的余数
//0xff(11111111)异或(相同为0不同为1)一个八位的整数是将该整数按位取反
void setbit(void* ptr, int max, int index)
{
assert(max>index);
assert(max%8==0);
((uint8_t*)ptr)[index>>3] |= 1<<(index&0x7);//index介于[0,max)
}
void unsetbit(void* ptr, int max, int index)
{
assert(max>index);
assert(max%8==0);
((uint8_t*)ptr)[index>>3] &= (0xff^(1<<(index&0x7)));
}
int getbit(void* ptr, int max, int index)
{
assert(max>index);
assert(max%8==0);
return (((uint8_t*)ptr)[index>>3]&(1<<(index&0x7)))!=0 ? 1 : 0;
}
void int2bin(const int in, char *out, int length = 0)
{
int i,r;
char t[32],*tp;
r=in;
tp=t;
while(r>=1) {
*tp++=r%2+48;
r/=2;
}
*tp--='\0';
if ((length>0)&&(strlen(t)<length)) {
for (int i = 0; i < length-strlen(t); ++i)
*out++ = '0';
}
while(*out++=*tp--);
*out='\0';
}
int main( int argc, char** argv )
{
int max, input, index;
cout << "请输入总位数>";
cin >> max;
assert( (max>0) && (max%8==0) );
void* ptr = malloc(max>>3);
assert(ptr);
begin:
cout << "1.设置\t2.清除\t3.获取\t4.全显\t0.exit>";
cin >> input;
switch (input)
{
case 1:
cout << "请输入位数[0," << max << ")>";
cin >> index;
setbit(ptr, max, index);
break;
case 2:
cout << "请输入位数[0," << max << ")>";
cin >> index;
unsetbit(ptr, max, index);
break;
case 3:
cout << "请输入位数[0," << max << ")>";
cin >> index;
cout << getbit(ptr, max, index) << endl;
break;
case 4:
for (int index = 0; index < max; ++index) {
cout << getbit(ptr, max, index);
}
cout << "(getbit())" << endl;
//显示实际内存的数据,用于验证这种实现可行,但是跟实际内存数据在每个字节内部是左右颠倒的,所以不能想当然的跳过接口取内存数据
//(1<<(index&0x7)如果改成0x80>>(index&0x7)应该可以保证接口展现和内存实际数据一致
for (int index = 0; index < (max>>3); ++index) {
int tmp = ((uint8_t*)ptr)[index];
char buf[32] = {0};
int2bin(tmp, buf, 8);
cout << buf;
}
cout << "(real mem)" << endl;
break;
case 0:
goto end;
default:
cout << "wrong input" << endl;
goto begin;
}
goto begin;
end:
free(ptr);
return 0;
}
//输出
xudongsong@HP:~/c++_study$ g++ -o bitoperator bitoperator.cpp -g; ./bitoperator
请输入总位数>64
1.设置 2.清除 3.获取 4.全显 0.exit>1
请输入位数[0,64)>0
1.设置 2.清除 3.获取 4.全显 0.exit>4
1000000000000000000000000000000000000000000000000000000000000000(getbit())
0000000100000000000000000000000000000000000000000000000000000000(real mem)
1.设置 2.清除 3.获取 4.全显 0.exit>1
请输入位数[0,64)>63
1.设置 2.清除 3.获取 4.全显 0.exit>4
1000000000000000000000000000000000000000000000000000000000000001(getbit())
0000000100000000000000000000000000000000000000000000000010000000(real mem)
1.设置 2.清除 3.获取 4.全显 0.exit>1
请输入位数[0,64)>34
1.设置 2.清除 3.获取 4.全显 0.exit>4
1000000000000000000000000000000000100000000000000000000000000001(getbit())
0000000100000000000000000000000000000100000000000000000010000000(real mem)
1.设置 2.清除 3.获取 4.全显 0.exit>2
请输入位数[0,64)>0
1.设置 2.清除 3.获取 4.全显 0.exit>4
0000000000000000000000000000000000100000000000000000000000000001(getbit())
0000000000000000000000000000000000000100000000000000000010000000(real mem)
1.设置 2.清除 3.获取 4.全显 0.exit>0
5.
explicit关键字用于取消构造函数的隐式转换,对有多个参数的构造函数使用
explicit是个语法错误
#include<iostream>
using namespace std;
class A
{
public :
A(int v) { this->v = v; }
int v;
};
int main( int argc, char** argv )
{
A a(10);
cout << a.v << endl;
A aa = 10;
cout << aa.v << endl;
}
~
~
~
:!g++ -o explicit explicit.cpp; ./explicit
10
10
#include<iostream>
using namespace std;
class A
{
public :
explicit A(int v) { this->v = v; }
int v;
};
int main( int argc, char** argv )
{
A a(10);
cout << a.v << endl;
A aa = 10;
cout << aa.v << endl;
}
~
~
~
:!g++ -o explicit explicit.cpp
explicit.cpp: 在函数‘int main(int, char**)’中:
explicit.cpp:18:9: 错误: 请求从‘int’转换到非标量类型‘A’
6.数组定义时候赋初值,可以用类似{1,2,3}{'a','b'}{1.f}{0}的形式初始化,指定的是前面一些元素的值,后面没指定的默认零;如果没有赋初值则所有数据都是不可预测的,没有默认值
7.对于数组类型参数int st[],其实退化成指针了,在函数内用sizeof(st)求出的是指针所占字节数,不是数组所占字节数
8.运算法优先级 来源http://www.cppblog.com/aqazero/archive/2006/06/08/8284.html
从上往下优先级下降
Precedence | Operator | Description | Example | Associativity |
1 | () | Grouping operator | (a + b) / 4; | left to right |
2 | ! | Logical negation | if( !done ) ... | right to left |
3 | ->* | Member pointer selector | ptr->*var = 24; | left to right |
4 | * | Multiplication | int i = 2 * 4; | left to right |
5 | + | Addition | int i = 2 + 3; | left to right |
6 | << | Bitwise shift left | int flags = 33 << 1; | left to right |
7 | < | Comparison less-than | if( i < 42 ) ... | left to right |
8 | == | Comparison equal-to | if( i == 42 ) ... | left to right |
9 | & | Bitwise AND | flags = flags & 42; | left to right |
10 | ^ | Bitwise exclusive OR | flags = flags ^ 42; | left to right |
11 | | | Bitwise inclusive (normal) OR | flags = flags | 42; | left to right |
12 | && | Logical AND | if( conditionA && conditionB ) ... | left to right |
13 | || | Logical OR | if( conditionA || conditionB ) ... | left to right |
14 | ? : | Ternary conditional (if-then-else) | int i = (a > b) ? a : b; | right to left |
15 | = | Assignment operator | int a = b; | right to left |
16 | , | Sequential evaluation operator | for( i = 0, j = 0; i < 10; i++, j++ ) ... | left to right |
9.内联和宏定义 参考《高质量C++编程》http://man.chinaunix.net/develop/c&c++/c/c.htm#_Toc520634045
关键字inline必须与函数定义体放在一起才能使函数成为内联,仅将inline放在函数声明前面不起任何作用。所以说,inline是一种“用于实现的关键字”,而不是一种“用于声明的关键字”。
内联函数的原理(如何工作?):对于任何内联函数,编译器在符号表里放入函数的声明(包括名字、参数类型、返回值类型)。如果编译器没有发现内联函数存在错误,那么该函数的代码也被放入符号表里。在调用一个内联函数时,编译器首先检查调用是否正确(进行类型安全检查,或者进行自动类型转换,当然对所有的函数都一样)。如果正确,内联函数的代码就会直接替换函数调用,于是省去了函数调用的开销。这个过程与预处理有显著的不同,因为预处理器不能进行类型安全检查,或者进行自动类型转换。假如内联函数是成员函数,对象的地址(this)会被放在合适的地方,这也是预处理器办不到的。
使用内联的目的:提高函数的执行效率(速度)
宏定义:在C程序中,可以用宏代码提高执行效率。宏代码本身不是函数,但使用起来象函数。预处理器用复制宏代码的方式代替函数调用,省去了参数压栈、生成汇编语言的CALL调用、返回参数、执行return等过程,从而提高了速度。使用宏代码最大的缺点是容易出错,预处理器在复制宏代码时常常产生意想不到的边际效应。例如 #define MAX(a, b) (a) >(b) ? (a) : (b) 使用 result = MAX(i, j) + 2 ;
宏定义的缺点:容易出错(边际效应);没有类型安全检查和自动类型转换;不能操作C++类的私有数据成员(书上这样说的,没验证)
内联的优点:C++ 语言的函数内联机制既具备宏代码的效率,又增加了安全性,而且可以自由操作类的数据成员。所以在C++ 程序中,应该用内联函数取代所有宏代码,“断言assert”恐怕是唯一的例外。assert是仅在Debug版本起作用的宏,它用于检查“不应该”发生的情况。为了不在程序的Debug版本和Release版本引起差别,assert不应该产生任何副作用。如果assert是函数,由于函数调用会引起内存、代码的变动,那么将导致Debug版本与Release版本存在差异。所以assert不是函数,而是宏。
内联的缺点:内联是以代码膨胀(复制)为代价,仅仅省去了函数调用的开销,从而提高函数的执行效率。如果执行函数体内代码的时间,相比于函数调用的开销较大,那么效率的收获会很少。另一方面,每一处内联函数的调用都要复制代码,将使程序的总代码量增大,消耗更多的内存空间。以下情况不宜使用内联:(1)如果函数体内的代码比较长,使用内联将导致内存消耗代价较高。(2)如果函数体内出现循环,那么执行函数体内代码的时间要比函数调用的开销大。
10.《高质量C++编程》以后的练习题
一、请填写BOOL ,float, 指针变量 与“零值”比较的 if 语句。(10分)
请写出 BOOL flag 与“零值”比较的 if 语句。(3分)
标准答案:
if ( flag )
if ( !flag )
如下写法均属不良风格,不得分。
if (flag == TRUE)
if (flag == 1 )
if (flag == FALSE)
if (flag == 0)
请写出 float x 与“零值”比较的 if 语句。(4分)
标准答案示例:
const float EPSINON = 0.00001;
if ((x >= - EPSINON) && (x <= EPSINON)
不可将浮点变量用“==”或“!=”与数字比较,应该设法转化成“>=”或“<=”此类形式。
如下是错误的写法,不得分。
if (x == 0.0)
if (x != 0.0)
请写出 char *p 与“零值”比较的 if 语句。(3分)
标准答案:
if (p == NULL)
if (p != NULL)
如下写法均属不良风格,不得分。
if (p == 0)
if (p != 0)
if (p)
if (!)
二、以下为Windows NT下的32位C++程序,请计算sizeof的值(10分)
三、简答题(25分)
char str[] = “Hello” ;
char *p = str ;
int n = 10;
请计算
sizeof (str ) = 6 (2分)
sizeof ( p ) = 4 (2分)
sizeof ( n ) = 4 (2分)
void Func ( char str[100])
{
请计算
sizeof( str ) = 4 (2分)
}
void *p = malloc( 100 );
请计算
sizeof ( p ) = 4 (2分)
1、头文件中的#ifndef/#define/#endif 干什么用?(5分)
答:防止该头文件被重复引用。
PS:#pragma once 跟上述语句作用一样,编译速度更快,两者区别如下(详见http://baike.baidu.com/view/1276747.htm):
#pragma once是编译器相关的,就是说即使这个编译系统上有效,但在其他编译系统也不一定可以,不过现在基本上已经是每个编译器都有这个杂注了。
#ifndef,#define,#endif是C/C++语言中的宏定义,通过宏定义避免文件多次编译。所以在所有支持C++语言的编译器上都是有效的,如果写的程序要跨平台,最好使用这种方式。2、#include <filename.h> 和 #include “filename.h” 有什么区别?(5分)
答:对于#include <filename.h> ,编译器从标准库路径开始搜索 filename.h
对于#include “filename.h” ,编译器从用户的工作路径开始搜索 filename.h
3、const 有什么用途?(请至少说明两种)(5分)
答:(1)可以定义 const 常量
(2)const可以修饰函数的参数、返回值,甚至函数的定义体。被const修饰的东西都受到强制保护,可以预防意外的变动,能提高程序的健壮性。
4、在C++ 程序中调用被 C编译器编译后的函数,为什么要加 extern “C”? (5分)
答:C++语言支持函数重载,C语言不支持函数重载。函数被C++编译后在库中的名字与C语言的不同。假设某个函数的原型为: void foo(int x, int y);
该函数被C编译器编译后在库中的名字为_foo,而C++编译器则会产生像_foo_int_int之类的名字。
C++提供了C连接交换指定符号extern“C”来解决名字匹配问题。
5、请简述以下两个for循环的优缺点(5分)
for (i=0; i<N; i++)
{
if (condition)
DoSomething();
else
DoOtherthing();
}
if (condition)
{
for (i=0; i<N; i++)
DoSomething();
}
else
{
for (i=0; i<N; i++)
DoOtherthing();
}
优点:程序简洁
缺点:多执行了N-1次逻辑判断,并且打断了循环“流水线”作业,使得编译器不能对循环进行优化处理,降低了效率。
优点:循环的效率高
缺点:程序不简洁
四、有关内存的思考题(每小题5分,共20分)
void GetMemory(char *p)
{
p = (char *)malloc(100);
}
void Test(void)
{
char *str = NULL;
GetMemory(str);
strcpy(str, "hello world");
printf(str);
}
请问运行Test函数会有什么样的结果?
答:程序崩溃。
因为GetMemory并不能传递动态内存,
Test函数中的 str一直都是 NULL。
strcpy(str, "hello world");将使程序崩溃。
char *GetMemory(void)
{
char p[] = "hello world";
return p;
}
void Test(void)
{
char *str = NULL;
str = GetMemory();
printf(str);
}
请问运行Test函数会有什么样的结果?
答:可能是乱码。
因为GetMemory返回的是指向“栈内存”的指针,该指针的地址不是 NULL,但其原现的内容已经被清除,新内容不可知。
void GetMemory2(char **p, int num)
{
*p = (char *)malloc(num);
}
void Test(void)
{
char *str = NULL;
GetMemory(&str, 100);
strcpy(str, "hello");
printf(str);
}
请问运行Test函数会有什么样的结果?
答:
(1)能够输出hello
(2)内存泄漏
void Test(void)
{
char *str = (char *) malloc(100);
strcpy(str, “hello”);
free(str);
if(str != NULL)
{
strcpy(str, “world”);
printf(str);
}
}
请问运行Test函数会有什么样的结果?
答:篡改动态内存区的内容,后果难以预料,非常危险。
因为free(str);之后,str成为野指针,
if(str != NULL)语句不起作用。
五、编写strcpy函数(10分)
已知strcpy函数的原型是
char*strcpy(char *strDest, const char *strSrc);
其中strDest是目的字符串,strSrc是源字符串。
(1)不调用C++/C的字符串库函数,请编写函数 strcpy
char *strcpy(char *strDest, constchar *strSrc);
{
assert((strDest!=NULL) && (strSrc !=NULL)); //2分
char *address = strDest; // 2分
while( (*strDest++ = * strSrc++) != ‘\0’ ) // 2分
NULL ;
return address ; // 2分
}
(2)strcpy能把strSrc的内容复制到strDest,为什么还要char * 类型的返回值?
答:为了实现链式表达式。 // 2分
例如 intlength = strlen( strcpy( strDest, “hello world”) );
六、编写类String的构造函数、析构函数和赋值函数(25分)
已知类String的原型为:
classString
{
public:
String(const char *str =NULL); // 普通构造函数
String(const String&other); // 拷贝构造函数
~String(void); // 析构函数
String & operate =(constString &other); // 赋值函数
private:
char *m_data; // 用于保存字符串
};
请编写String的上述4个函数。
标准答案:
// String的析构函数
String::~String(void) // 3分
{
delete [] m_data;
// 由于m_data是内部数据类型,也可以写成delete m_data;
}
//String的普通构造函数
String::String(constchar *str) // 6分
{
if(str==NULL)
{
m_data = new char[1]; // 若能加 NULL 判断则更好
*m_data = ‘\0’;
}
else
{
int length = strlen(str);
m_data = new char[length+1]; // 若能加 NULL 判断则更好
strcpy(m_data, str);
}
}
// 拷贝构造函数
String::String(constString &other) // 3分
{
int length =strlen(other.m_data);
m_data = new char[length+1]; // 若能加 NULL 判断则更好
strcpy(m_data, other.m_data);
}
// 赋值函数
String& String::operate =(const String &other) // 13分
{
//(1) 检查自赋值 // 4分
if(this == &other)
return *this;
// (2) 释放原有的内存资源 //3分
delete [] m_data;
//(3)分配新的内存资源,并复制内容 // 3分
int length =strlen(other.m_data);
m_data = newchar[length+1]; // 若能加 NULL 判断则更好
strcpy(m_data, other.m_data);
//(4)返回本对象的引用 //3分
return *this;
}
11.宏定义中的#和##(谨记:宏的处理是在预编译进行的,只是展开和替换而已)
# ——把后面的符号(宏参数)进行字符串化操作,把传入的变量名(或者常量名)跟#前面的字符做连接并把连接结果当成字符串来用
##——连接两个参数(符号),把传入的变量名(或者常量名)跟##前面的字符做连接并把连接结果当成变量或者常量来用[dongsong@localhost c++_study]$ cat define##.cpp #include <iostream> using namespace std ; #define TEST(pid) (cout << para##pid << endl) ; #define TEST2(p) (cout << "hello"#p << endl); int main() { int para3 = 3; int para2 = 2; TEST(2); TEST(3); TEST2(test); TEST2("test2"); TEST2(para2); return 0; } [dongsong@localhost c++_study]$ g++ -o define## define##.cpp [dongsong@localhost c++_study]$ ./define## 2 3 hellotest hello"test2" hellopara2
12.extern的作用 参考:http://www.cnblogs.com/yc_sunniwell/archive/2010/07/14/1777431.html
extern可以置于变量或者函数前,以标示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义。此外extern也可用来进行链接指定。
也就是说extern有两个作用,第一个,当它与"C"一起连用时,如: extern "C" void fun(int a, int b);则告诉编译器在编译fun这个函数名时按着C的规则去翻译相应的函数名而不是C++的,C++的规则在翻译这个函数名时会把fun这个名字变得面目全非,可能是fun@aBc_int_int#%$也可能是别的,这要看编译器的"脾气"了(不同的编译器采用的方法不一样),为什么这么做呢,因为C++支持函数的重载啊,在这里不去过多的论述这个问题,如果你有兴趣可以去网上搜索,相信你可以得到满意的解释!
第二,当extern不与"C"在一起修饰变量或函数时,如在头文件中: extern int g_Int; 它的作用就是声明函数或全局变量的作用范围的关键字,其声明的函数和变量可以在本模块或其他模块中使用,记住它是一个声明不是定义!也就是说B模块(编译单元)要是引用模块(编译单元)A中定义的全局变量或函数时,它只要包含A模块的头文件即可,在编译阶段,模块B虽然找不到该函数或变量,但它不会报错,它会在连接时从模块A生成的目标代码中找到此函数。第二点的附加说明:从本质上来讲,变量和函数没有区别。函数名是指向函数二进制块开头处的指针。如果文件a.c需要引用b.c中的函数,比如在b.c中原型是int fun(int mu),那么就可以在a.c中声明extern int fun(int mu),然后就能使用fun来做任何事情。就像变量的声明一样,externint fun(int mu)可以放在a.c中任何地方,而不一定非要放在a.c的文件作用域的范围中。对其他模块中函数的引用,最常用的方法是包含这些函数声明的头文件。使用extern和包含头文件来引用函数有什么区别呢?extern的引用方式比包含头文件要简洁得多!extern的使用方法是直截了当的,想引用哪个函数就用extern声明哪个函数。这大概是KISS原则的一种体现吧!这样做的一个明显的好处是,会加速程序的编译(确切的说是预处理)的过程,节省时间。在大型C程序编译过程中,这种差异是非常明显的。
验证第二点的程序:[dongsong@localhost c++_study]$ cat t1.h #ifndef __T1_H #define __T1_H int t1_func(void); #endif [dongsong@localhost c++_study]$ cat t1.c #include <stdio.h> #include "t1.h" int t1_func(void) { fprintf(stderr, "I am in file t1.c"); } [dongsong@localhost c++_study]$ cat t2.c //#include "t1.h" extern int t1_func(void); int main(int argc, char** argv) { t1_func(); } [dongsong@localhost c++_study]$ gcc -o t2 t1.c t2.c [dongsong@localhost c++_study]$ ./t2 I am in file t1.c
13.字节序,大头小头 --- 不属于C/C++的问题,没想好归类,也懒得单开文章,就追加在此了 (2013.10.22)
维基百科 http://zh.wikipedia.org/wiki/%E5%AD%97%E8%8A%82%E5%BA%8F
百度百科 http://baike.baidu.com/view/567601.htm
1>存储地址内的排列有两个通用规则。一个多位的整数将按照其存储地址的最低或最高字节排列。如果最低有效字节在最高有效字节的前面,则称小端序;反之则称大端序。在网络应用中,字节序是一个必须被考虑的因素,因为不同机器类型可能采用不同标准的字节序,所以均按照网络标准转化。
2>对于单一的字节(a byte),大部分处理器以相同的顺序处理位元(bit),因此单字节的存放方法和传输方式一般相同。
3>网络传输一般采用大端序,也被称之为网络字节序,或网络序。IP协议中定义大端序为网络字节序。
4>处理器(详见维基页面)
x86,MOS Technology 6502,Z80,VAX,PDP-11等处理器为Little endian
Motorola 6800,Motorola 68000,PowerPC 970,System/370,SPARC(除V9外)等处理器为Big endian
ARM, PowerPC (除PowerPC 970外), DEC Alpha, SPARC V9, MIPS, PA-RISC and IA64的字节序是可配置的
5>一个多字节值 0xFECDBA98,内存从地址100开始存放:
大端序(big-endian 大头): FE(对应地址100) | CD(对应地址101) | BA(对应地址102) | 98(对应地址103)
小端序(little-endian 小头): 98 | BA | CD | FE
6>转换
16位的转换函数:ntohs(network to host short) htons(host to network short)
32位转换函数ntohl和htonl: ntohl(network to host long) htons(host to network long)
14.float double 在内存中的存储
内容转自http://www.cppblog.com/aaxron/archive/2011/12/03/161347.html
从存储结构和算法上来讲,double和float是一样的,不一样的地方仅仅是float是32位的,double是64位的,所以double能存储更高的精度。
任何数据在内存中都是以二进制(0或1)顺序存储的,每一个1或0被称为1位,而在x86CPU上一个字节是8位。比如一个16位(2 字节)的short int型变量的值是1000,那么它的二进制表达就是:00000011 11101000。由于Intel CPU的架构原因,它是按字节倒 序存储的,那么就因该是这样:11101000 00000011,这就是定点数1000在内存中的结构。
目前C/C++编译器标准都遵照IEEE制定的浮点数表示法来进行float,double运算。
这种结构是一种科学计数法,用符号、指数和 尾数来表示,底数定为2——即把一个浮点数表示为尾数乘以2的指数次方再添上符号。
下面是具体的规格:
类型 符号位 阶码 尾数 长度
float 1 8 23 32
double 1 11 52 64
临时数 1 15 64 80
由于通常C编译器默认浮点数是double型的,下面以double为例: 共计64位,折合8字节。
由最高到最低位分别是第63、62、61、……、0位: 最高位63位是符号位,1表示该数为负,0正; 62-52位,一共11位是指数位; 51-0位,一共52位是尾数位。
按照IEEE浮点数表示法,下面将把double型浮点数38414.4转换为十六进制代码。
把整数部和小数部分开处理:整数部直接化十六进制:960E。小数的处理: 0.4=0.5*0+0.25*1+0.125*1+0.0625*0+…… 实际上这永远算不完!这就是著名的浮点数精度问题。所以直到加上前面的整数部分算够53位就行了 (隐藏位技术:最高位的1 不写入内存)。
如果你够耐心,手工算到53位那么因该是:38414.4(10)=1001011000001110.0110101010101010101010101010101010101(2)
科学记数法为:1.001……乘以2的15次方。指数为15! 于是来看阶码,一共11位,可以表示范围是-1024 ~ 1023。因为指数可以为负,为了便于计算,规定都先加上1023,在这里, 15+1023=1038。
二进制表示为:100 00001110 符号位:正—— 0 ! 合在一起(尾数二进制最高位的1不要): 01000000 11100010 11000001 11001101 01010101 01010101 01010101 01010101 按字节倒序存储的十六进制数就是: 55 55 55 55 CD C1 E2 40。
[dongsong@localhost c++-study]$ cat float_format.cpp
#include<iostream>
#include<stdio.h>
using namespace std;
int main(int argc, char** argv)
{
float f = 100.1011;
unsigned int a = *(unsigned int*)&f;
printf("%.5f\n", f);
printf("%d\n", a);
printf("%.5f\n", *(float*)&a);
cout << "------------------------------------" << endl;
cout << f << endl;
cout << a << endl;
cout << *(float*)&a << endl;
}
[dongsong@localhost c++-study]$
[dongsong@localhost c++-study]$ !g++
g++ -o float_format float_format.cpp ; ./float_format
100.10110
1120416707
100.10110
------------------------------------
100.101
1120416707
100.101
这个程序表明:1>默认cout输出float的时候只做到三位小数的精度 2>程序中一个四字节的内存空间存放的数据被解释成float还是int都由我们自己决定,如何解释都不会让数据改变或丢失
15.g++如何输出英文的信息(中文的错误信息google不到结果,faint~)(2014.4.21)
dongsong@localhost c++-study]$ export LC_ALL=en_GB.UTF-8
[dongsong@localhost c++-study]$ g++ -o TemplateTest main.cpp
main.cpp:9: error: multiple types in one declaration
main.cpp: In function ‘int main(int, char**)’:
main.cpp:13: error: non-template type ‘Test’ used as a template
main.cpp:13: error: invalid type in declaration before ‘;’ token
main.cpp:14: error: request for member ‘GetObject’ in ‘t’, which is of non-class type ‘int’
16.template class定义和实现要保证都在.h文件进行
[dongsong@localhost c++-study]$ cat TemplateTest.h
template <class T> class Test
{
public:
Test();
~Test();
T* GetObject();
private:
T* m_obj;
};
[dongsong@localhost c++-study]$ cat TemplateTest.cpp
#include "TemplateTest.h"
template<class T> Test<T>::Test() { m_obj = new T; }
template<class T> Test<T>::~Test() { delete m_obj; }
template<class T> T* Test<T>::GetObject() { return m_obj; }
[dongsong@localhost c++-study]$ cat main.cpp
#include <iostream>
using namespace std;
#include "TemplateTest.h"
struct Object {
int a;
int b;
Object() { a=100; b=200; }
void Show() { cout << "(" << a << "," << b << ")" << endl; }
};
int main(int argc, char** argv)
{
Test<Object> t;
t.GetObject()->Show();
return 0;
}
[dongsong@localhost c++-study]$ !g++
g++ -o TemplateTest main.cpp
/tmp/ccOvY1tF.o: In function `main':
main.cpp:(.text+0x1a): undefined reference to `Test<Object>::Test()'
main.cpp:(.text+0x26): undefined reference to `Test<Object>::GetObject()'
main.cpp:(.text+0x3f): undefined reference to `Test<Object>::~Test()'
main.cpp:(.text+0x5b): undefined reference to `Test<Object>::~Test()'
collect2: ld 返回 1
VS报错:
1>test.obj : error LNK2019: unresolved external symbol "public: bool __thiscall TowerAoi::Tower<struct Object>::Add(struct Object *)" (?Add@?$Tower@UObject@@@TowerAoi@@QAE_NPAUObject@@@Z) referenced in function "public: bool __thiscall TowerAoi::TowerAoi<struct Object>::AddObject(struct Object *,struct Location const &)" (?AddObject@?$TowerAoi@UObject@@@TowerAoi@@QAE_NPAUObject@@ABULocation@@@Z)
1>E:\projects\aoi\Debug\aoi.exe : fatal error LNK1120: 1 unresolved externals
http://stackoverflow.com/questions/15577478/separate-h-and-cpp-files-in-template-class-implementation
http://stackoverflow.com/questions/495021/why-can-templates-only-be-implemented-in-the-header-file
17.const位置不同效果也不同
const int* p 指针p所指的内容不能变,p的指向可以变
int* const p 指针p指向不能改变,p所指的内容可以变
int const n=21; n是常量,不能变,也可以写成const int 21;定义常量时必须有初始的值
int Binarysearch(T &x,const int low,const int high); 这种写法是不好的,应该写成int Binarysearch(cosnt T &x,int low,int high); 一般函数传递引用的时候,如果你不想改变改传递数据的值,那么应该将传递的引用声明为const,不然可以通过x改变你原数据的值,传递引用主要是为了节省传递实参的时间.而把low和high声明为const根本没有意义
int Find(T &x)const; 这样的函数用在类中,函数后面加const是不能改变类中成员的值
18.inf nan
inf,infinity,无穷,超出浮点表示范围的数值,1.0/0.0等于inf,-1.0/0.0等于-inf,0.0+inf=inf
nan,not a number,和自己做比较返回值为假,对负数开方sqrt(-1.0)、对负数求对数(log(-1.0))、0.0/0.0、0.0*inf、inf/inf、inf-inf这些操作都会得到nan。(0/0会产生操作异常;0.0/0.0不会产生操作异常,而是会得到nan)
#include<math.h>
int fpclassify(x);
FP_NAN:x是一个“not a number”。
FP_INFINITE: x是正、负无穷。
FP_ZERO: x是0。
FP_SUBNORMAL: x太小,以至于不能用浮点数的规格化形式表示。
FP_NORMAL: x是一个正常的浮点数(不是以上结果中的任何一种)。
int isfinite(x);
int isnormal(x);
int isnan(x);
int isinf(x);
19.今天看到skynet中有函数名跟宏定义同名的代码,一时没想明白就查了一下
#define skynet_malloc malloc
#define skynet_calloc calloc
#define skynet_realloc realloc
#define skynet_free free
void * skynet_malloc(size_t sz);
void * skynet_calloc(size_t nmemb,size_t size);
void * skynet_realloc(void *ptr, size_t size);
void skynet_free(void *ptr);
我们知道编译器编译C语言程序时,先执行预编译,然后再执行编译、汇编和链接等步骤。预编译阶段质主要处理宏,而编译阶段同名的宏已经被替换掉了,故而此时是不存在标识符冲突的。
20.今天(2014.10.31)把一个地图解析程序打包成python的扩展时遇到一个问题,敲了一个警钟。
assert(InitMap(mapId));
bool rt = InitMap(mapId);
assert(rt);
前者到处的so在python中使用正常,后者异常。
原因是,编译成python的so的时候默认release版本,而release版本assert不光是不触发异常,而且assert(xxx)中的xxx是不执行的(所以不应该把xxx搞成业务需要的表达式,这一点我一直有怀疑但是没有细查手册)。
assert.h的源码显示release版本assert里面的表达式还是应该被执行的,故这个疑惑还得再查一下(时间不早了,回头再查)。
# define assert(expr) \
((expr) \
? __ASSERT_VOID_CAST (0) \
: __assert_fail (__STRING(expr), __FILE__, __LINE__, __ASSERT_FUNCTION))
--> 确认了(2017.5.23),release版本assert(expr)里面的expr不会被执行. If the macro NDEBUG was defined at the moment <assert.h> was last included, the macro assert() generates no code, and hence does nothing at all.
21.关于头文件中符号定义的问题(2014.12.18)
我理解的是:在一个应用程序编译过程中,某个头文件中的符号是否有效需要看它本身是否引入了符号定义的文件 或者在之前的编译过程中是否已经引入过符号定义的头文件
[dongsong@localhost include_test]$ cat main.cpp
#include <map>
using namespace std;
#include "test.h"
#include <stdio.h>
int main(int argc, char** argv)
{
MyMap myMap;
myMap[1] = 2;
printf("element %d\n", myMap[1]);
return 0;
}
[dongsong@localhost include_test]$ cat test.h
typedef map<int, int> MyMap;
[dongsong@localhost include_test]$ g++ main.cpp
[dongsong@localhost include_test]$ ./a.out
element 2
而cpp文件中符号是否有效得看符号定义的头文件是否已直接或间接的引入到当前cpp。
22.改写new的行为
[root@test-22 c++-study]# cat newtest.cpp
#include<stdio.h>
#include <stdlib.h>
void* operator new (size_t size, const char *file,int line,const char *func, const char* arg)
{
printf("func new %d bytes, file(%s) line(%d) func(%s) arg(%s)\n\n", size, file, line, func, arg);
return malloc ( size ) ;
}
void* operator new (size_t size, void* p)
{
printf("func new %d bytes, ptr(%p)\n\n", size, p);
return p;
}
class MyClass{
public:
void* operator new(size_t size, const char *file, int line, const char *func, const char* arg) {
printf("MyClass:new %d bytes, file(%s) line(%d) func(%s) arg(%s)\n\n", size, file, line, func, arg);
return malloc ( size ) ;
}
void* operator new(size_t size, const char *arg1, const char *arg2) {
printf("MyClass:new %d bytes, arg1(%s), arg2(%s)\n\n", size, arg1, arg2);
return malloc ( size ) ;
}
void* operator new(size_t size, void* p) {
printf("MyClass:new %d bytes, ptr(%p)\n\n", size, p);
return malloc ( size ) ;
}
};
void TestFunc() {
int* a = new int;
delete a;
int* b = new (__FILE__, __LINE__, __FUNCTION__,"aaa") int;
delete b;
char buf[100] = {0};
int* c = new (buf) int;
*c = 'a';
printf("c(%d) buf(%s)\n\n", *c, buf);
MyClass *m = new (__FILE__, __LINE__, __FUNCTION__, "abc") MyClass();
delete m;
MyClass *n = new ("abc","def") MyClass();
delete n;
MyClass *l = new (buf) MyClass();
delete l;
return ;
}
int main (int argc, char** argv){
TestFunc();
return 0;
}
[root@test-22 c++-study]# g++ -o newtest newtest.cpp ; ./newtest
func new 4 bytes, file(newtest.cpp) line(36) func(TestFunc) arg(aaa)
func new 4 bytes, ptr(0x7fffd22eeb60)
c(97) buf(a)
MyClass:new 1 bytes, file(newtest.cpp) line(44) func(TestFunc) arg(abc)
MyClass:new 1 bytes, arg1(abc), arg2(def)
MyClass:new 1 bytes, ptr(0x7fffd22eeb60)