结构体和类
在 C++ 中,struct 和 class 本质上非常相似,唯一的区别在于默认的访问权限:
- struct 默认的成员和继承是 public 。
- class 默认的成员和继承是 private 。
- 可以将 struct 当作一种简化形式的 class ,适合用于没有太多复杂功能的简单数据封装。
例子如下:
struct Books {
string title;
string author;
string subject;
int book_id;
// 构造函数
Books(string t, string a, string s, int id) : title(t), author(a), subject(s), book_id(id) {}
void printInfo() const {
cout << "书籍标题: " << title << endl;
cout << "书籍作者: " << author << endl;
cout << "书籍类目: " << subject << endl;
cout << "书籍 ID: " << book_id << endl;
}
};
void printBookByRef(const Books& book){
book.printInfo();
}
关键字
extern
extern+变量
extern+变量就是告诉编译器,在其他文件有一个这个变量存在;
extern+函数
当一个函数被声明或定义的时候,extern关键字被隐式地假定了
int foo(int arg1, char arg2); // 函数的声明
// 编译器会自动认为是 extern int foo(int arg1, char arg2);
static
static + 全局变量
该全局变量只允许在定义的文件中使用,其他文件无法访问。从而解决了在不同文件中定义相同的变量名, 同全局变量也一样,如果不初始化时,默认值是0
static局部变量
同普通局部变量一样,只能在定义的函数中使用,但是与局部变量的不同的是,不管其所在的函数是否被调用过,它会一直存在,而不像其他局部变量那样,会随着函数的被调用或退出而存在或消失。并且static局部变量只赋值一次
static 函数
同static全局变量类似,解决了相同函数名可以在不同文件中定义,同时其他文件无法访问static函数
register变量
register声明告诉编译器,将变量直接放入到寄存器中,因为寄存器的访问速度比内存快,所以register变量可以用于程序中使用频率高的。但编译器可以忽略此选项。
注意如下几点:
- register变量不允许返回地址
- register变量不允许与static公用,考虑一下为什么??
- register变量不允许定义成全局变量,只适用于局部变量和函数的形参,否则 在VS会有警告
volatile变量
volatile关键字是防止编译器对变量进行过度优化。保证每次读取该变量时都是从内存中读取,而不是被优化的某个临时寄存器中,一般应用在多线程或者中断服务中
可变参数
va_list ap; // 为了访问可变参数的指针
va_start(va_list ap, argN); // 初始化va_list, argN表示堆栈中的最后一个变量名称,一般是可变参数的第一个变量
va_arg(va_list ap, type); // type表示类型
va_end(va_list ap); // 结束可变参数遍历
用C风格实现myprintf
#include <iostream>
#include <cstdarg>
void MyPrintf(const char* format, ...) {
va_list args;
va_start(args, format);
while (*format) {
if (*format == '%' && *(format + 1)) { // 处理格式化字符
++format;
switch (*format) {
case 'd': { // 整数
int i = va_arg(args, int);
std::cout << i;
break;
}
case 'c': { // 字符
char c = static_cast<char>(va_arg(args, int)); // char 在可变参数中按 int 处理
std::cout << c;
break;
}
case 's': { // 字符串
char* s = va_arg(args, char*);
std::cout << s;
break;
}
case 'f': { // 浮点数
double f = va_arg(args, double);
std::cout << f;
break;
}
default:
std::cout << '%' << *format; // 直接输出不支持的格式
break;
}
} else {
std::cout << *format; // 直接输出普通字符
}
++format;
}
va_end(args);
}
int main() {
MyPrintf("Hello %s! Your score is %d and height is %f meters.\n", "Alice", 95, 1.75);
MyPrintf("Character: %c, Integer: %d\n", 'A', 123);
return 0;
}
为什么在可变参数中 char 要按 int 处理:
- 这是由于 C/C++ 的"默认参数提升"(default argument promotion)规则
- 当传递可变参数时:
- char、short 会被提升为 int
- float 会被提升为 double
- 所以即使你传入的是 char,在可变参数列表中实际存储的是 int
C++中的类型转换运算符
static_cast<目标类型>(要转换的值)
// 整数转字符
int i = 65;
char c = static_cast<char>(i); // c 现在是 'A'
// 浮点数转整数
float f = 3.14;
int n = static_cast<int>(f); // n 现在是 3
// 大类型转小类型
long l = 1000;
short s = static_cast<short>(l); // 把 long 转成 short
预处理
宏
define
带参数
#define MAX(a, b) ((a) > (b) ? (a) : (b))
int x = 5, y = 10;
int z = MAX(x + 1, y * 2);
为什么要加括号?
有括号展开后:
int z = ((x + 1) > (y * 2) ? (x + 1) : (y * 2));
无括号展开后:
int z = x + 1 > y * 2 ? x + 1 : y * 2;
这会导致 x + 1 > y * 2
的优先级低于 ? :
,从而产生错误的结果。
字符串化运算符#
#define PRINTVALUE(a) #a
printf("%s\n", PRINTVALUE(1) );
这里的#a
是把PRINTVALUE(a)变成字符串,即printf输出的是字符串"1"
,不加#
号就是数字1;
标记粘贴操作符
##
是标记粘贴操作符,它的作用是将两个标记(a
和 b
)连接成一个新的标记;
#define PRINTVALUE(a, b) a##b
void fun2() {
printf("This is fun2\n");
}
void test1() {
printf("This is test1\n");
}
int main() {
PRINTVALUE(fun, 2)(); // 调用 fun2()
PRINTVALUE(test, 1)(); // 调用 test1()
return 0;
}
条件编译
#ifdef
和 #ifndef
#define DEBUG
#ifdef DEBUG
// 如果 DEBUG 被定义,这段代码会被编译
printf("Debug mode is on.\n");
#endif
#ifndef RELEASE
// 如果 RELEASE 没有被定义,这段代码会被编译
printf("Release mode is off.\n");
#endif
#if
, #elif
, #else
#define VERSION 2
#if VERSION == 1
// 如果 VERSION 等于 1,这段代码会被编译
printf("Version 1\n");
#elif VERSION == 2
// 如果 VERSION 等于 2,这段代码会被编译
printf("Version 2\n");
#else
// 如果 VERSION 不等于 1 或 2,这段代码会被编译
printf("Unknown version\n");
#endif
#endif
#endif
:用于结束条件编译块。每个 #if
、#ifdef
、#ifndef
都必须有一个对应的 #endif
#ifdef
和#if defined
的区别
特性 | #ifdef | #if defined |
---|---|---|
功能 | 只能检查单个宏是否被定义 | 可以检查单个宏,也可以组合多个条件 |
灵活性 | 较低 | 较高 |
逻辑运算符支持 | 不支持 | 支持(如 && ) |
可读性 | 简单直观 | 更复杂,但功能更强大 |
#ifdef DEBUG
printf("Debug mode is on.\n");
#endif
#if defined(DEBUG) && defined(VERBOSE)
printf("Verbose debug mode is on.\n");
#elif defined(DEBUG)
printf("Debug mode is on.\n");
#endif
#error
#error
是 C/C++ 预处理指令之一,用于在编译时强制生成一个错误消息并停止编译过程
#define VERSION 10
#if VERSION < 20
#error "版本太低,不支持编译"
#endif
如果 VERSION
的值小于 20
,编译器会停止编译,并输出错误信息:"版本太低,不支持编译"
友元函数
什么是友元函数?
可以访问一个类的私有成员的外部函数;
注意:友元函数虽然在类种,但都不是成员函数!
用在什么情况?
当有两个类,两个类需要互相访问资源的时候;
举例子
可以实现访问不同的类的对象的私有成员:
class B;
class A{
private:
int valueA;
public:
A(int a) valueA(a){}
friend void showSum(A a ,B b);//声明友元函数
};
class B {
private:
int valueB;
public:
B(int b) : valueB(b) {}
friend void showSum(A a, B b); // 同样声明
};
void showSum(A a ,B b){
std::cout<<"A+B="<<a.valueA + b.valueB<<std::endl;//这里showSum虽然是外部函数,但是直接访问a的private值和b的prvate值,统一调度两个类的成员变量,而不用再在各个类中写get()函数,更方便;
}
当然也可以直接访问同一个类的不同对象的私有成员:
class A{
private:
int value;
public:
A(int a) value(a){}
friend int showSum(A a1 ,A a2);//声明友元函数,虽然函数在类中声明,但这是非成员函数
};
int showSum(A a1,A a2){
return a1.value + a2.value;//
}
也可以这样写(直接在类中实现函数):
class A{
private:
int value;
public:
A(int a) : value(a){}
friend int showSum(A a1 ,A a2){//该函数不是类的成员函数,但是可以访问对象a1和a2的私有成员变量
return a1.value + a2.value;
}
};