C++ 学习笔记 1 — A Better C

C++ 是作为 C 语言的面向对象版本而生的,因此它完全兼容 C 语言的特性。本文首先回顾了 C 语言中关于预处理器和指针的相关知识,而后介绍了引用、命名空间、重载等 C++ 特性。最后,本文总结了 static、const 等关键字的使用方法。

预处理器

C 语言中,我们习惯于使用宏定义来提高程序的执行效率。但宏定义的作用域为整个工程,不符合 C++ 中封装的思想。因此除非必要,还是应该使用类变量或函数来完成宏的功能(控制宏除外)。

常量

常量宏最为常见

#include <stdio.h>
#define PI 3.14 // #define Identifier Substitution

int main(int argc, char *argv[]) {
	printf("%.2lf\n", PI);
	return 0;
}
/* output:
 * 3.14
 */

在大部分 PI 出现过的地方都会被 3.14 替换,除了以下情况

  • 字符串字面量(两个引号之间)
  • 另一个标识符内部(如PICTURE中的PI不会被替换)

除了用户自定义的常量宏,预处理器定义了一些内置常量,主要用于 debug

cout << __FILE__ << endl; // main.cpp
cout << __LINE__ << endl; // 13
cout << __TIME__ << endl; // 10:48:07
cout << __DATE__ << endl; // Aug 29 2019

另外,在 gcc 中还支持如

cout << __func__ << endl; // main
cout << __FUNCTION__ << endl; // main
cout << __PRETTY_FUNCTION__ << endl; // int main(int, char**)

宏函数

当函数中存在一小段语句块被大量使用时,可以将其定义为宏函数(而不是一般的函数)以提高运行效率。例如

#include <stdio.h>
#define ADD(a,b) ((a)+(b))

int main(int argc, char *argv[]) {
	printf("%d\n", ADD(1,2);
	return 0;
}
/* output:
 * 3
 */

如果一个宏函数要跨越多行,可以采用如下形式

#define	LIST_HEAD(name, type) \
struct name { \
	struct type *lh_first; /* first element */ \
}

行尾的 \ 表示当前行没有结束,将下一行的内容拼接上来。注意最后一行不可以出现\,除非在编程时确保宏函数后跟空行。在大多数多行宏函数中,需要采用 do-while 语句块来包裹

#define macro(a) do { \
	expr1; \
	expr2; \
} while (0)

for (int i = 0; i < N; i++) 
	macro(a);

等价于

for (int i = 0; i < N; i++)
    do {    
        expr1;
        expr2;
    } while (0);

字符串操作

## 运算符可以用于字符串拼接

