2007年10月19日星期五

关于C++类的拷贝构造函数


#include <iostream>

using namespace std;

class A
{
public:
A() {cout << "A()" << endl;}
A(const A& a){ cout << "A(const A& a)" << endl;}
void print() const {cout << "print in A" << endl;}
};

class B : public A
{
public:
B() {cout << "B()" << endl;}
B(const B& a){ cout << "B(const A& a)" << endl;}
void print() const {cout << "print in B" << endl;}
};

void printByValue(A a)
{
a.print();
}
void printByRef(const A& a)
{
a.print();
}
int main(int argc, char* argv[])
{
A a;
printByValue(a);
printByRef(a);
B b;
printByValue(b);
printByRef(b);
a = b;
printByValue(a);
printByRef(a);

return 0;
}

结果为:
A()
A(const A& a)
print in A
print in A
A()
B()
A(const A& a)
print in A
print in A
A(const A& a)
print in A
print in A

注:函数参数直接传值和“=”赋值均会调用拷贝构造函数。

2007年8月10日星期五

关于自定义类型名与变量名

今天看linux的源代码,居然发现一个地方自定义的类型名与变量名重名!试了一下,果然如此:
#include <iostream.h>

struct S
{
int i;
};
typedef int I;
class C
{
int j;
};

void main()
{
S S;
S.i = 4;

I I;
I = 4;
cout << I << endl;

C C;
}

以上程序是正确的,能通过编译。
另注:
若如此使用了,接下来的程序若再次用,比如说,类C定义变量,则必须像这样用:
class C cx;
而用
C cx;
则会报错!
(如果此时是typedef,而不是class或是struct,该怎么使用呢?)

2007年4月1日星期日

关于变量名空间的一小点

在C/C++中,变量名与(类名、结构名、typedef定义的类型名)属于不同的名空间。因此它们是可以相同的。如:
struct S { ... };
class C { ... };
typedef int INT;

S S; // right
C C; // right
INT INT = 3; // right

2007年3月21日星期三

重拾java(3):继承

❁java不支持多继承

❁子类不能访问父类中声明为private的类成员

❁java与C++中均一样,父类引用不能访问仅在子类中定义的函数或变量:
public class t2 {
public static void main(String args[]) {
F p = new S();
int i = p.s; // wrong
}
}

class F {
int f;
}

class S extends F {
int s;
}
❁super()必须永远是一个子类构造函数内执行的第一条语句。如下面的程序将会报错:
class S extends F {
int s;
S() {
int u;
super(); // Wrong! Constructor
call must be the first
statement in a constructor
int k;
}
}

因为构造函数按派生顺序,从超类到子类被调用。

❁如下程序例1。父类中的变量虽被覆盖,但p仍访问的是父类变量。而父类中的方法却被覆盖,因此p.pr()访问的是子类函数,类似于C++中的虚函数。这一点非常奇怪。因为在C++中,不管是变量还是函数,它们都是统一的,要覆盖就都会被覆盖,像这种动态的函数调用必须通过virtual来声明。另:C++中类中变量不能为virtual,只能是函数才可以用virtual修饰。这也许是java中要如此分开对待的原因吧。
public class t2 {
public static void main(String args[]) {
F p = new S();
System.out.println(p.i); // 1 父亲中的变量
p.pr(); // In S... 儿子的方法
}
}

class F {
int f;
int i;
F() {
;
}
void pr()
{ System.out.println("In F..."); }
}

class S extends F {
int s;
int i;
S() {
super.i = 1;
i = 2;
}
void pr()
{ System.out.println("In S..."); }
}

例2:
public class t2 {
public static void main(String args[]) {
F p1 = new S();
System.out.println(p1.i); // 1

S p2 = new S();
System.out.println(p2.i); // 2
}
}
class F {
int f;
int i;
F() {
;
}
}
class S extends F {
int s;
int i;
S() {
super.i = 1;
i = 2;
}
}
❁抽象abstract:
子类必须实现父类中所有的抽象函数。
包含一个或多个抽象方法的任何类也必须被声明为抽象的。
不能声明抽象构造函数或抽象静态方法。
抽象类虽不可被实例化,但可以被用来创建对象引用。
同C++中virtual类似,abstract不能用来修饰类中变量,而只能是函数。

重拾java(2)

1、类型
(1) 自动类型提升
byte a = 40;
byte b = 50;
byte c = 100;
int d = a * b / c; // a、b、c在计算中,
其类型自动提升为int型
System.out.println(d);

byte e = 5;
e = e * e; // wrong. Type mismatch: cannot
convert from int to byte
byte a = 64, b;
b = (byte)(a << 2);

