经验:GUI编程中不要在栈上分配GUI类对象

Posted by lazybugg at 2013-11-15 with tags C++, GUI, Qt, Pointer

各种GUI类库的常用对象,在构建时其内部都会维护着对象之间的父子关系,这也是它们的构造函数都要求传递一个GUI类库最顶层的类的指针的原因。每个对象维护着它的子对象列表,当这个对象析构时,会遍历子对象列表, 删除子对象。这样可以确保父子对象的生命周期同时结束。程序员不需要关心子对象什么时候删除。

但是不要把在栈上建立的对象的地址传递作为parent,传递给构造函数,这样容易出错,析构时会析构再次,而析构函数只能调用一次,因此程序就会异常退出。

刚才我就遇到了这个问题,纠结了两个多小时。我原本需要的是一个pointer to array of 4 pointers of class QTableView 的指针数组,但是由于没有正确的定义这个数组,最后变成了pointer to array of 4 class QTableView. 数组的内容是对象而不是对象指针。接着在建立其他对象时就直接传了这个数组里的对象的地址(用&取地址,现在想想这是很不应该的,到这一步就应该Google 一下正确的 poniter to array of 4 poiners 怎样写了。

Top

C++ typecasting: dynamic_cast, reinterprete_cast, static_cast, const_cast, typeid

Posted by lazybugg at 2013-11-13 with tags C++, typecast, dynamic_cast, reinterprete_cast, static_cast, const_cast, typeid

C++ 类型转换方法总结


本文是cplusplus.com上文章的笔记,原文: http://www.cplusplus.com/doc/tutorial/typecasting/


传统类型转换

隐式类型转换(implicit conversion)

隐式类型转换自动进行,不需要程序员关心(确切的说是不需要程序员写代码,不关心可是不行的:-)。例如:

short a = 2000;
int b = a;

上例中,a的值自动从short提升为int,然后赋给b。这种转换常见于数值类型,可以从低精度到高精度,自动进行转换。

隐式类型转换也包括类的单参数构造函数和运行符重载的转换。例如:

class A{};
class B{ public: B(A a){} };
A a;
B = a;

在上例中,类B含有参数类型为A的构造函数,所以从AB的隐式类型转换是合法的,这通常也是需要特别注意的:)


显式类型转换

直接看例子:

short a = 2000;
int b;
b = (int) a;	// C 风格
b = int (a);	//函数调用风格

上面这种类型转换对基础数据类型已经足够,但是这些转换也可以不加区分的应用于类和指向类对象的指针。这就容易写出语法正确,运行时出错的代码:

class A{ ... };
class B{ ... };

int main(){
	A a;
	B *b = (A*)&a; //运行时错误
}

C++新引入的类型转换

dynamic_cast:派生类 –> 基类

dynamic_cast 只能用于指针或者引用类型, 它确保转换的结果是有效完整的要转换成的类的对象。

使用情景:把一个类对象转换为它的基类对象。Derived –> Base

class Base {...};
class Derived:public Base{ ... };
Base b, *pb;
Derived d, *pd;
pb = dynamic_cast<Base *>(&d);		//派生类 --> 基类,没有问题
pd = dynamic_cast<Derived *>(&b);	//基类 --> 派生类,错误

第二个转换是错误的,基类–>派生类的转换只有在基类是多态的情况下才有效。


当一个类是多态类时,dynamic_cast进行运行时检查,确保转换出来的对象是有效完整的要转换成的类的对象(valid complete object of the requested class)。

#include <iostream>
#include <exception>

class Base {public: virtual void dummy() {} };
class Derived: public Base { int a; };

int main(){
	try{
		Base *pba = new Derived;
		Base *pbb = new Base;
		Base b;
		Base &rb = b;

		Derived *pd = dynamic_cast<Derived *>(pba);
		pd == nullptr && std::cout << "pd is nul" << std::endl;
		Derived *pd2 = dynamic_cast<Derived *>(pbb);
		pd2 == nullptr && std::cout << "pd2 is nul" << std::endl;
		void *pv = dynamic_cast<void *>(pba);
		pv == nullptr && std::cout << "pv is nul" << std::endl;
		Derived &rd = dynamic_cast<Derived &>(rb);
	} catch (std::exception &e) {
		std::cout << "Exception: " << e.what() << std::endl; }
	return 0;
}