#define ENV_CREATE(x) \
{ \
	extern u_char binary_##x##_start[]; \
	extern u_int binary_##x##_size; \
	env_create( \
	    binary_##x##_start, \
		(u_int)binary_##x##_size); \
}

ENV_CREATE(user_icode);

上述代码将声明两个变量binary_user_icode_startbinary_user_icode_size,可见##的作用即是将参数作为字符串强制拼接到函数体中。

与之相似,# 会将参数替换为用引号包裹的字符串。

#define STR(x) #x

void main(int argc, char *argv[]) {
	cout << STR(hello) << endl;
	return;
}
/* output:
 * hello
 */

STR 将其参数直接替换为 "hello" 输出。

控制宏

控制宏经常被用于控制头文件中代码被源文件包含的次数,避免重定义

#ifndef HEADER_H
#define HEADER_H
// ...
#endif /* HEADER_H */

Visual Studio中,这段代码的等价形式是

#pragma once

在某些情况下,可能需要同时支持 C 和 C++ 的编译方式,这时可以使用内置常量宏 __cplusplus。当使用 C 方式编译时,这一常量不会被定义

#ifdef __cplusplus
	// ...
#else
	// ...
#endif /* __cplusplus */

指针

C 语言程序员对指针都是很熟悉的,但是很多人可能会忽视使用指针的规范

  1. 申请指针变量时赋初值
  2. 不需要指针时赋值为 NULL
  3. 申请内存后或使用指针前判断其有效性
  4. Never pass by value
  5. 不要使用浅拷贝

这些编程习惯可以有效的解决 fly pointermemory leak重复释放等问题。其中,最重要的是不要让两个指针指向同一片内存,也就是不要使用浅拷贝。尤其在并行程序中,浅拷贝所导致的问题可能有多种表现形式,十分难以查错。

回调函数

函数也需要保存在内存中,因此函数也有地址,可以被赋值给指针。指向函数的指针称为函数指针,通过指针调用的函数称为回调函数。函数指针变量的声明如下

// return_type (*p_name)(param_list);
int (*p_fun)(int, int);

使用时和函数调用类似

int fun(int, int);
p_fun = fun; // assignment
int c = (*p_fun)(1, 2); // call back

函数指针主要用于参数传递,可以实现模板

#include <stdio.h>
#include <stdlib.h>

int add(int a, int b) {
	return a + b;
}

int mult(int a, int b) {
	return a * b;
}

int template(int (*fun)(int, int), int a, int b) {
	return (*fun)(a, b);
}

void main() {
	printf("add result: %d\n", template(add, 2, 3));
	printf("mult result: %d\n", template(mult, 2, 3));
}

输出

add result: 5
mult result: 6

引用

为了从语言层避免指针引发的一些问题,C++ 引入了引用。引用是更安全的指针,类似于变量的一个别名,他和指针有如下区别

  1. 引用不能为空:创建引用时就需要赋值
  2. 引用不能更改:一旦创建,不能指向其他对象

引用的定义和使用都和普通的变量一样

int a = 3;
int &b = a;
cout << b << endl;

甚至可以作为函数参数使用。例如用引用完成两个数的交换

void swap(int& a, int& b) {
	int c = a;
	a = b;
	b = c;
}

类似的,引用也可以作为函数的返回值。只不过一般情况下我们不会返回引用,这是因为如果引用的对象定义在函数外,那么使用 inout 参数就可以了。而如果指针指向的对象定义在函数内,则一般是栈空间变量,在函数返回时会被销毁。但在以下的情境中,引用是可以作为函数返回值的

int& fun(void) {
	static int a = 10;
	return a;
}

void main(void) {
	fun() = 20;
}

此时 fun的返回值放在了等号左边,被称作左值。函数返回值很少作为左值,此处即为一个特例。

关键字

static

static 关键字用于描述静态函数或变量。

静态局部变量

静态局部变量会默认初始化为 0(未初始化的静态变量会被存放在 bss 段)。且该变量自声明起会一直存在,直到程序运行结束。但只有变量所在函数可以访问变量。

void fun(void) {
	static int n;
	cout << n++ << endl;
}

void main(int argc, char* argv[]) {
	fun();
	fun();
	fun();
	return;
}

输出

0
1
2

静态函数与静态全局变量

静态函数的使用和静态全局变量类似,只有当前文件内部可以访问。静态函数常常和 inline 关键字结合,用于在头文件中定义小型函数。

// header.h
static inline void fun(int, int) {
	// ...
}

这样当多个源文件包含了 header.h 时,fun 实际以静态形式在这些文件中展开,并不会引发重定义的问题。

静态成员

在类中使用 static 关键字可以将成员变量声明为静态

// cat.h
class Cat 
    : public Animal {
public:
	static int NLEG; // 声明静态成员变量
};
// cat.cpp
int Cat::NLEG = 4; // 需要在类外定义

int main(int argc, char *argv[]) {
	Cat c;
	cout << c.NLEG << endl; // 可以通过对象访问
	cout << Cat::NLEG << endl; // 可以通过类名访问
	return 0;
}

输出

4
4

静态成员变量在定义时分配存储空间,因此不能在头文件中定义。静态成员变量在类中只存在一份拷贝,且生命周期与程序相同。此外,静态成员变量既可以通过对象访问,也可以通过类名访问。

静态成员函数与静态成员变量类似,属于整个类而非某个特定对象。因此,静态成员函数中没有 this 指针,也无法调用非静态成员变量。

const

const 关键字用于定义一个不可变对象

const int i(5);

const 修饰指针变量时,涉及到指针本身和指针指向的变量两个变量,有时容易弄混究竟哪个是常量。助记方法:从右向左读声明语句。

int a = 3;
const int* p = &a; // p 是一个指向(*)整数(int)的常量(const)指针
int const *p = &a; // 同上
int * const p = &a; // p 是一个常量(const)指针,指向(*)整数(int)
const int* const p = &a; 

常量引用指引用的对象不能通过引用被改变,但可以通过其他方式改变。

void main() {
	int a = 3;
	const int& ref = a;
	cout << ref << endl;
	a = 4;
	cout << ref << endl;
}

输出

3
4

全局变量

被标记为 const 的全局变量默认具有 static 属性。要将其作用域变为全局,需要显式地将其声明为 extern

// animal.h
extern const int var;
// const int var = 10; // error LNK1120: 1 unresolved externals

不要忘记在声明时加上 const 关键字。

成员变量

// animal.h
class Animal {
	const int spec;
public:
	Animal(int spec);
};

被标记为 const 的成员变量必须通过初始化列表进行赋值。

// animal.cpp
Animal::Animal(int spec) 
    : spec(spec) {
	// ...
}

成员函数

有时一个自定义类型的对象会被标记为 const 。这时,随意调用方法可能会改变对象状态,从而破坏 const 限制。因此,编译器会阻止对象调用任何非 const 方法

class Animal
{
	int age;
public:
	Animal(void) : age(0) {}
	int getAge() const { return age; }
};

void main() 
{
	const Aniaml animal;
	animal.getAge(); 
}

需要注意的是,修饰为 const 的方法

  • 不能修改对象的成员变量(修饰为 mutable 的变量除外)
  • 不能调用非 const 方法

函数参数

函数参数可以视为局部变量。将函数参数声明为 const 不仅可以避免函数定义中更改参数内容,同时可以标明输入参数与输出参数。

因为 never pass by value,自定义类型的函数参数往往是指针形式的。而这些指针中既包含输入参数,又包含输出参数,容易弄混。比较好的解决办法是将输入参数标为只读,这样用户只通过函数声明就可以知道哪些参数是输入参数,哪些是输出。

char* strcpy(char* dest, const char* src);

这是 C 库中 strcpy 函数的声明,很好的体现了 const 关键字在函数声明中的作用。

函数返回值

const 修饰函数返回值并不是很常见。这是因为

  1. 如果返回值是基础数据类型,不需要声明为常量
  2. 自定义数据类型通过 inout 参数返回

inline

inline 关键字用于声明内联函数。类似于 #define 定义的宏函数,内联函数也是为了提高程序运行效率而存在的,而且只适用于比较短小的函数。

当一个函数调用内联函数时,编译器将内联函数的代码拷贝到本函数中。这样就省去了运行时保存现场的开销,但是也造成了代码膨胀的问题。因此,使用 inline 关键字有一定限制

  • 不能包含复杂的结构控制语句,如 forwhileswitch
  • 内联函数本身不能是递归函数
  • 关键字需要与函数定义在一起才能生效(建议放在头文件)

同时需要注意

  • 即使标识了关键字,编译器也不一定按内联函数处理
  • 在类声明中如果定义了函数,则该函数默认内联

new & delete

newdelete 是 C++ 中定义的一对操作符,其作用分别对应于 C 中的 mallocfree 函数。

Cat* cat = new Cat();
delete cat; 

等价于

Cat* cat = (Cat*)malloc(sizeof(Cat));
cat->Cat();

cat->~Cat();
free cat;

值得注意的是,在使用 newdelete 为数组分配空间时,同样需要配对使用

int *arr = new int[10];
delete [] arr;

如果仅仅使用 delete arr 将仅删除 arr 数组的第一个元素。

如果仅使用 new 分配了内存空间,而没有使用 delete 合理释放,将会导致内存泄露。因此,一般情况下我们需要确保一个语句块中的 newdelete 成对出现。

参考

C++打印类名+函数名的方法

【转】C++11之for循环的新用法

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
SQLAlchemy 是一个 SQL 工具包和对象关系映射(ORM)库,用于 Python 编程语言。它提供了一个高级的 SQL 工具和对象关系映射工具,允许开发者以 Python 类和对象的形式操作数据库,而无需编写大量的 SQL 语句。SQLAlchemy 建立在 DBAPI 之上,支持多种数据库后端,如 SQLite, MySQL, PostgreSQL 等。 SQLAlchemy 的核心功能: 对象关系映射(ORM): SQLAlchemy 允许开发者使用 Python 类来表示数据库表,使用类的实例表示表中的行。 开发者可以定义类之间的关系(如一对多、多对多),SQLAlchemy 会自动处理这些关系在数据库中的映射。 通过 ORM,开发者可以像操作 Python 对象一样操作数据库,这大大简化了数据库操作的复杂性。 表达式语言: SQLAlchemy 提供了一个丰富的 SQL 表达式语言,允许开发者以 Python 表达式的方式编写复杂的 SQL 查询。 表达式语言提供了对 SQL 语句的灵活控制,同时保持了代码的可读性和可维护性。 数据库引擎和连接池: SQLAlchemy 支持多种数据库后端,并且为每种后端提供了对应的数据库引擎。 它还提供了连接池管理功能,以优化数据库连接的创建、使用和释放。 会话管理: SQLAlchemy 使用会话(Session)来管理对象的持久化状态。 会话提供了一个工作单元(unit of work)和身份映射(identity map)的概念,使得对象的状态管理和查询更加高效。 事件系统: SQLAlchemy 提供了一个事件系统,允许开发者在 ORM 的各个生命周期阶段插入自定义的钩子函数。 这使得开发者可以在对象加载、修改、删除等操作时执行额外的逻辑。
SQLAlchemy 是一个 SQL 工具包和对象关系映射(ORM)库,用于 Python 编程语言。它提供了一个高级的 SQL 工具和对象关系映射工具,允许开发者以 Python 类和对象的形式操作数据库,而无需编写大量的 SQL 语句。SQLAlchemy 建立在 DBAPI 之上,支持多种数据库后端,如 SQLite, MySQL, PostgreSQL 等。 SQLAlchemy 的核心功能: 对象关系映射(ORM): SQLAlchemy 允许开发者使用 Python 类来表示数据库表,使用类的实例表示表中的行。 开发者可以定义类之间的关系(如一对多、多对多),SQLAlchemy 会自动处理这些关系在数据库中的映射。 通过 ORM,开发者可以像操作 Python 对象一样操作数据库,这大大简化了数据库操作的复杂性。 表达式语言: SQLAlchemy 提供了一个丰富的 SQL 表达式语言,允许开发者以 Python 表达式的方式编写复杂的 SQL 查询。 表达式语言提供了对 SQL 语句的灵活控制,同时保持了代码的可读性和可维护性。 数据库引擎和连接池: SQLAlchemy 支持多种数据库后端,并且为每种后端提供了对应的数据库引擎。 它还提供了连接池管理功能,以优化数据库连接的创建、使用和释放。 会话管理: SQLAlchemy 使用会话(Session)来管理对象的持久化状态。 会话提供了一个工作单元(unit of work)和身份映射(identity map)的概念,使得对象的状态管理和查询更加高效。 事件系统: SQLAlchemy 提供了一个事件系统,允许开发者在 ORM 的各个生命周期阶段插入自定义的钩子函数。 这使得开发者可以在对象加载、修改、删除等操作时执行额外的逻辑。
SQLAlchemy 是一个 SQL 工具包和对象关系映射(ORM)库,用于 Python 编程语言。它提供了一个高级的 SQL 工具和对象关系映射工具,允许开发者以 Python 类和对象的形式操作数据库,而无需编写大量的 SQL 语句。SQLAlchemy 建立在 DBAPI 之上,支持多种数据库后端,如 SQLite, MySQL, PostgreSQL 等。 SQLAlchemy 的核心功能: 对象关系映射(ORM): SQLAlchemy 允许开发者使用 Python 类来表示数据库表,使用类的实例表示表中的行。 开发者可以定义类之间的关系(如一对多、多对多),SQLAlchemy 会自动处理这些关系在数据库中的映射。 通过 ORM,开发者可以像操作 Python 对象一样操作数据库,这大大简化了数据库操作的复杂性。 表达式语言: SQLAlchemy 提供了一个丰富的 SQL 表达式语言,允许开发者以 Python 表达式的方式编写复杂的 SQL 查询。 表达式语言提供了对 SQL 语句的灵活控制,同时保持了代码的可读性和可维护性。 数据库引擎和连接池: SQLAlchemy 支持多种数据库后端,并且为每种后端提供了对应的数据库引擎。 它还提供了连接池管理功能,以优化数据库连接的创建、使用和释放。 会话管理: SQLAlchemy 使用会话(Session)来管理对象的持久化状态。 会话提供了一个工作单元(unit of work)和身份映射(identity map)的概念,使得对象的状态管理和查询更加高效。 事件系统: SQLAlchemy 提供了一个事件系统,允许开发者在 ORM 的各个生命周期阶段插入自定义的钩子函数。 这使得开发者可以在对象加载、修改、删除等操作时执行额外的逻辑。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

LutingWang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值