Archive for ‘之语言特性’ Category

August 17, 2010

  const在C,尤其是C++,是个老生常谈的问题,但这里不谈const具体有哪些特性,如何使用,而是说说const在C和C++中的区别。编译器,C使用gcc,C++使用g++,其它编译器(cl等)请自行验证。
  在我的印象中,const就是常量constant)。但这并不是真的。在C中,const仅仅表示其所修饰对象不可修改。常量和所谓“不可修改”有什么区别呢?C中,const int N = 10; 这样的语句声明了一个整型数据,声明之后你不能再为其赋值,此即为不可修改。但在C中,N却不是常量,而诸如372, 3.72, ‘A’之类才是常量,在C中定义常量通常使用#define。而在C++中,const int N = 10; 就会定义N为常量。
  怎么证明呢?你可能知道,定义静态数组,必须使用整型常量指定其大小,那咱们就用这个特性来验证上面描述的观点。

1
2
3
4
5
6
7
int
main(int argc, char **argv)
{
    const int N = 10;
    int a[N] = { 1, 2, 3 };
    return 0;
}
Tags: . 44 views
August 10, 2010

volatile

  可能很多人都没用过C/C++中的这个关键词,甚至不知道它的存在,本人以前也只是有所耳闻,但似懂非懂。
  这是一个类型修饰符,位置同conststatic等。一个使用volatile修饰的变量,比如volatile int i; 每次对该变量的直接引用,都会访问内存,而不是从寄存器中读取(如果其已经在寄存器中)。这样一来,volatile似乎没什么用处,反倒会使数据的读取相对变慢很多。但是,如果没有volatile,编译器可能会优化你的程序,使得数据从寄存器中读取,从而加快程序的运行,但如果这个变量是同其它进程/线程共享的,就可能造成数据的不一致。多线程情况下,你可以使用互斥机制来保证对共享数据访问的原子性。但是,在单片机等嵌入式环境中,硬件经常不会有这种互斥机制的支持,这时某些共享的数据(比如端口)就可能会产生不一致的情况。而使用volatile就会使编译器不对代码进行优化,每次对该变量的访问都会从内存中读取。
  下面通过观察使用volatile前后编译产生的汇编代码的不同,来加深对volatile关键词的理解。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[ ... ]
main:
[ ... ]
	call	foo # 调用函数foo,返回值保存至寄存器eax
	movl	%eax, 44(%esp) # 为i赋值
	movl	44(%esp), %edx # 读取i值
	movl	44(%esp), %ecx # 读取i值
	movl	44(%esp), %eax # 读取i值
	movl	%ecx, 16(%esp) # 参数k入栈,使用i值
	movl	%edx, 12(%esp) # 参数j入栈,使用i值
	movl	%eax, 8(%esp) # 参数i入栈
	movl	$.LC0, 4(%esp) # 格式字符串地址入栈
	call	__printf_chk
	movl	$0, %eax
	leave
	ret
[ ... ]
Tags: ,. 73 views
July 6, 2010

  对qsort需要注意几点:

  • qsort中第一个参数是待排序数组的开始地址,既然是数组,各元素就是同类型、同大小的对象,且数组是“一维数组”(即地址是连续的);
  • qsort用以区分对象的依据是第二和第三个参数,分别表示对象个数和每个对象的大小(字节);
  • qsort并不知道每个对象的类型和结构,排序准则由用户在第四个参数(比较函数)中指出,qsort按该比较函数准则的“升序”对数组进行排序;
  • 标准C不支持运算符重载,各对象的交换(因为这是qsort)靠的是逐字节的拷贝(memcpy?)。

  在上面的两片代码中,待排序的对象一个是字符型指针,一个是char (*)[10]型数组。然后,就没有然后了。

Tags: ,,. 52 views
May 21, 2010

  在形如struct structName varName;的语句中,struct是必须的吗?这是一个显而易见的语法问题,但却容易被忽略,尤其容易被C+C++的同志们忽略。
  在标准C中,struct关键字是必须的:

1
2
3
4
5
6
7
8
9
10
struct structName {
    int n;
};
//~ typedef struct structName structName;
int
main()
{
    struct structName n;
    return 0;
}

若没有前置的struct关键字,上面的代码就不能通过编译。我的gcc会提示”structName” undeclared, parse error before ‘n’的错误。为了方便,通常会使用typedef struct structName structName;语句来为struct structName定义类型别名。
  但是,在C++中,一般情况下,struct/class关键字就不是必须的。但是,在有些情况下struct/class关键字又是必须的,因为这时的名字structName有歧义性。这是因为,C/C++语言允许用户自定义类型和函数同名。

Tags: ,. 38 views
April 29, 2010

  提到C++ STL,首先被人想到的是它的三大组件:Containers, Iterators, Algorithms,即容器,迭代器和算法。容器为用户提供了常用的数据结构,算法大多是独立于容器的常用的基本算法,迭代器是由容器提供的一种接口,算法通过迭代器来操控容器。接下来要介绍的是另外的一种组件,函数对象(Function Object,JJHou译作Functor仿函数)。

