当写C语言写多了,自然就喜欢C++了----小话c++(1)

news/2024/12/29 15:22:52/

[Mac  10.7.1  Lion  x64  Intel-based  gcc4.2.1  xcode4.2]


Q: 解释下标题吧。

A: 依稀记得,写一个数值绝对值的函数时,写到第三个,实在感觉很痛苦,重复了这么多遍,立刻体会了重载和STL的重要意义。

int	abs(int n)
{return n < 0 ? -n : n;
}long	abs_long(long n)
{return n < 0 ? -n : n;
}double	abs_double(double n)
{return n < 0 ? -n : n;
}
写到第三个函数的时候,感觉实在不爽,原来c++中的重载这么现实,很清楚c程序员的痛苦之处;对于基本类型,很多绝对值操作都是类似的,为什么还要写这么多函数?写代码不是比谁写的多,是比简洁易懂和稳定, STL更明白写上面代码的痛苦。
template<class T>
T	abs(T a)
{return a < 0 ? -a : a;
}
当然,也可以用宏来实现,不过不是很推荐:
#define	ABS(a)	((a) < 0 ? (-a) : (a))
不小心就可能有问题:
std::cout << ABS(-12.4) << std::endl;
编译提示:
error C2105: '--' needs l-value
哦,原来ABS(-12.4)被宏替换成了--12.4.宏有的时候真得小心啊...
#define	ABS(a)	((a) < 0 ? -(a) : (a))
这样编译就ok了。

另外,还有,用c语言的时候,经常会写到一个结构体以及对结构体的操作,必须申请空间来构造某个结构,写了N个malloc,直到每次写malloc都有种想吐的感觉,c++明白了这个痛苦之处,构造函数让写malloc到吐的程序员迅速爱上c++.

  还有很多地方是c++对于c语言的一些改进,这里不一一介绍了。


Q: 在c++中,使用cstring头文件和string.h有什么区别?

A: 先来看两个例子:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>#define PRINT_D(intValue)       printf(#intValue" is %d\n", (intValue));
#define PRINT_STR(str)          printf(#str" is %s\n", (str));
#define FOR_EVER()              { while(1) ; }int main()
{size_t len = ::strlen("hello");return 0;
}
如上,保存为std_strlen.c, 编译:

error: expected expression [1]size_t len = ::strlen("hello");^
1 error generated.
可以看出,c语言中并没有作用域运算符,这里编译错误;

#include <iostream>
#include <cstring>int main()
{size_t len = ::strlen("hello");return 0;
}
保存为main.cpp,编译,没出现什么问题。

这里就体现了cstring和string.h的不同之处:c++支持了作用域运算符::, 原先c库中的函数被默认当成c++中全局作用域的函数,当然也是std作用域的函数。如下:

len = std::strlen("hello");
上面的代码依然可以编译通过。现在再看看cstring和string.h头文件里面的内容:

cstring头文件(部分):

_GLIBCXX_BEGIN_NAMESPACE(std)using ::memcpy;using ::memmove;using ::strcpy;using ::strncpy;using ::strcat;using ::strncat;using ::memcmp;using ::strcmp;using ::strcoll;using ::strncmp;using ::strxfrm;using ::strcspn;using ::strspn;............_GLIBCXX_END_NAMESPACE

string.h头文件(部分):

__BEGIN_DECLS
void	*memchr(const void *, int, size_t);
int	 memcmp(const void *, const void *, size_t);
void	*memcpy(void *, const void *, size_t);
void	*memmove(void *, const void *, size_t);
void	*memset(void *, int, size_t);
char	*strcat(char *, const char *);
char	*strchr(const char *, int);
int	 strcmp(const char *, const char *);
int	 strcoll(const char *, const char *);
char	*strcpy(char *, const char *);
size_t	 strcspn(const char *, const char *);
char	*strerror(int) __DARWIN_ALIAS(strerror);
size_t	 strlen(const char *);
char	*strncat(char *, const char *, size_t);
int	 strncmp(const char *, const char *, size_t);
char	*strncpy(char *, const char *, size_t);
char	*strpbrk(const char *, const char *);
char	*strrchr(const char *, int);
size_t	 strspn(const char *, const char *);
char	*strstr(const char *, const char *);
char	*strtok(char *, const char *);
size_t	 strxfrm(char *, const char *, size_t);
__END_DECLS

Q: c++中的引用到底和类似功能的指针有什么不同?

A: 从本质上来说,基本没什么不同;从使用上来看,是有不同的。如下例子:

#include <iostream>
#include <cstring>#define COUT_ENDL(str)  std::cout << #str << " is " << (str) << std::endl;void    swap(int &a, int &b)
{int temp = a;a = b;b = temp;
}void    swap(int *pa, int *pb)
{int temp = *pa;*pa = *pb;*pb = temp;
}int main()
{int a = 1, b = 2;swap(a, b);COUT_ENDL(a)COUT_ENDL(b)a = 1, b = 2;swap(&a, &b);COUT_ENDL(a)COUT_ENDL(b)return 0;
}
保存为main.cpp.

void swap(int &a, int &b);函数的汇编如下:

0x0000000100000c00 <_Z4swapRiS_+0>:	push   %rbp
0x0000000100000c01 <_Z4swapRiS_+1>:	mov    %rsp,%rbp
0x0000000100000c04 <_Z4swapRiS_+4>:	mov    %rdi,-0x8(%rbp)
0x0000000100000c08 <_Z4swapRiS_+8>:	mov    %rsi,-0x10(%rbp)
0x0000000100000c0c <_Z4swapRiS_+12>:	mov    -0x8(%rbp),%rsi
0x0000000100000c10 <_Z4swapRiS_+16>:	mov    (%rsi),%eax
0x0000000100000c12 <_Z4swapRiS_+18>:	mov    %eax,-0x14(%rbp)
0x0000000100000c15 <_Z4swapRiS_+21>:	mov    -0x10(%rbp),%rsi
0x0000000100000c19 <_Z4swapRiS_+25>:	mov    (%rsi),%eax
0x0000000100000c1b <_Z4swapRiS_+27>:	mov    -0x8(%rbp),%rsi
0x0000000100000c1f <_Z4swapRiS_+31>:	mov    %eax,(%rsi)
0x0000000100000c21 <_Z4swapRiS_+33>:	mov    -0x14(%rbp),%eax
0x0000000100000c24 <_Z4swapRiS_+36>:	mov    -0x10(%rbp),%rsi
0x0000000100000c28 <_Z4swapRiS_+40>:	mov    %eax,(%rsi)
0x0000000100000c2a <_Z4swapRiS_+42>:	pop    %rbp
0x0000000100000c2b <_Z4swapRiS_+43>:	retq 

void swap(int *pa, int *pb);函数的汇编如下:

0x0000000100000c30 <_Z4swapPiS_+0>:	push   %rbp
0x0000000100000c31 <_Z4swapPiS_+1>:	mov    %rsp,%rbp
0x0000000100000c34 <_Z4swapPiS_+4>:	mov    %rdi,-0x8(%rbp)
0x0000000100000c38 <_Z4swapPiS_+8>:	mov    %rsi,-0x10(%rbp)
0x0000000100000c3c <_Z4swapPiS_+12>:	mov    -0x8(%rbp),%rsi
0x0000000100000c40 <_Z4swapPiS_+16>:	mov    (%rsi),%eax
0x0000000100000c42 <_Z4swapPiS_+18>:	mov    %eax,-0x14(%rbp)
0x0000000100000c45 <_Z4swapPiS_+21>:	mov    -0x10(%rbp),%rsi
0x0000000100000c49 <_Z4swapPiS_+25>:	mov    (%rsi),%eax
0x0000000100000c4b <_Z4swapPiS_+27>:	mov    -0x8(%rbp),%rsi
0x0000000100000c4f <_Z4swapPiS_+31>:	mov    %eax,(%rsi)
0x0000000100000c51 <_Z4swapPiS_+33>:	mov    -0x14(%rbp),%eax
0x0000000100000c54 <_Z4swapPiS_+36>:	mov    -0x10(%rbp),%rsi
0x0000000100000c58 <_Z4swapPiS_+40>:	mov    %eax,(%rsi)
0x0000000100000c5a <_Z4swapPiS_+42>:	pop    %rbp
0x0000000100000c5b <_Z4swapPiS_+43>:	retq  

可以看出,两段汇编代码完全一致。其实也可以这么理解,编译器对于引用其实就是默认看成传指针,当然这取决于编译器,不能确定的是所有使用引用和指针方式的代码的汇编代码都一致,但是至少它们最终完成的功能是一致的。


Q: 对于像上面的代码,同为swap函数,编译器最终如何将它们区分开?

A: 既然有不同,编译器自然能把它们区分开。正如上面的汇编中显示的,第一个swap函数在编译器内部的名称是_Z4swapRiS_,第二个swap函数的名称是_Z4swapPiS_.

使用nm命令查看生成的可执行文件内部的符号表(在这里工程默认生成的可执行为testForCpp):

可以看出,确实存在这两个名称。对于为什么会是这样的名字,这里只能提供一个常用的命名规则,一般会采用"返回值+(命名空间)+函数名+参数形式",具体对于不同编译器处理不尽相同。


Q: c++中的输入输出,cout, cin到底和printf、scanf函数有什么区别?