运行结果:

pd2 is nul
Exception: std::bad_cast

上例中第二个转换不能产生完整的对象,因此dynamic_cast返回nullptr表示转换失败。如果dynamic_cast用于转换引用类型,转换失败时会抛出bad_cast类型的异常。

dynamic_cast也可用于把任意类型的指针转为空指针(void *)。


static_cast:派生类–>基类、基类–>派生类、C风格转换

可进行派生类 <–> 基类的互相转换,它不像dynamic_cast那样在运行时检查转换结果的有效性和完整性。靠程序员来确保转换的安全性。

class Base {};
class Derived: public Base {};
Base *b = new Base;
Derived *d = static_cast(<Derived *>(a);

上例的转换是有效的,虽然这样b会指向一个不完整的类对象并且解引用(deferenced)时会导致运行时错误。

static_cast也可用于任何可以隐式进行的非指针的转换,例如:

double d = 3.141592653;
int i = static_cast<int>(d);

reinterpret_cast:任意类型 –> 任意类型

reinterpret_cast把任意类型转为其他任意类型,即使是不相关的类型。这个操作的结果只是简单的把值从一个指针复制到另一个。任何指针转换都可以:既不检查指向的内容,也不检查指针自身的类型。

从名字就可以看出来这一点:reinterprete,重新解释内存中的数据,也就不管这些数据原来是什么类型了,不要忘了程序数据(类对象,基础类型)只不过是内存中的二进制流。4字节的int类型不过是连续的32个位(bit),我完全可以把它当作UTF-32编码的一个Unicode文字,虽然这个文字与前后的内容放在一起时,很可能没有意义,所以我们通常不会这样做,但我完全可以这样做,贵在可以reinterprete就是用来干这个事的。

指针 <–> 整型:整型的表示方式与具体平台相关。唯一能够保证的是:把指针转换为一个能容纳它的整型,这个整型就可以再转成一个有效的指针。

能够使用reinterpret_cast而不能使用static_cast进行的转换是低层的操作,它的解释通常与与系统相关,是不可移植的。


const_cast:去掉const属性

原文说的是const_cast操作对象的constness,或者加上,或者去除。但是我不知道怎样能加上,非const的变量可以作为参数传递给需要const参数的函数,而使用const_cast<const char *>(s)或者const_cast<char *>(s)试图把非consts加上const属性时,g++没有报错。但是转换与不转换,g++给出的警告是一样的:

warning: deprecated conversion from string constant to ‘char*’ [-Wwrite-strings]

所以,const_cast能否用于“加上”const属性就存在疑问了。

例如:为了把一个const参数传给一个需要非const参数的函数,需要去掉(cast away)const

#include <iostream>
using namespace std;

void print(const char * str){
	cout << str << endl;
}
int main(){
	const char * s = "sample";
	print(const_cast<char *>(s));
	return 0;
}

typeid():取得表达式的类型

这个操作符返回一个type_info类型的常量对象的引用。返回值可以使用==!=与其他值进行比较,也可以使用name()成员得到一个以null结尾的字符串代表数据类型或者类名。

#include <iostream>
#include <typeinfo>
using namespace std;

class Foo {public: virtual void f() {} };
class Bar: public Foo {};

int main(){
	int *a, b;
	a = 0; b = 0;
	if(typeid(a) != typeid(b)){
		cout << "a and b are different types" << endl;
		cout << "a is: " << typeid(a).name() << endl;
		cout << "b is: " << typeid(b).name() << endl;
	}
	cout << typeid(int).name() << endl;
	cout << typeid(double).name() << endl;
	Foo f;
	cout << typeid(f).name() << endl;
	cout << typeid(Foo).name() << endl;

	Foo *pf = new Bar;
	cout << typeid(pf).name() << endl;
	cout << typeid(*pf).name() << endl;

	cout << typeid(cout).name() << endl;
	cout << typeid(cin).name() << endl;
	cout << typeid(cerr).name() << endl;
	return 0;
}

运行结果(Ubuntu 12.04.3, g++ 4.6.4)

a and b are different types
a is: Pi
b is: i
i
d
3Foo
3Foo
P3Foo
3Bar
So
Si
So

注:name()返回的字符串与编译器实现有关,可以是任何字符串。

如果typeid应用于多态类,返回的结果是“the type of the most derived complete object”(不知道怎么翻,也不懂意思)。

如果typeid的参数是解引用的指针(*p),并且指针为nullptr,将抛出bad_typeid异常。在我的试验里,没有抛出异常,与指针不为空时行为一致。

Top

Add metadata(tag) for mp4 video file using avconv(aka. ffmpeg)为mp4文件添加标签

Posted by lazybugg at 2013-11-09 with tags software, metadata, ffmpeg, avconv, video, tag, mp4, python

为mp4视频文件添加标签等元信息(metadata)

我有一些mp4格式的歌曲,需要在播放歌曲的时候让OSDLyrics自动搜索显示对应的歌词。OSDLyrics和大部分视频/音频播放器是通过tag/metadata得到歌曲信息的(歌名、歌手、专辑等)。而绝大部分mp4视频文件是不带这些数据的。网上搜了一下,没找到好用的GUI软件,只好把寄希望于强大的FFmpeg上了。FFmpg(在Ubuntu里现在改名叫avconv了)可以在各种视频音频文件之间进行转换,同样也可以添加tag/metadata信息。

例如:

avconv -i input.mp4 \
-metadata title="圣哉三一歌" \
-metadata artist="新编赞美诗" \
-metadata album="新编赞美诗" \
-metadata track=1 \
-metadata comment="上海市基督教怀恩堂瓦器制作" \
-vcodec copy -acodec copy \
output.mp4

这样就为视频文件添加了tag信息,可喜的是,这样做的效率是非常高的,我写了个Python脚本处理的十几首mp4格式的歌曲,只用了几秒钟。之所以这样高效,原因就在我们选项的视频和音频编码器 copy,即只从输入流复制到输出流,不进行重新编码,这自然就快了。

不同文件格式支持的metadata也不尽相同,如果知道某种文件格式的-metadata选项后面用哪些tag呢,下面的网址有一份列表,但也有完全正确,如上面我使用的artist在列表上mp4 文件那里就没有,这是我自己猜出来的(列表中的author不正确,产生的文件vlc和mocp都无法读到artist的值。

FFmpeg Metadata

另外,从列表上可以看到, ffmpeg还支持mkv, mp3, avi, flv, mpg, wmv, wma 等常见格式。

脚本自动化

我要转换的歌曲文件名有规律的,适合使用脚本处理。要处理的文件名如下:

新编赞美诗_001_圣哉三一歌_怀恩堂.mp4
新编赞美诗_307_欢乐颂扬歌_怀恩堂.mp4
新编赞美诗_362_收成归天家歌_怀恩堂.mp4
新编赞美诗_379_诗篇二十三篇_怀恩堂.mp4
新编赞美诗_382_诗篇一二一篇_怀恩堂.mp4
新编赞美诗_383_詩篇一三三篇.mp4
新编赞美诗_383_诗篇一三三篇_怀恩堂.mp4
新编赞美诗_384_詩篇一五零篇.mp4
新编赞美诗_384_诗篇一五〇篇_怀恩堂.mp4
新编赞美诗_短歌01_神爱世人_怀恩堂.mp4
新编赞美诗_短歌41_旧约目录歌_怀恩堂.MP4
新编赞美诗_短歌42_新约目录歌_怀恩堂.mp4

使用的Python脚本:

# coding: utf8

import re
from os import listdir
from os.path import isfile, join
from subprocess import call

#regexp = re.compile(r'.*?_(.*?)_(.*?)(_|\.)(.*)')
regexp = re.compile(r'.*?(\d+)_(.*?)(_|\.)(.*)')

mypath = '/home/starfish/video/hymn-video'

filelist = [ f for f in listdir(mypath) if f.endswith(".mp4") and isfile(join(mypath, f)) ]

cmd = """avconv -i '%s' \
-metadata title='%s' \
-metadata artist="新编赞美诗" \
-metadata album="新编赞美诗" \
-metadata track=%s \
-metadata comment="上海市基督教怀恩堂瓦器制作" \
-vcodec copy -acodec copy \
'%s'
"""
for f in filelist:
	print f
	newfile = f.rsplit('.')[0] + "_metadata.mp4"
	print newfile
	result = regexp.findall(f);
	if result:
		for idx, s in enumerate(result[0]):
			print idx, ":", s
		fullcmd =  cmd % ( f, result[0][1], result[0][0], newfile)
		call(fullcmd, shell=True)

如果你的文件名没有这么有规律,恐怕就要手工修改了。。。不过还是可以写个脚本提示输入各种参数,或者先编辑好所有命令,再一次运行。这总比每次在命令行上翻出历史–>左右移动光标编辑–>运行快的多了。

Top

RTFM: Read The Fucking Manual

Posted by lazybugg at 2013-11-04 with tags Glossary, RTFM, 知乎

RTFM 去读手册(帮助文档)


RTFM

Read The Fucking Manual 或者 Read The Fucking Man page 等类似短语的缩写。与GIYF(Google is your friend),LMGTFY(let me google for you)意思相似。

知乎上就有不少这样的问题。Google一下就能很容易的找到答案的问题,却拿到知乎上提问,回答者从Google上找到答案,搬运到知乎骗赞同。有时回答还不如Google来的答案。比如,有些问题Wikipedia上有详细的资料,提问者在提问前都没有做一点调查研究就把问题抛出来,我一直疑惑这是出于什么心态或者有什么原因。知乎相比stackoverflow等其会员制度还很不完美,比如讨巧的回答经常能得到最多的赞同,这不是一个好现象。

Top

sort Chinese characters with C++

Posted by lazybugg at 2013-10-25 with tags C++, Chinese, CJK, pinyin, sort, locale, collate

C++按照拼音对中文排序


方法来自《C++ cookbook》,书中以德语为例,思想是设置程序使用的locale为相应语言,按照语言的locale进行排序。

我的工作是解决了在Ubuntu中locale名称不对的问题。还有调用sort函数前不用再改变全局的locale,这里没有处理locale名称找不到时抛出的runtime_error

locale名称的查找方法:

  • man 7 locale,我是从这里找到的。cookbook上用的是locale("french"), locale("english-american"), 作者用的编译器是VC++7.1。我试过locale("chinese"), locale("chinese-china")都不行。
  • libstdc++ 文档

C++标准只规定了一个locale:C。其他locale与实现有关,名称也不尽相同。

#include <iostream>
#include <string>
#include <locale>
#include <vector>
#include <algorithm>

using namespace std;

// Linux g++ locale 名称: "zh_CN.utf"
// VC2010 locale 名称:	"Chinese"或者"Chinese_china"
#ifdef _MSC_VER
static const char *ZH_CN_LOCALE_STRING = "Chinese_china";
#else
static const char *ZH_CN_LOCALE_STRING = "zh_CN.utf8";
#endif

static const locale zh_CN_locale = locale(ZH_CN_LOCALE_STRING);
static const collate<char>& zh_CN_collate = use_facet<collate<char> >(zh_CN_locale);

bool zh_CN_less_than(const string &s1, const string &s2){
	const char *pb1 = s1.data();
	const char *pb2 = s2.data();
	return (zh_CN_collate.compare(pb1, pb1+s1.size(), pb2, pb2+s2.size()) < 0);
}

int main(void){
	vector<string> v;
	v.push_back("啊");
	v.push_back("阿");
	v.push_back("第一");
	v.push_back("第二");
	v.push_back("第贰");
	v.push_back("di");
	v.push_back("第三");
	v.push_back("liu");
	v.push_back("第叁");
	v.push_back("第四");
	v.push_back("abc");
	v.push_back("aa");

	cout << "locale name: " << zh_CN_locale.name()<< endl;
	sort(v.begin(), v.end(), zh_CN_less_than);
	for(vector<string>::const_iterator p = v.begin(); p != v.end(); ++p){
		cout << *p << endl;
	}

	return EXIT_SUCCESS;
}

在Linux 上使用 g++ 编译,我的环境:

系统:Ubuntu 12.04.3 64bit %
g++: 4.6.3
LANG环境变量:zh_CN.UTF8

运行结果:

➜  i18n  g++ sort_chinese_characters.cpp        
➜  i18n  ./a.out 
locale name: zh_CN.utf8
aa
abc
di
liu
阿
啊
第二
第贰
第三
第叁
第四
第一
➜  i18n  

在英文Windows 7 环境下的 Visual Studio 2010上运行结果:

locale name: Chinese (Simplified)_People's Republic of China.936
aa
abc
di
liu
阿
啊
第二
第贰
第三
第叁
第四
第一
Press any key to continue . . .
Top

Java Nested Classes(static and non-static), local class, and anonymous class Reference

Posted by lazybugg at 2013-09-29 with tags Java, OOP, class, nested class

Java嵌套类(静态和非静态)、本地类、匿名类


Java doc has a very good documents about this. I think Java docs is the best place to learn java, way better than some textbooks. So I only place a link to there to let you know this good resource.

http://docs.oracle.com/javase/tutorial/java/javaOO/nested.html

summary

Nested classes are divided into two categories:static and non-static, can be decalared private, public, protected, or package private:

static nested classes

Nested classes that are declared static. Like static method, a static nested class cannot refer directly to instance variables or methods defined in its enclosing class. It can use them only through an object reference.

In effect, a static nested class is behaviorally a top-level class that has been nested in another top-level class for packaging convenience.

inner classes

Non-static nested classes. As with instance methods and variables, an inner class is associated with an instance of its enclosing class and has direct access to that object’s methods and fields. Also, because an inner class is associated with an instance, it cannot define any static members itself.

To instantiate an inner class, you must first instantiate the outer class. Then, create the inner object within the outer object with this syntax:

OuterClass.InnerClass innerObject = outerObject.new InnerClass();

There are two special kinds of inner classes: local classes and anonymous classes.

Local classes

Local classes are classes that are defined in a block, which is a group of zero or more statements between balanced braces. You typically find local classes defined in the body of a method. ( see original document for an example)

  • A local class has access to the members of its enclosing class
  • A local class has access to local variable that are declared``fina`.
  • Starting in Java SE 8, a local class can access local variables and parameters of the enclosing block that are final or effectively final. A variable or parameter whose value is never changed after it is initialized is effectively final.
  • Starting in Java SE 8, if you declare the local class in a method, it can access the method’s parameters
  • You cannot declare static initializers or member interfaces in a local class.(in my comprehention, you cannot declare static method in local class)

Anonymous classes

Anonymous classes enable you to make your code more concise. They enable you to declare and instantiate a class at the same time. They are like local classes except that they do not have a name. Use them if you need to use a local class only once.

Syntax of Anonymous Classes

While local classes are class declarations, anonymous classes are expressions. Consider the instantiation of the frenchGreeting object:

HelloWorld frenchGreeting = new HelloWorld() {
	String name = "tout le monde";
	public void greet() {
		greetSomeone("tout le monde");
	}
	public void greetSomeone(String someone) {
		name = someone;
		System.out.println("Salut " + name);
	}
};

The anonymous class expression consists of the following:

  • The new operator
  • The name of an interface to implement or a class to extend. In this example, the anonymous class is implementing the interface HelloWorld.
  • Parentheses that contain the arguments to a constructor, just like a normal class instance creation expression. Note: When you implement an interface, there is no constructor, so you use an empty pair of parentheses, as in this example.
  • A body, which is a class declaration body. More specifically, in the body, method declarations are allowed but statements are not.

Anonymous classes have the same access to local variables of the enclosing scope as local classes.

Anonymous classes are often used in graphical user interface (GUI) applications. For example:

btn.setOnAction(new EventHandler<ActionEvent>() {
 
	@Override
	public void handle(ActionEvent event) {
		System.out.println("Hello World!");
	}
});
Top