什么是函数对象

  顾名思义,函数对象首先是一个对象,即某个类的实例。其次,函数对象的行为和函数一致,即是说可以像调用函数一样来使用函数对象,如参数传递、返回值等。这种行为是通过重载类的()操作符来实现的,举例说明之,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Print
{
public:
    void operator()(int n)
    {
        std::cout<<n<<std::endl;
        return ;
    }
};
int
main(int argc, char **argv)
{
    Print print;
    print(372);
    print.operator()(372); //~ 显式调用
    return 0;
}
Tags: ,. 193 views
April 26, 2010

进程之死

  对于一个用C++写的程序,被加载至内存后运行,最终走向死亡。程序的死亡大致有三种:

  • 自然死亡,即无疾而终,通常就是main()中的一个return 0;
  • 自杀,当程序发现自己再活下去已经没有任何意义时,通常会选择自杀。当然,这种自杀也是一种请求式的自杀,即请求OS将自己毙掉。有两种方式:void exit(int status)和void abort(void)。
  • 他杀,同现实不同的是,程序家族中的他杀行径往往是由自己至亲完成的,通常这个至亲就是他的生身父亲(还是母亲?)。C++并没有提供他杀的凶器,这些凶器往往是由OS直接或者间接(通过一些进程库,如pthread)提供的。

  自然死是最完美的结局,他杀是我们最不愿意看到的,自杀虽是迫不得已,但主动权毕竟还是由程序自己掌控的。下面探究程序一下不同的死亡方式对对象的析构有何影响。

Tags: ,. 144 views
April 20, 2010

  在现行的C++标准中有这么一个名字,std,它是一个namespace(命名空间)。C++标准库的所有类型/对象/函数/模板都定义在这个命名空间中。关于”What namespace? Why namespace? How namespace?” 的问题,任何一本基础的C++读物中都会有介绍,此处略过。为了使用std内定义的对象,可供使用的语法无非是

1
2
3
4
5
6
7
std::vector<int> ivec;
//~ or
using std::vector;
vector<int> ivec;
//~ or
using namespace std;
vector<int> ivec;

  现在有这样一种需要,我想为std内的某种类型(比如string)重载操作符,而不想使用std内为我们提供的版本。

Tags: . 25 views
April 18, 2010

  今天在VS中遇到一个十分诡异的事情,先看下面这个简单的程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class cmp
{
public:
	bool operator()(long long l, long long r)
	{
		//~ 此间内容可略去不看。
		//~ 如果l和r含有的digits完全相同则返回false,
		//~ 如125 vs. 512
		char tmp1[1024], tmp2[1024];
		sprintf(tmp1,"%ld",l);
		sprintf(tmp2,"%ld",r);
		sort(tmp1, tmp1+strlen(tmp1));
		sort(tmp2, tmp2+strlen(tmp2));
		if (!strcmp(tmp1, tmp2))
		{
			return false;
		}
		return true;
	}
};
Tags: . 20 views
March 27, 2010

  好久没写C++没用STL了,今天在STLChina泡了一下午。完整的代码只写了这么一个,贴上来吧。
  写了两个类,Max_Heap和Min_Heap,以他们特例化priority_queue,可以方便地实现最大、最小堆。写完了忽然又觉得这俩类似乎是多此一举了,完全可以用greater来实现最小堆,但由priority_queue的模板定义看出,那样就必须提供一个容器类型,比如vector
  最后还写了测试用例:找出n个数中最大的k个。

1
2
3
4
5
6
7
8
9
10
11
/*
template < class T, class Container = vector<T>,
           class Compare = less<typename Container::value_type> > class priority_queue;
 
explicit priority_queue ( const Compare& x = Compare(),
                          const Container& y = Container() );
template <class InputIterator>
         priority_queue ( InputIterator first, InputIterator last,
                          const Compare& x = Compare(),
                          const Container& y = Container() );
*/
Tags: ,,. 59 views
March 14, 2010

  所谓大小端模式,就是一个关于数据字节在存储顺序的问题。在某些编程环境中,了解大小端是非常重要的,比如汇编和网络编程中,对存取和发送、接收数据的字节序都有严格的要求。当然,在高层次,你很少会需要考虑到这些。为什么会有大小端模式之分呢?这是因为在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节。在许多计算机语言中,许多数据类型都是多字节的,这就产生了多字节数据在内存中存放数据的问题。在大端模式(Big-endian)中,数据的低位(就是权值较小的后面那几位)保存在内存的高地址中,而数据的高位,保存在内存的低地址中;在小端模式(Little-endian)中,数据低位就存放在内存的低位。了解了什么是大小端,看下面的程序,输出结果是什么?

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
int
main()
{
    int a[] = {1, 2, 3, 4, 5};
    int *ptr1 = (int*) (&a + 1) - 1;
    int *ptr2 = (int*) ((int)a + 1);
    printf("%x, %x\n", *ptr1, *ptr2);
    return 0;
}
Tags: . 19 views
Page 1 of 812345678