A: 从一个角度来说,cout和cin是对象;printf和scanf是函数;另外一个角度来说,cout的效率很可能比printf函数要低,因为它内部封装了许多函数,为了安全原因或者模块化的考虑,而printf相对比较直接。对于cout是否调用printf函数,应该说不能完全确定,尽管有说法是如此,也许是兼容的原因,不同平台应该有不同的考虑。

如下是cout等变量的声明:

  extern istream cin;		///< Linked to standard inputextern ostream cout;		///< Linked to standard outputextern ostream cerr;		///< Linked to standard error (unbuffered)extern ostream clog;		///< Linked to standard error (buffered)

Q: 对于虚函数可以实现动态绑定,可以认为c++是动态语言吗?

A: 按照动态语言的基本概念,标准c++算不上,它依然是要求比较严格的编译型语言。对于动态绑定,c++也仅仅用静态的方式实现了略微有些动态特性的功能。实现动态绑定,一般都有虚表的支持,编译器会根据类以及继承体系中虚函数函数的名称,组装出一个虚表,然后根据调用代码的具体含义简单地调用对应虚表位置的函数。虽然,表面看起来很单纯,实际上,编译器早可以计算出实际调用的是什么(当然除了参数为基类指针的单独函数除外,这也是为什么要有虚表的原因之一).

如果这个特性可以看成动态的话,那么c++也就是具备了一点点动态特性的语言而已。


xichen

2012-5-30 13:07:59



http://www.ppmy.cn/news/171783.html

相关文章

c库(下)----小话c语言(18)

[Mac 10.7.1 Lion Intel-based x64 gcc4.2.1 xcode4.2 ] Q&#xff1a; 如何解决abs函数传入一个整形数最小值返回溢出的数&#xff1f; #include <stdio.h> #include <stdlib.h> #include <string.h> #include <limits.h>#define PRINT_D(int…

c语言,有时莫名,有时奇妙----小话c语言(25)

作者&#xff1a;陈曦 日期&#xff1a;2012-8-17 12:53:12 环境&#xff1a;[Mac 10.7.1 Lion Intel i3 支持64位指令 gcc4.2.1 xcode4.2] 转载请注明出处 Q1&#xff1a; 为什么下面的输出不按照代码的顺序显示&#xff1f; #include <stdio.h>#include <uni…

开发实用命令和工具----小话c语言(16)

[Mac 10.7.1 Lion Intel-based x64 gcc4.2.1] Q&#xff1a; 有的时候&#xff0c;记得在某个目录下写过某个变量或者其它什么文本形式的东西&#xff0c;但是后来忘记写在哪个文件里了&#xff0c;怎么找到&#xff1f; A&#xff1a; 这个就需要用到grep命令了。它很强大…

小话层次分析法(AHP)

在目标决策领域&#xff0c;有的决策数据信息是量化的&#xff0c;如一个项目的未来收益、消耗成本等&#xff0c;通过对各种信息进行计算可以做出较好的决策&#xff1b;但有的决策数据信息并不全是数字化的&#xff0c;如项目信息为“这个收益更好”、“这个成本更高”这样的…

小话设计模式(番外一)插件模式

插件&#xff08;Plugin&#xff09;模式向用户提供了一种扩展程序的接口&#xff0c;用户可以在程序本体之外&#xff0c;按照指定接口编写插件来为程序增加功能。 可能实际开发中不太会运用到插件模式&#xff0c;但是它确实我们经常会使用到的一种模式。例如CocosBuilder和…

前言----小话c语言(1)

不知道该怎么开头&#xff0c;不过开头的几个字都写了&#xff0c;就继续写下去吧。 看过很多以大话开头的书籍&#xff0c;觉得也不怎么样&#xff0c;觉得还没达到大话的层次&#xff0c;本人本着谦虚的精神&#xff0c;暂且以小话开头吧&#xff1b;可能读者看完&#xff0c…

Git安装与仓库配置(附带)

Git的安装与仓库配置 前期准备安装与配置安装Git注册账户&#xff1a;环境配置配置用户名与邮箱&#xff1a;生成SSH添加SSH配置仓库仓库建立初始化仓库 提交文件操作&#xff1a; Git 的基本语法总结总结小话 前期准备 下载Git安装包&#xff08;根据需求不同选择安装Window/…

小故事

小故事 老和尚背姑娘 小和尚和老和尚下山化缘&#xff0c;走到河边&#xff0c;见一姑娘正发愁没法过河。老和尚对姑娘说&#xff1a;我把你背过去把&#xff0e;姑娘同意事后小和尚目瞪口呆&#xff0c;又不敢问。就这样又走了二十里路&#xff0e;实在忍不住了&#xff0c;就…