一个浮点文字自动为double,如:
float ff = 3.55; //wrong
float f = 5 / 3 + 7.88; //wrong! Type mismatch:
cannot convert from
double to float

应该为:
float ff = 3.55F;
float f = 5 / 3 + 7.88f; //right!

2、数组
(1) 数组是作为对象实现的,因此有类内变量length:
int m[][] = new int[4][];
m[0] = new int[5];
m[1] = new int[8];
System.out.println("m[][] = " + m.length); // 4
System.out.println("m[0] = " + m[0].length); // 5
//System.out.println("m[1][] = " + m[1][].length);
// wrong
//System.out.println("m[1][3] = " + m[1][3].length);
// wrong
//System.out.println("m[3] = " + m[3].length);
// wrong

(2) 数组越界:运行时错误(C/C++不提供运行时边界检查)
int m[] = new int[2];
m[0] = 0;
m[1] = 1;
m[2] = 2; //wrong

(3) 同C/C++,“int n[] = { 1, 2, 3, 4, };” 是正确的(即末尾可多一个逗号)。

(4) java支持多维不等数组。如:
int twoD[][] = new int[3][];   // 仅需第一维指定内存
twoD[0] = new int[5];
twoD[1] = new int[3];
twoD[2] = new int[1];
3、运算符
(1) java中,“%”运算符可以用于浮点类型,而C/C++不能。如:
double d = 22.33 % 2.34;
(2) >> :向右移 >>> :向右移,左补零
byte类型的值最高位若为1,则>>>对其是不可能的。如:
byte b = 0xf1; 则 b>>4为0xff,b>>>4为0xff,(b & 0xff)>>4为0x0f。

(3) java中对boolean类型的值来说有运算符&(逻辑与)与&&(短路与)的区分(|与||同)。

(4)
float f = 3;
int i = 3;
if(i == f)
  System.out.println("Yes"); // This will be printed
else
  System.out.println("No");

4、程序控制语句
(1) 在程序控制语句中,除了for语句可以使用逗号作为分隔符外,其他的地方不能使用逗号。如:
int k = 0;
int i = true ? (k=3,k) : (k=4,k);
// 错误,Syntax error on token
",", invalid AssignmentOperator
这一点与C/C++不同。

(2) switch语句中,若default语句不在最后,其后也应该跟一个break语句。如:
int i = 4;
int k = 0;
switch(i)
{
  default :
    k = 4;
  case 1:
    k = 1;
    break;
  case 2:
    k = 2;
    break;
  case 3:
    k = 3;
    break;
}
System.out.println(k);

将打印出“k=1”。

(3) java中break、continue后可以跟一个标号。如:
one:for(int j=0;j<100;j++)
{
if(j==10) break one;
System.out.println(j + " ");
}
而在C/C++中不可以。

(4) 像“7 + 8;”这样的无意义的语句在java中是不允许的;而C/C++中可以。

5、函数
(1) 关于重载,java与C++中都是允许的,而在C中是不允许的。
在C++中,看下面这个例子:
#include <iostream.h>

//函数1
//void f()
//{ printf("haha1\n"); }

//函数2
void f(int i)
{ printf("haha2\n"); }

int main()
{
int g = (int)f; //(*)
((void (*)())g)();

return 0;
}
若不将函数1、2中的一个注释掉,赋值语句(*)处将会报错,因为在这里产生了二义。而只留有一个函数即没有重载,就没有事。

6、类
(1) java中默认为近似public的属性,而C++中为private。

(2) 关于类实例的声明:
java中:
X x = new X();  // X x = new X;的形式是错误的
x.i = 0;
C++中:
X x = new X(); 或 X x = new X;
x->i = 0;

X x; // 若没有构造函数或构造函数无参数,则 X x();的形式是错的的
x.i = 0;
(3) C++中类定义结尾必须有分号,而java可有可无。

(4) java中finalize方法与C++的析构函数是有区别的。protected void finalize()函数仅在垃圾收集时被调用,而当一个对象在作用域外时,她是不会被调用的。
这意味着你不知道什么时候或是否会执行finalize()。因此,你的程序必须提供其他的方法来释放被对象使用的系统资源。
这与C++是不同的。C++的析构函数在对象处于作用域外时被调用。
java中因为所有的对象是使用new动态分配的,因此不需要担心关于对象在作用域范围之外的问题。因为对象创建时所在的方法终止了,
对象将继续存在,只要在你的程序中的某处存在一个此对象的引用。当不存在它的引用时,下次垃圾收集起作用时,将重新收回这个对象。

(5) 在C++中,如下:
class X {
public:
int i;
};

int main()
{
X x();

return 0;
}
此x将被认为是函数的声明。如:
class X {
public:
int i;
};

