内容简介:SQLite,是一款轻型的数据库,是遵守ACID的关系型数据库管理系统,它包含在一个相对小的C库中。它是D.RichardHipp建立的公有领域项目。它的设计目标是嵌入式的,而且目前已经在很多嵌入式产品中使用了它,它占用资源非常的低,在嵌入式设备中,可能只需要几百K的内存就够了。它能够支持Windows/Linux/Unix等等主流的操作系统,同时能够跟很多程序语言相结合,比如 Tcl、C#、PHP、Java等,还有ODBC接口,同样比起Mysql、PostgreSQL这两款开源的世界著名数据库管理系统来讲
SQLite,是一款轻型的数据库,是遵守ACID的关系型数据库管理系统,它包含在一个相对小的C库中。它是D.RichardHipp建立的公有领域项目。它的设计目标是嵌入式的,而且目前已经在很多嵌入式产品中使用了它,它占用资源非常的低,在嵌入式设备中,可能只需要几百K的内存就够了。它能够支持Windows/Linux/Unix等等主流的操作系统,同时能够跟很多程序语言相结合,比如 Tcl、C#、 PHP 、 Java 等,还有ODBC接口,同样比起 Mysql 、PostgreSQL这两款开源的世界著名数据库管理系统来讲,它的处理速度比他们都快。( 摘自百度百科 )
鉴于 SQLite
具备如此优秀的业务处理能力,Android 平台自然使用它作为内置的数据库存储模块。存储在 SD 卡上,只是一个文件的形式,可以方便的备份、移植。有兴趣的小伙伴,可以至 SQLite 官网下载 C 源码研究研究。
另外 Android 平台提供了一整套的 SQLite 操作 API。可以方便快速的创建表、更新表、对表数据进行增删改查。
1 创建数据库
创建数据库方法,分四类。
- create/createInMemory 在内存中创建数据库,当数据库关闭时即销毁。
- openDatabase 打开数据库,可以指定打开方式。
- openOrCreateDatabase 创建并打开数据库。
- getWritableDatabase/getReadableDatabase 打开读写,或只读数据库。
上述四个方式,都可以指定 CursorFactory
、 Flags
、 DatabaseErrorHandler
(API 11)、 OpenParams
(API 27) 四个基本参数。最终都 执行到 openDatabase
方法。
SQLite 对数据库文件添加四种访问权限,做 flags 参数:
- OPEN_READWRITE :读写方式打开数据库文件,可以进行增删改查等操作。
-
OPEN_READONLY
:只读方式打开数据库文件,只能执行查询操作,插入数据会抛出异常
android.database.sqlite.SQLiteReadOnlyDatabaseException
。 - CREATE_IF_NECESSARY :打开数据库时,如没有数据库文件,便会自动创建,并允许增删改查。
- NO_LOCALIZED_COLLATORS : 使 setLocale 方法不生效。
// 执行 query 方法时,使用该对象,用于生成 cursor SQLiteDatabase.CursorFactory factory = new SQLiteDatabase.CursorFactory() { @Override public Cursor newCursor(SQLiteDatabase db, SQLiteCursorDriver masterQuery, String editTable, SQLiteQuery query) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { return new SQLiteCursor(masterQuery, editTable, query); }else return new SQLiteCursor(db,masterQuery, editTable, query); } }; // 数据库发生异常时,执行该对象。 DatabaseErrorHandler errorHandler = new DatabaseErrorHandler() { @Override public void onCorruption(SQLiteDatabase dbObj) { // 做关闭数据库的操作 dbObj.close(); } }; // 打开数据库的参数,API 27 才支持。 SQLiteDatabase.OpenParams openParams = new SQLiteDatabase.OpenParams.Builder() .setCursorFactory(factory) // 指定 CursorFactory .setErrorHandler(errorHandler)// 指定 DatabaseErrorHandler .addOpenFlags(SQLiteDatabase.CREATE_IF_NECESSARY) // 指定打开权限。 .build(); 复制代码
1.1 create
直接在内存中创建数据库,create/createInMemory 两个方法,后者在 API 27 才支持,建议使用 create。
/** * 在内存中创建 SQLite 数据库,当数据库关闭时,即销毁。 * 适合临时保存数据。 * * @param factory 可选参数,建议传入 NULL 或者构建 factory 实例。 * @return 返回数据库对象 * @throws 数据库创建失败时,抛出 SQLiteException */ @NonNull public static SQLiteDatabase create(@Nullable CursorFactory factory) { // This is a magic string with special meaning for SQLite. return openDatabase(SQLiteDatabaseConfiguration.MEMORY_DB_PATH, factory, CREATE_IF_NECESSARY); } /** * 在内存中创建 SQLite 数据库,当数据库关闭时,即销毁。 * 适合临时保存数据。 * * @param openParams 配置打开数据库的参数 * @return 返回数据库对象 * @throws 数据库创建失败时,抛出 SQLiteException */ @NonNull public static SQLiteDatabase createInMemory(@NonNull OpenParams openParams) { return openDatabase(SQLiteDatabaseConfiguration.MEMORY_DB_PATH, openParams.toBuilder().addOpenFlags(CREATE_IF_NECESSARY).build()); } 复制代码
都执行到 openDatabase 方法,Flags = CREATE_IF_NECESSARY。
1.2 openDatabase
/** * 根据 flags 打开数据库 * * @param path 创建或打开数据库的文件路径。可以使用相对地址或绝对地址。 * 相对地址存在应用缓存 database 目录,绝对地址可以存在 sd 卡目录下。 * @param factory 可选参数,建议传入 NULL 或者构建 factory 实例。 * @param flags 控制数据的方式方式, * @return 返回打开新打开的数据库 * @throws 数据库创建失败时,抛出 SQLiteException */ public static SQLiteDatabase openDatabase(@NonNull String path, @Nullable CursorFactory factory, @DatabaseOpenFlags int flags) { return openDatabase(path, factory, flags, null); } /** * 根据 flags 打开数据库 * * @param path 打开或创建数据库的文件 * @param factory 可选参数,建议传入 NULL 或者构建 factory 实例。 * @param flags 控制数据库文件的访问方式 * @param errorHandler 当 SQLite 报出数据库出错时,使用 DatabaseErrorHandler 处理错误。如关闭数据库。 * @return 返回打开新打开的数据库 * @throws 数据库创建失败时,抛出 SQLiteException */ public static SQLiteDatabase openDatabase(@NonNull String path, @Nullable CursorFactory factory, @DatabaseOpenFlags int flags, @Nullable DatabaseErrorHandler errorHandler) { SQLiteDatabase db = new SQLiteDatabase(path, flags, factory, errorHandler, -1, -1, -1, null, null); db.open(); return db; } /** * 根据指定的参数,打开数据库 * * @param path 创建或打开数据库的文件路径。 * 使用绝对路径,或者通过context#getDatabasePath(String) * @param openParams 配置打开数据库的参数 * @return 返回打开新打开的数据库 * @throws 数据库创建失败时,抛出 SQLiteException */ public static SQLiteDatabase openDatabase(@NonNull File path, @NonNull OpenParams openParams) { return openDatabase(path.getPath(), openParams); } private static SQLiteDatabase openDatabase(@NonNull String path, @NonNull OpenParams openParams) { Preconditions.checkArgument(openParams != null, "OpenParams cannot be null"); SQLiteDatabase db = new SQLiteDatabase(path, openParams.mOpenFlags, openParams.mCursorFactory, openParams.mErrorHandler, openParams.mLookasideSlotSize, openParams.mLookasideSlotCount, openParams.mIdleConnectionTimeout, openParams.mJournalMode, openParams.mSyncMode); db.open(); return db; } 复制代码
四个方法中,可以分为两类。
- 前两个分别指定 Flags、CursorFactory 和 DatabaseErrorHandler(API 11)支持。
- 后两个统一由 OpenParams 指定打开数据库的参数。包含但不限于上面三个属性。
1.3 openOrCreateDatabase
/** * 相当于 openDatabase(file.getPath(), factory, CREATE_IF_NECESSARY). */ public static SQLiteDatabase openOrCreateDatabase(@NonNull File file, @Nullable CursorFactory factory) { return openOrCreateDatabase(file.getPath(), factory); } /** * 相当于 openDatabase(path, factory, CREATE_IF_NECESSARY). */ public static SQLiteDatabase openOrCreateDatabase(@NonNull String path, @Nullable CursorFactory factory) { return openDatabase(path, factory, CREATE_IF_NECESSARY, null); } /** * 相当于 openDatabase(path, factory, CREATE_IF_NECESSARY, errorHandler). */ public static SQLiteDatabase openOrCreateDatabase(@NonNull String path, @Nullable CursorFactory factory, @Nullable DatabaseErrorHandler errorHandler) { return openDatabase(path, factory, CREATE_IF_NECESSARY, errorHandler); } 复制代码
创建并打开数据库,三个方法最后都会执行到 SQLiteDatabase openDatabase(String ,CursorFactory,int,DatabaseErrorHandler)
方法。
1.4 SQLiteOpenHelper
SQLiteOpenHelper 是 Android 封装的最人性化的工具。方便开发者自行管理数据库表目录和结构。
/** * 管理数据库创建和版本 */ public class DBOpenHelper extends SQLiteOpenHelper { // 相当于 SQLiteDatabase openDatabase(String, CursorFactory) public DBOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) { super(context, name, factory, version); } // 相当于 SQLiteDatabase openDatabase(String, CursorFactory, DatabaseErrorHandler) @TargetApi(Build.VERSION_CODES.HONEYCOMB) public DBOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version, DatabaseErrorHandler errorHandler) { super(context, name, factory, version, errorHandler); } // 相当于 SQLiteDatabase openDatabase(String , OpenParams); @TargetApi(Build.VERSION_CODES.P) public DBOpenHelper(Context context, String name, int version, SQLiteDatabase.OpenParams openParams) { super(context, name, version, openParams); } // 创建数据文件时调用,此时适合创建新表 @Override public void onCreate(SQLiteDatabase db) { } // 更新数据库版本时调用,适合更新表结构或创建新表 @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { } } // 生成 helper 对象,可以打开数据库文件。文件名可以是相对路径或绝对路径 DBOpenHelper dbHelper = new DBOpenHelper(this, "test.db", null, 1); // 用读写的方式打开数据库文件 SQLiteDatabase database = dbHelper.getWritableDatabase(); 复制代码
1.5 使用场景
openDatabase 适合打开已经存在的数据库文件。并且表目录和结构固定,一般只有对数据库表的常规操作。如,已有一个全国城市信息数据库文件,需要在项目中打开。
SQLiteOpenHelper 需要自己重新创建数据库文件,根据后期的需求可能需要对数据库的表目录及结构做修改。升级数据版本,在 onUpgrade
方法中处理即可。openDatabase 也可以实现这个需求,只是稍微麻烦些。
2 插入数据
插入操作方法总结为两类,insert 和 replace ,最后都执行 insertWithOnConflict 方法。
/** * 向数据库插入一行数据 * * @param table 指定表名,插入一行数据 * @param nullColumnHack 可选参数,建议是 null * 如果设置 null,将不允许向表中插入空数据,即 values = null 时无法正确执行插入操作。 * 如果不设置 null,那么需要设置表中可以为空的属性列的名称。 * 当 values = null 时,可以向表中插入空数据。 * 而实际上是插入一行数据,只有属性列名 nullColumnHack 的值是 null。 * @param values map 集合,包含需要插入的一行数据。至少需要包含一个属性的 key 和 value。 * key 是属性列名称,value 是属性值。 * @return 返回新插入的行序号, 发生错误时,返回 -1 */ public long insert(String table, String nullColumnHack, ContentValues values) { try { return insertWithOnConflict(table, nullColumnHack, values, CONFLICT_NONE); } catch (SQLException e) { Log.e(TAG, "Error inserting " + values, e); return -1; } } /** * 向数据库插入一行数据。只是在插入操作异常时,需要手动捕获异常。 * 参数都同上 * @param table * @param nullColumnHack * @param values * @throws SQLException * @return */ public long insertOrThrow(String table, String nullColumnHack, ContentValues values) throws SQLException { return insertWithOnConflict(table, nullColumnHack, values, CONFLICT_NONE); } 复制代码
/** * 根据主键替换表中的一行数据,如果替换的主键在表中不存在,则插入一行数据。 * 参数都同上 * @param table * @param nullColumnHack * @param initialValues * @return 返回新插入的行序号, 发生错误时,返回 -1 */ public long replace(String table, String nullColumnHack, ContentValues initialValues) { try { return insertWithOnConflict(table, nullColumnHack, initialValues, CONFLICT_REPLACE); } catch (SQLException e) { Log.e(TAG, "Error inserting " + initialValues, e); return -1; } } /** * 根据主键替换表中的一行数据,如果替换的主键在表中不存在,则插入一行数据。 * 参数都同上 * @param table * @param nullColumnHack * @param initialValues * @throws SQLException * @return */ public long replaceOrThrow(String table, String nullColumnHack, ContentValues initialValues) throws SQLException { return insertWithOnConflict(table, nullColumnHack, initialValues, CONFLICT_REPLACE); } 复制代码
/** * 向数据库中插入一行数据的通用方法。 * 参数同上。 * @param table * @param nullColumnHack * @param initialValues * @param conflictAlgorithm 解决冲突的处理算法 * @return 返回插入数据的行序号。当 conflictAlgorithm 错误或者发生插入异常时,返回-1。 */ public long insertWithOnConflict(String table, String nullColumnHack, ContentValues initialValues, int conflictAlgorithm) { acquireReference(); try { StringBuilder sql = new StringBuilder(); sql.append("INSERT"); // 冲突处理算法,插入时只用第一个和最后一个。 // {"", "OR ROLLBACK", "OR ABORT", "OR FAIL", "OR IGNORE", "OR REPLACE"}; sql.append(CONFLICT_VALUES[conflictAlgorithm]); sql.append(" INTO "); sql.append(table); sql.append('('); Object[] bindArgs = null; int size = (initialValues != null && !initialValues.isEmpty()) ? initialValues.size() : 0; // 拼接 sql 语句 if (size > 0) { bindArgs = new Object[size]; int i = 0; for (String colName : initialValues.keySet()) { sql.append((i > 0) ? "," : ""); // 拼接列名称 sql.append(colName); // 保存属性值参数 bindArgs[i++] = initialValues.get(colName); } sql.append(')'); sql.append(" VALUES ("); // 将所有的值用 ? 做占位符 for (i = 0; i < size; i++) { sql.append((i > 0) ? ",?" : "?"); } } else { // 插入的数据集合是空时,至少需要有一列数据,列名称是 nullColumnHack,值是 NULL sql.append(nullColumnHack + ") VALUES (NULL"); } sql.append(')'); // 执行插入操作 SQLiteStatement statement = new SQLiteStatement(this, sql.toString(), bindArgs); try { return statement.executeInsert(); } finally { statement.close(); } } finally { releaseReference(); } } 复制代码
演示代码,不掩饰抛出异常的方法:
ContentValues values = new ContentValues(); values.put("id", 1); values.put("name", "flueky"); values.put("age", 27); // 插入完整的数据,id name age database.insert("user", null, values); // 插入空数据,将 name 设置为 null database.insert("user", "name", null); // 替换 id = 1 的数据 values.put("id", 1); values.put("name", "xiaofei"); values.put("age", 27); database.replace("user", null, values); 复制代码
replace
方法类似于 update
操作。
3 删除数据
删除数据库表数据,只有一个方法。支持指定删除条件
/** * 删除数据库中一行的方法 * * @param table 需要删除的表名 * @param whereClause 传入 null 时表示删除表中全部数据。 * 或者指定删除条件,只会删除满足条件的行。 * @param whereArgs 指定删除条件的值,按照顺序替换在删除条件中的 ? 。 * @return 删除满足条件的行时,返回删除的行数。找不到满足条件删除的时候,返回 0 。 */ public int delete(String table, String whereClause, String[] whereArgs) { acquireReference(); try { SQLiteStatement statement = new SQLiteStatement(this, "DELETE FROM " + table + (!TextUtils.isEmpty(whereClause) ? " WHERE " + whereClause : ""), whereArgs); try { return statement.executeUpdateDelete(); } finally { statement.close(); } } finally { releaseReference(); } } 复制代码
4 更新数据
更新表数据方法有两个,建议只用第一个, conflictAlgorithm
参数默认 CONFLICT_NONE
。
/** * 更新数据库表中的一行数据 * * @param table 需要更新的表名 * @param values 包含属性名和新属性值的 map 集合。 * @param whereClause 可选的 WHERE 条件决定需要更新的行。 * 如果是空,则更新所有的行。 * @param whereArgs 替换在 where 条件中包含的 ? 。 * @return 返回更新的行数 */ public int update(String table, ContentValues values, String whereClause, String[] whereArgs) { return updateWithOnConflict(table, values, whereClause, whereArgs, CONFLICT_NONE); } /** * 更新数据库表中的一行数据 * 参数同上 * * @param table * @param values * @param whereClause * @param whereArgs * @param conflictAlgorithm 决定更新冲突的算法。 * @return */ public int updateWithOnConflict(String table, ContentValues values, String whereClause, String[] whereArgs, int conflictAlgorithm) { // 官方源码中说,null is valid,此处却抛出异常,有点费解 if (values == null || values.isEmpty()) { throw new IllegalArgumentException("Empty values"); } acquireReference(); try { // 拼接更新的 sql 语句 StringBuilder sql = new StringBuilder(120); sql.append("UPDATE "); sql.append(CONFLICT_VALUES[conflictAlgorithm]); sql.append(table); sql.append(" SET "); // 将 values 和 whereArgs 值拼接在一起 int setValuesSize = values.size(); int bindArgsSize = (whereArgs == null) ? setValuesSize : (setValuesSize + whereArgs.length); Object[] bindArgs = new Object[bindArgsSize]; int i = 0; // 拼接需要更新的列属性 for (String colName : values.keySet()) { sql.append((i > 0) ? "," : ""); sql.append(colName); // 保存属性值 bindArgs[i++] = values.get(colName); sql.append("=?"); } // 添加 where 条件的值 if (whereArgs != null) { for (i = setValuesSize; i < bindArgsSize; i++) { bindArgs[i] = whereArgs[i - setValuesSize]; } } if (!TextUtils.isEmpty(whereClause)) { sql.append(" WHERE "); sql.append(whereClause); } SQLiteStatement statement = new SQLiteStatement(this, sql.toString(), bindArgs); try { // 执行 sql 语句 return statement.executeUpdateDelete(); } finally { statement.close(); } } finally { releaseReference(); } } 复制代码
5 查询数据
Android 给数据库的表查询操作提供了丰富的 API,一共10个方法,去除重载方法,共四个。下表中列出,四个方法和使用的参数。
- ● 表示必传参数
- ○ 表示可传参数
- - 表示不用的参数
参数 | query | queryWithFactory | rawQuery | rawQueryWithFactory |
---|---|---|---|---|
String table | ● | ● | - | - |
String[] columns | ● | ● | - | - |
String selection | ● | ● | - | - |
String[] selectionArgs | ● | ● | ● | ● |
String groupBy | ● | ● | - | - |
String having | ● | ● | - | - |
String orderBy | ● | ● | - | - |
String limit | ○ | ● | - | - |
boolean distinct | ○ | ● | - | - |
CancellationSignal cancellationSignal | ○ | ○ | ○ | ○ |
CursorFactory cursorFactory | - | ● | - | ● |
String sql | - | - | ● | ● |
String editTable | - | - | - | ● |
参数说明:
- table 需要查询的表名。
-
columns
列出需要查询的列。传递
null
将返回所有的列。不建议这样做,防止从不用的存储中读取数据。 -
selection
声明需要返回表数据的过滤器,同
where
条件子句格式一样(不包含where
自身)。传入null
将返回表中所有的数据。 -
selectionArgs
替换在
selection
中使用的?
或sql
中使用的?
。 -
groupBy
申明返回的表数据的分组规则,同
GROUP BY
子句一样(不包含GROUP BY
)传入null
将不分组。 -
having
决定哪些分组会包含在
cursor
中。如果使用了分组条件将按照HAVING
子句(不包含HAVING
)去格式化分组。传入null
会将所有的分组都包含在cursor
中。且不使用分组时,必须用null
。 -
orderBy
对表数据进行排序。同
ORDER BY
的条件语句一样(不包含ORDER BY
)。传入null
将不排序。 -
limit
限定查询数据的条数。同
LIMIT
子句。传入null
不限定查询条数。 -
distinct
如果想每行数据不一样,用
true
,否则用false
。 -
cancellationSignal
取消查询进程中的操作信号。如果不需要手动取消,使用
null
。如果操作被取消会抛出OperationCanceledException
。 -
cursorFactory
生成
Cursor
对象,如果是null
,则使用打开数据库时使用的cursorFactory
。如果打开数据库时也使用null
,那么自动生成Cursor
。 -
sql
所有的
query
方法,最后都会合并出sql
执行rawquery
方法。 - editTable 指定需要编辑的表名。
/** * 最简单也最常用的 query 方法。最后拼接好 sql 语句,去执行 rawQuery 方法。 */ public Cursor query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy) { return query(false, table, columns, selection, selectionArgs, groupBy, having, orderBy, null /* limit */); } /** * 参数最全的 query 方法,所有的参数可以手动指定,或使用 null。除 table。 */ public Cursor queryWithFactory(CursorFactory cursorFactory, boolean distinct, String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit, CancellationSignal cancellationSignal) { acquireReference(); try { String sql = SQLiteQueryBuilder.buildQueryString( distinct, table, columns, selection, groupBy, having, orderBy, limit); return rawQueryWithFactory(cursorFactory, sql, selectionArgs, findEditTable(table), cancellationSignal); } finally { releaseReference(); } } /** * 最简单也最常用的 rawQuery 方法。可以自定义好查询的 sql 语句后,自主查询。适用于复杂的关系表关联查询。 */ public Cursor rawQuery(String sql, String[] selectionArgs) { return rawQueryWithFactory(null, sql, selectionArgs, null, null); } /** * 参数最全的 rawQuery 方法,使用场景不多。 */ public Cursor rawQueryWithFactory( CursorFactory cursorFactory, String sql, String[] selectionArgs, String editTable, CancellationSignal cancellationSignal) { acquireReference(); try { SQLiteCursorDriver driver = new SQLiteDirectCursorDriver(this, sql, editTable, cancellationSignal); return driver.query(cursorFactory != null ? cursorFactory : mCursorFactory, selectionArgs); } finally { releaseReference(); } } 复制代码
6 事务
事务( Transaction
)是一个对数据库执行工作单元。事务( Transaction
)是以逻辑顺序完成的工作单位或序列,可以是由用户手动操作完成,也可以是由某种数据库程序自动完成。
Android
中, SQLite
事务主要提供了三个 API。在结束事务前,如想将事务提交到数据库,需要设置事务完成标记。否则,在事务开启时候做的数据库操作将不会保留。
database.beginTransaction(); /* 此处执行数据库操作 */ database.setTransactionSuccessful(); database.endTransaction(); 复制代码
7 结束语
本文到此,已经介绍了 SQLite
常用的操作。如想自定义 sql
语句实现更丰富的数据库操作,使用 database.execSQL
方法。使用自定义 sql
查询数据库请使用 database.rawQuery
方法。
为避免篇幅过长, 暂时介绍到这里。关于 SQLite
更多的操作语句和用法,请见后续文章。
觉得有用?那打赏一个呗。[去打赏]({{ site.url }}/donate/)
此处是广告 : Flueky的技术小站
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- Golang 和本地数据存储 - 快速灵活的数据存储
- 收集和存储数据——数据仓库(一)
- Python3网络爬虫实战---32、数据存储:关系型数据库存储:MySQL
- 数据库的“行式存储”和“列式存储”
- Flutter持久化存储之数据库存储
- 关于python中数据存储大总结,涵盖文件系统和数据库存储两种方法
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Essential C++中文版
李普曼 (Stanley B.Lippman) / 侯捷 / 电子工业出版社 / 2013-8-1 / CNY 65.00
本书以四个面向来表现C++的本质:procedural(面向过程的)、generic(泛型的)、object-based(基于对象的)、objectoriented(面向对象的)。全书围绕一系列逐渐繁复的程序问题,以及用以解决这些问题的语言特性来组织。循此方式,你将不只学到C++的功能和结构,也可学到它们的设计目的和基本原理。 本书适合那些已经开始从事软件设计,又抽不出太多时间学习新技术的程......一起来看看 《Essential C++中文版》 这本书的介绍吧!