C/C++知识点记录
1. strcpy 的部分理解
char* strcpy(char *dest, const char*src)
{
while(*dest++=*src++)
return dest-1;
}
这是strcpy的实现,这个实现是比较简洁的。
这儿值得思考的一个问题就是,为什么要有返回值,没有返回值可以吗?
这个问题非常的好,这种函数完全可以没有返回值。但是这种类似的函数提供返回值有一个很好的用处就是可以进行嵌套使用(链式表达),比如:strcpy(strcpy(dest,src1),src2)
其实C和C++中有很多类似的函数都是这么设计的。
2. linux中获得当前目录路径的C函数
主要涉及到的函数是:
#include <unistd.h>
char *getcwd(char *buf, size_t size);
char *getwd(char *buf);
char *get_current_dir_name(void);
第一个比较常用,其中buf是缓存,用于存储得到的路径,size是buf的大小。
第二个函数中和第一个类似,但是默认的size是采用PATH_MAX
第三个和前两个不同的是,前两个都是调用者已经分配好了内存buf,而现在了是使用动态分配内存的方式malloc来根据路径的长度进行内存的分配。所以,在使用的时候,注意要利用free进行释放内存。
3.字符串复制函数
char* strdup(const char*some_string);
该函数也是复制字符串,与strcpy不同的是它要自动的动态分配内存,并且使用完毕后要注意释放内存。
这个函数不是标准库中的函数,属于POSIX标准的
4. 写一个函数,完成内存之间的拷贝。[考虑问题是否全面]
答:
void* mymemcpy( void *dest, const void *src, size_t count ) { char* pdest = static_cast<char*>( dest ); const char* psrc = static_cast<const char*>( src ); if( pdest>psrc && pdest<psrc+cout ) 能考虑到这种情况就行了 { for( size_t i=count-1; i!=-1; --i ) pdest[i] = psrc[i]; } else { for( size_t i=0; i<count; ++i ) pdest[i] = psrc[i]; } return dest; }
5. c++中一种输出数组的方法
利用stl中的copy函数
copy(v.begin(),v.end(),ostream_iterator<int>(cout, " "));
或者
ostream_iterator< int > print( cout, " " );
copy(v.begin(),v.end(),print);
6. 利用位操作得到两个数较小数
利用按位操作来得到x和y中的较小的数:y^((x^y)&(-(x<y)))
当x<y时,y^((x^y)&(-(x<y))) = y^((x^y)&(-1)) = y^((x^y)&0xffffffff) = y^(x^y) = (y^y)^x = 0^x = x
当x>=y时,y^((x^y)&(-(x<y))) = y^((x^y)&(-0)) = y^((x^y)&0) = y^0 = y
7.判断一个数是不是2的n次方
((x > 0) && (0 == (x & (x - 1)))) ? printf("yes\n") : printf("not\n");
C++模板类的派生类中必须显式调用基类成员
进程内存分配
Linux下多线程开发
Linux 上线程开发 API 的概要介绍
多线程开发在 Linux 平台上已经有成熟的 Pthread 库支持。其涉及的多线程开发的最基本概念主要包含三点:线程,互斥锁,条件。其中,线程操作又分线程的创建,退出,等待 3 种。互斥锁则包括 4 种操作,分别是创建,销毁,加锁和解锁。条件操作有 5 种操作:创建,销毁,触发,广播和等待。其他的一些线程扩展概念,如信号灯等,都可以通过上面的三个基本元素的基本操作封装出来。
线程,互斥锁,条件在 Linux 平台上对应的 API 可以用表 1 归纳。为了方便熟悉 Windows 线程编程的读者熟悉 Linux 多线程开发的 API,我们在表中同时也列出 Windows SDK 库中所对应的 API 名称。
表 1. 线程函数列表
scanf输入格式:
1. 类似%*d的方式表示此输入的整数被忽略;
scanf("%d%*d%d", &i, &j); 输入: 100 2 200 然后回车,100被保存在i中,2被忽略, 200被保存在j中;
char s[32]; scanf("%[abcd]", s); %[abcd]表示如果输入的字符是a,b,c,d中的任何一个,那么接收,否则不接收;
如果输入 abr3dc 然后回车,那么字符串ab将被保存在s中。 这里可以看到正则表达式的影子。
查看c标准,可以发现%[abcd]也可以用%[a-d]来表示。
使用%i可以输入整数,但是可以输入八进制或者十六进制的形式,这是它和%d作为输入整数格式的不同之处:
scanf("%i", &i); 输入012然后回车,10会被保存在i中; 如果输入0x12, 那么18会被保存在i中;
按照标准来说,getc和fgetc在不涉及交互的时候是全缓冲模式(在缓冲区大小满后提交数据),如果是涉及到交互(如标准输入),那么是行缓冲;而getchar就行缓冲模式(回车会提交输入缓冲区数据,不过如果缓冲区已经有数据了就不需要等待回车了).
关于内存拷贝,memcpy和memmove到底有什么区别?
区别在于memmove处理了重复内存区域的拷贝,memcpy没有处理内存区域重复可能导致拷贝结果错误,这种情况下结果是不确定的。所以最好使用memmove,如果可以确定内存区域不重复,也可以直接使用memcpy.
11. C++库
C++各大有名库的介绍——C++标准库 标准库中提供了C++程序的基本设施。虽然C++标准库随着C++标准折腾了许多年,直到标准的出台才正式定型,但是在标准库的实现上却很令人欣慰得看到多种实现,并且已被实践证明为有工业级别强度的佳作。
Dinkumware C++ Library 参考站点:http://www.dinkumware.com/ P.J. Plauger编写的高品质的标准库。P.J. Plauger博士是Dr. Dobb’s程序设计杰出奖的获得者。其编写的库长期被Microsoft采用,并且最近Borland也取得了其OEM的license,在其 C/C++的产品中采用Dinkumware的库。
RogueWave Standard C++ Library 参考站点:http://www.roguewave.com/ 这个库在Borland C++ Builder的早期版本中曾经被采用,后来被其他的库给替换了。笔者不推荐使用。
SGI STL 参考站点:http://www.roguewave.com/ SGI公司的C++标准模版库。
STLport 参考站点:http://www.stlport.org/ SGI STL库的跨平台可移植版本。
12. 运算符重载
支持运算符重载和定义新运算符的语言:
PostgreSQL的SQL方言
Ruby
Haskell
支持运算符重载的语言:
Ada
C++
C#
D
Perl
Python
Pico (某种程度上)
Pascal (仅Free Pascal Dialect)
FreeBASIC
Visual Basic(需要 Visual Basic .NET 2008 或更高版本)
不支持运算符重载的语言:
C
Delphi
Java
15.fltk的使用
用标准的编译器编译程序
在UNIX环境下,你需要告诉编译器到哪里去找到头文件,通常用-I选项
CC -I/usr/local/include ...
gcc -I/usr/local/include ...
fltk-config脚本能自动找到头文件,但是你的头文件需要安装在指定的目录下。
CC `fltk-config --cxxflags` ...
同样,当连接你的程序时需要告诉编译器你的FLTK库
CC ... -L/usr/local/lib -lfltk -lXext -lX11 -lm
gcc ... -L/usr/local/lib -lfltk -lXext -lX11 -lm
除了fltk库之外,还有fltk_forms库支持Xforms类,fltk_gl支持OpenGL和GLUT类,fltk_image库支持image文件类,Fl_Help_Dialog控件和系统图标
注意:以上库的名称分别为 "fltk.lib", "fltkgl.lib", "fltkforms.lib", and "fltkimages.lib"
类似,fltk-config脚本也能获得库文件
CC ... `fltk-config --ldflags`
Forms,GL,images库在编译时应该使用”—use-foo”选项。比如:
CC ... `fltk-config --use-forms --ldflags`
CC ... `fltk-config --use-gl --ldflags`
CC ... `fltk-config --use-images --ldflags`
CC ... `fltk-config --use-forms --use-gl --use-images --ldflags`
最后你可以用fltk-config脚本编译一个简单的FLTK程序
fltk-config --compile filename.cpp
fltk-config --use-forms --compile filename.cpp
fltk-config --use-gl --compile filename.cpp
fltk-config --use-images --compile filename.cpp
fltk-config --use-forms --use-gl --use-images --compile filename.cpp
以上的编译将会产生一个可执行文件filename.
16. C/C++可重入问题
一、可重入函数
1)什么是可重入性?
可重入(reentrant)函数可以由多于一个任务并发使用,而不必担心数据错误。相反,不可重入(non-reentrant)函数不能由超过一个任务所共享,除非能确保函数的互斥(或者使用信号量,或者在代码的关键部分禁用中断)。可重入函数可以在任意时刻被中断,稍后再继续运行,不会丢失数据。可重入函数要么使用本地变量,要么在使用全局变量时保护自己的数据。
2)可重入函数:
不为连续的调用持有静态数据。
不返回指向静态数据的指针;所有数据都由函数的调用者提供。
使用本地数据,或者通过制作全局数据的本地拷贝来保护全局数据。
如果必须访问全局变量,记住利用互斥信号量来保护全局变量。
绝不调用任何不可重入函数。
3)不可重入函数:
函数中使用了静态变量,无论是全局静态变量还是局部静态变量。
函数返回静态变量。
函数中调用了不可重入函数。
函数体内使用了静态的数据结构;
函数体内调用了malloc()或者free()函数;
函数体内调用了其他标准I/O函数。
函数是singleton中的成员函数而且使用了不使用线程独立存储的成员变量 。
总的来说,如果一个函数在重入条件下使用了未受保护的共享的资源,那么它是不可重入的。
20.利用gcc生成静态库和动态库【存档自用】
假设当前目录下有这些源文件:[main.c func.c func.h],其中main.c要调用func.c中的函数。
【1】生成静态库:
$ gcc -c func.c -o func.o
$ ar rcs libfunc.a func.o
$ gcc main.c -o main -static -L. -lfunc
$ ./main
【2】生成动态库:
$ gcc -fPIC -c func.c -o func.o
$ gcc -shared -o libfunc.so.1.0.0 func.o
$ ln -s libfunc.so.1.0.0 libfunc.so
$ gcc main.c -o main -L. -lfunc
$ export LD_LIBRARY_PATH=$(pwd)
$ ./main
如果将so文件copy到系统lib目录(如/usr/lib),则最后2步就不用了。
最后还有3个小知识:
【1】nm命令:列出目标文件或2进制文件的所有符号。
【2】ldd命令:列出为了使程序正常运行所需要的所有共享库。
【3】/etc/ld.so.conf文件:除了标准目录(/lib和/usr/lib)之外,链接器和加载器搜索共享库时要检查的其他目录,和这个文件相关的一个命令是:ldconfig 。
21.dbm数据库
编译的时候使用的编译命令如下,引入/usr/include/gdbm头文件搜索路径,链接库文件gdbm
gcc -o dbm1 -I/usr/include/gdbm dbm1.c -lgdbm
但是编译不通过,查看了头文件ndbm.h里面的内容没有错的的,命名有声明这个几个函数,然后百度之后找到答案,需要链接一个gdbm_compat库才可以。使用命令如下,编译成功。
gcc -o dbm1 -I/usr/include/gdbm dbm1.c -lgdbm -lgdbm_compat
非C++内建类型A和B,在那几种情况下B能够隐式转化为A?
有四种方法:
第一种:class B: public A {…}
B公有继承A,可以是间接公有继承,当把B的对象赋值给A,会发生隐式转换。
(待求证,保护继承、私有继承、B是A的成员能否发生转换?)
第二种:class B:
{
Operator A();
….
}
转换constructor。类B实现了隐式转化为类A;compiler会在需要的时候自动调用该函数发生类型转换,如果想要在代码中显式的调用转换函数才能发生类型转化,可以定义explicit operator A()
第三种:class A
{
A (const B &)
}
A实现了一个个non-explicit的构造函数,参数为B(还可以带其他的有缺省值的参数)
第四种:A &operator =(const B & )
赋值操作,不是正宗的隐式类型转换,但可以勉强算一个。
注意:对于类之间的公有继承总可以把子类转化为父类,只是把派生类对象切割为基类对象即可。
注意2:上述第二种和第三种方法同时存在一个程序中,应该注意这样的调用:
f(const A &);
B b;
f(b)则会产生调用的二义性。
注意3:如果不想使用隐式生成的函数(当然这些函数一般是缺省构造函数、copy构造函数和赋值构造函数),就要把它显式的禁止;对于一般的转换constructor可以添加explicit明确的要求显式的调用,compiler不能自动发生隐式转换。如:
Private:
A & operator = (const B &);
A ( const A & );
成员在类中的偏移量 & 类成员指针
下面一个程序输出结果:
#include <stdio.h> class A { public: A() {m_a = 1; m_b = 2;} ~A() {} void fun() {printf("%d %d", m_a, m_b);} private: int m_a; int m_b; }; class B { public: B() {m_c = 3;} ~B() {} void fun() {printf("%d", m_c);} private: int m_c; }; void main() { A a; B *pb = (B*)(&a); pb->fun(); }
程序的输出结果为1。
这里主要涉及到两方面:一是对象调用成员函数时会将调用对象与函数绑定;二是对象访问成员是根据该成员距离对象的偏移量来访问的,而不是根据成员名来访问,所谓偏移量,就是告诉你一个特定的成员位置距离对象的起点有多少个字节。
上面程序,内存中实例化了一个A类对象,然后将该地址强制转换成一个B类地址,即将该对象的地址内容强制看成一个B类对象。pb为B类的指针,理所当然调用的是B类中的fun()函数(可以跟多态的情形相比较),当调用fun()函数时,调用对象与该函数进行绑定,即fun()函数中隐含的形参this指针初始化为调用对象(A类对象)的地址,假设为0xff80。然后fun()函数打印值m_c。这里要注意,对象在访问类成员时,编译器并没有存储该对象各个成员的实际地址,而是存储了其相对于当前对象首地址的偏移量,由于B类只有一个成员m_c,在编译阶段,编译器就记录了m_c对于B类对象的偏移量为0,故访问m_c时,便是访问当前对象地址this+偏移量0,注意,this在这里绑定的是A类对象的首地址,在A类中,偏移量为0的成员是m_a,故打印出m_a的值。
关于类成员偏移量的输出,可以用程序验证。
例如如下程序:将地址0强制转换为A类对象的地址,那么打印类成员m_a和m_b的地址便是他们的偏移量,如下分别输出0, 4。
cout<<&((A*)0)->m_a<<endl;
cout<<&((A*)0)->m_b<<endl;
再如,我们可以通过输出类成员指针来验证。
printf("%p\n",&A::m_a);
printf("%p\n",&A::m_b);
分别输出 00000000,00000004。
如程序,&A::m_a实际是一个指向int型的A类的成员指针,用m_a初始化,即相当于:
int A::*ptr = &A::m_a;
这里说一下,输出成员指针的值,最好使用printf,%p输出指针,我曾试图使用语句
cout<<&A::m_a<<&A::m_b;
结果全是1。
究其原因,应该是ostream对象没有重载类成员指针的参数,故不能直接输出类成员指针的类型,而我们知道指针类型与bool类型的转换属于标准转换的(常常用来测试指针合法性是否为空),而ostream对象可以输出bool类型,故编译器将成员指针类型转换成了bool类型,从而输出,既然这样为什么全是输出1呢?说明地址全是合法的,即偏移量全是大于0,不对呀,第一个类成员的偏移量不是0么,这里真心不明白,不过《C++必知必会》中有这样一句话:大多数编译器都将成员指针实现为一个整数,包含被指向成员的偏移量,另外加上1(加1是为了让值0可以表示一个空的数据成员指针)。这大概就是全输出1的原因了吧。
关于成员指针
成员指针只是记录一个成员的偏移量,而非地址,因为类中没有地址,选择一个类的成员只是意味着在类中偏移,只有把这个偏移和具体对象的首地址结合,才能得到实际地址。
成员指针并不指向一个具体的内存位置,它指向的是一个类的特定成员,而不是指向一个特定对象的特定成员,最直接的理解是将其理解为一个偏移量。这个偏移量适用于某一类A的任何对象,换言之,如果一个A类对象的成员a距离起点的偏移量是4,那么任何其他A类对象中,a的偏移都是4字节。
总结一下就是两句话:
类对象访问其成员时,是根据该成员在类中的偏移量来访问的。
类成员指针,可以理解为指向类数据成员的一个偏移量,而非地址。
参考:http://www.ahathinking.com/archives/98.html
const在类中的作用
一般来讲,使用const修饰函数,则函数一定是类成员函数。
const对象只能调用const成员函数 非const对象可以调用const成员函数
解释:不能将指向const对象的指针赋值给非const对象指针,而允许将非const对象指针赋值给指向const对象的指针。
为什么这样解释?这源自于调用成员函数时发生的事情:
调用成员函数时会发生的事情:将调用对象与函数绑定,即将成员函数隐含的形参this初始化为调用对象的地址。
因此,const对象传递的实参地址为const class * const this,而非const成员函数被调用时还是使用了class *const this的形参接收,结果就是把指向const对象的指针赋给非const对象的指针,这是不允许的,所以const对象只能调用const成员函数;后者同理,非const对象在调用const成员函数时实质上是将非const对象的指针(实参)赋值给了const对象指针(形参),而这又是可以的,故非const对象可以调用const成员函数