int main()
{
X x();
x();
return 0;
}

X x()
{
cout << "haha\n":
...
}
(6) 在函数中的参数,简单类型是按值传递,而对象则按引用传递

(7) 关于类中的static
声明为static的方法有几条限制:
─它们仅可以调用其他static方法;
─它们只能访问static数据;
─它们不能以任何方式引用this或super。
关于static块,这个块仅在该类被第一次加载时执行一次。例:
public class t2 {
static int i;
public static void main(String args[]) {
System.out.println("Start in main");

Y y = new Y();

System.out.println("End in main");

y = null;
}

}

class Y {
int i;
static int a = 3;
static int b;
static {
System.out.println("Static block");
b = a * 4;
}
Y() {
System.out.println("Con...");
}
protected void finalize() {
System.out.println("decon...");
}
}
打印:
Start in main
Static block
Con...
End in main

(8) 关于类嵌套(C++中没有)
A:外壳类 B:核类
A可知B,而A外的作用域不可知B。
B可访问A的成员,包括私有成员;A不能访问B的成员。
B可分为静态的(修饰以static)和非静态的(内部类)。静态的B必须通过一个对象来访问A的成员,而不能直接引用之;而非静态类则可以
访问A的所有成员,并且可以像A的其他非静态成员那样以同样的方式直接引用它们。
另外,嵌套类可以定义在任何块内,比如函数、for循环内等等。

(9) String
String ss = "wre"+"fsd";  // 对
String ss = "wre""fsd"; // 错,虽然在
C/C++中可以这样
(10) 与C/C++不同,java的命令行参数计数不是从所运行的程序名开始的。如:
class CommandLine {
public static void main(String args[])
{
for(int i=0;i<args.length;i++)
{
System.out.println("args["+i+"]:"+args[i]);
}
}
执行 # java CommandLine this is a test 100 -1
将打印出
args[0]:this
args[1]:is
args[2]:a
args[3]:test
args[4]:100
args[5]:-1

2007年3月20日星期二

不用循环和递归,用C打印1~999

同学出了rt的这道题。没作出来。看看答案,果然厉害,让我想起linux内核源代码中的一大堆的define扩展。
答案:
#define A(x) x;x;x;x;x;x;x;x;x;x;
int main ()
{
int i = 1;
A(A(A(printf("%d", i++))));
}

2007年3月19日星期一

重拾java

上星期六去参加了IBM的研究生实习招聘的笔试。两份卷子,其中一份厚厚一叠都是java。作下来很不爽,java几乎都忘光了。所以决心重拾java。晚上在我的ubuntu上弄好了eclipse,然后看了点书,作笔记如下(大部分是java与C/C++间的比较):
1、
java和C中,变量名中可以使用$,如“int $hu;”。
2、
java是强类型的,甚至比C/C++还严格。如:
在java中,int i = 4.5; // 错误
而在C中,int i = 4.5; // 可以
3、
java不支持无符号的正整数。
java中byte(8)、short(16)、int(32)、long(64)其宽度是固定的,不随目标机器的不同而不同 。
java中char是16位(无符号数)的Unicode表示。
4、
“boolean b = true;
System.out.print("boolean true is : " + b);”
将打印出“true”而不是“1”。
5、
关于char型变量的数字表示,在java与C/C++中有一些区别:


javaC/C++
八进制\ddd\ddd
十六进制\uhhhh\xhh
6、
对于java的字符串,它们必须在同一行开始和结束,没有像在其他语言中的行连续转义(如C/C++中的“\”)序列(对于很长的字符串,要换行可以使用+号连接)。
7、
关于块作用域,在java中,你不能声明一个变量为与外部作用域的变量一样的名称。如下面的程序:
class ScopeErr {
public static void main(String arg[]) {
int bar = 1;
{ // creates a new scope
int bar = 2; // Compile-time error - bar already defined!
}
}
}
而这种情况在C/C++中是合法的。

2007年3月16日星期五

阅读《C陷阱与缺陷》:……


  • 宏中的空格

  • 在语句“#define f (x) ((x)-1)”中,若要定义类似函数f(x)的功能,则f后的空格将会引起错误的替换。正确的写法是“#define f(x) ((x)-1)”。但此空格规则不适合宏调用,如“f (3)”其值仍为2。
  • 兼容性一例

  • 为保持与老版本编译器的兼容性,函数定义如:
    void func(int arg1,int arg2)
    { ... }

    也可写成
    void func(arg1,arg2)
    int arg1, arg2;
    { ... }


    void func(arg1,arg2)
    int arg1; int arg2;
    { ... }



  • 在C中(包括gcc与MSC),如下写法是错误的:
    for(int i=0;i<n;i++) { ... } 
    而应该写成
    int i;
    for(i=0;i<n;i++) { ... }
  • 多余的逗号

  • 在C中,允许初始化列表中出现多余的逗号。例如:
    int days[] = {1,2,3,4,5,};
    但只能在 末尾 多 一 个。

2007年3月13日星期二

阅读《C陷阱与缺陷》:语法“陷阱”

函数调用“f(...)” 等价于 “(*f)(...)”;前者只是后者的简写形式。

阅读《C陷阱与缺陷》:导读、词法“陷阱”

  • 边界溢出一例
  • #define N 10

    int main()
    {
    int i;
    int a[N];

    printf("Begin...\n");

    for(i=0;i<=N;i++)
    a[i] = 0; //有可能会陷入死循环!

    printf("End...\n");

    return 0;
    }
    因为每次循环结束时a溢出,使得i复为0。

  • 词法分析中的贪心法

  • 1、a---b等价于a-- - b而不是a - --b;
    2、“y = x/*p /* p指向余数 */;”,其中“/*”为注释起始而不是除以*p。

  • 以0开头的数字为八进制表示

  • 如041=33。

  • 单引号引起的问题

  • 1、printf('\n'); //危险!打印地址为'\n'处的字符串。
    2、如下程序:
    int main()
    {
    int i = '1234'; //MSVC与gcc均编译通过,
    但若单引号内的字符大于4则会报错

    printf("%x\n", i); // 0x31323334

    return 0;
    }

    编译器将会把单引号内的字符所对应的ASCII码依次填入整型变量的4个字节中。

关于函数printf()[2]

有下面一段代码:
...
char str[20];
...
scanf("Please input a string less than 20: %s", str);
...
printf(str);
...

危险在于:若用户输入的字符串中包含格式化参数如%x、%d等等,语句printf(str)将会把邻接内存内的内容按照所给的格式化参数打印出来。更危险的是,如果用户输入的字符串中使用了%n,则会向相应的内存中写入数据。格式化参数$的使用则会便利这种危险的内存注入。
因此,正确的写法应该是
printf("%s", str);

2007年3月12日星期一

关于函数printf()

printf()函数有一个可变参数列表。在其实现里,先通过第一个参数找到可变参数列表的首地址,然后根据可变参数的类型依次定位。而所依据的类型则是在第一个参数──即格式化字符串中给出的。因此格式化字符串中必须给出与后面可变参数列表中各个参数相一致的类型,否则则会发生意想不到的结果,如下面一个例子:
int main()
{
float n1 = 11.11;
double n2 = 222.222;
long n3 = 3333;
long n4 = 4444;

printf("Right: %f,%f,%ld,%ld\n",n1,n2,n3,n4);
printf("Wrong: %ld,%ld,%ld,%ld\n",n1,n2,n3,n4);

return 0;
}

其输出结果为:
root@BlueIris:~# ./printf_type
Right: 11.110000,222.222000,3333,4444
Wrong: -536870912,1076246609,-1614907703,1080805146
%f指示应该在内存中往后读8个字节,而%ld只有4个,因此出现偏差。

参考:深入printf

关于函数strcat()

摘自glibc-2.4/string/strcat.c:
char *
strcat (dest, src)
char *dest;
const char *src;
{
char *s1 = dest;
const char *s2 = src;
reg_char c;

/* Find the end of the string. */
do
c = *s1++;
while (c != '\0');

/* Make S1 point before the next character, so we can increment
it while memory is read (wins on pipelined cpus). */
s1 -= 2;

do
{
c = *s2++;
*++s1 = c;
}
while (c != '\0');

return dest;
}
摘自MVC:
char * strcat (char * dst, char * src)
{
char * cp = dst;

while( *cp )
++cp; /* Find end of dst */
while( *cp++ = *src++ )
; /* Copy src to end of dst */
return( dst );
}
从函数strcat()的实现中我们可以看出,它并不会对其参数进行检查。因此我们写程序时应该格外小心,因为源字符串若长度不够,则会溢出,从而有可能覆盖掉邻接的内存里的内容。可以看一个例子:
int main()
{
char a[] = {'1','1','1','\0'};
char d[] = {'2','2','2','\0'};
char s[] = {'3','3','3','\0'};

printf("before strcat() a is %s\n", a);
printf("before strcat() d is %s\n", d);

strcat(d,s);

printf("after strcat() a is %s\n", a);
printf("after strcat() d is %s\n", d);
}
其输出为:
root@BlueIris:~# ./strcpy_error
before strcat() a is 111
before strcat() d is 222
after strcat() a is 33
after strcat() d is 222333
可见字符数组a已经被d溢出的部分所覆盖。原理见下图。