
Preprocessor and #define Directive
During the C++ compilation process, the source code is first passed to the preprocessor. #define is a preprocessor directive used to create symbol constants (also known as macro constants).
Basic Syntax
#define identifier replacement_text
Basic Usage Example
#include <iostream>
using namespace std;
// Using #define to define symbol constants
#define MAX_SIZE 100
#define PI 3.14159
#define COMPANY_NAME "Tech Corp"
#define NEWLINE '\n'
int main() {
cout << "=== #define Symbol Constant Example ===" << endl;
// Using defined symbol constants
int array[MAX_SIZE];
cout << "Array maximum size: " << MAX_SIZE << endl;
double radius = 5.0;
double area = PI * radius * radius;
cout << "Area of circle with radius " << radius << ": " << area << endl;
cout << "Company Name: " << COMPANY_NAME << NEWLINE;
return 0;
}
How the Preprocessor Works
#include <iostream>
using namespace std;
// The preprocessor performs text replacement before compilation
#define WIDTH 10
#define HEIGHT 5
#define AREA WIDTH * HEIGHT // Note: This is text replacement, not calculation
void demonstratePreprocessor() {
cout << "\nPreprocessor text replacement demonstration:" << endl;
cout << "Width: " << WIDTH << endl;
cout << "Height: " << HEIGHT << endl;
cout << "Area: " << AREA << endl; // Replaced with 10 * 5
// At compile time, the above code actually becomes:
// cout << "Area: " << 10 * 5 << endl;
}
Parameterized Macros (Macro Functions)
#include <iostream>
using namespace std;
// Parameterized macros
#define SQUARE(x) ((x) * (x))
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define IS_EVEN(n) ((n) % 2 == 0)
void demonstrateMacroFunctions() {
cout << "\nParameterized macro function example:" << endl;
int num = 5;
cout << num << " squared: " << SQUARE(num) << endl;
cout << num << " squared after adding 1: " << SQUARE(num + 1) << endl; // Note the importance of parentheses
cout << "MAX(10, 20) = " << MAX(10, 20) << endl;
cout << "MIN(10, 20) = " << MIN(10, 20) << endl;
cout << num << " is even? " << (IS_EVEN(num) ? "Yes" : "No") << endl;
cout << "4 is even? " << (IS_EVEN(4) ? "Yes" : "No") << endl;
}
Preprocessor Replacement Considerations
#include <iostream>
using namespace std;
// Problematic macro definitions
#define SQUARE_BAD(x) x * x
#define SQUARE_GOOD(x) ((x) * (x))
void demonstrateMacroPitfalls() {
cout << "\nMacro definition pitfalls:" << endl;
int a = 5;
// Correct usage
cout << "SQUARE_BAD(5) = " << SQUARE_BAD(5) << endl; // 25
cout << "SQUARE_GOOD(5) = " << SQUARE_GOOD(5) << endl; // 25
// Problematic example
cout << "SQUARE_BAD(5 + 1) = " << SQUARE_BAD(5 + 1) << endl; // 5 + 1 * 5 + 1 = 11
cout << "SQUARE_GOOD(5 + 1) = " << SQUARE_GOOD(5 + 1) << endl; // ((5 + 1) * (5 + 1)) = 36
// Another problematic example
#define INCREMENT_BAD(x) x++
#define INCREMENT_GOOD(x) ((x)++)
int b = 5;
cout << "INCREMENT_BAD(b) = " << INCREMENT_BAD(b) << endl; // 5
cout << "b after = " << b << endl; // 6
int c = 5;
int result = INCREMENT_BAD(c) * 2; // c++ * 2, undefined behavior
cout << "Dangerous operation result: " << result << endl;
}
Multiline Macro Definitions
#include <iostream>
using namespace std;
// Multiline macro definitions use backslash for continuation
#define SWAP(a, b) do { \
typeof(a) temp = a; \
a = b; \
b = temp; \
} while(0)
#define PRINT_ARRAY(arr, size) cout << "Array contents: "; \
for(int i = 0; i < size; i++) { \
cout << arr[i] << " "; \
} \
cout << endl;
void demonstrateMultilineMacros() {
cout << "\nMultiline macro definition example:" << endl;
int x = 10, y = 20;
cout << "Before swap: x = " << x << ", y = " << y << endl;
SWAP(x, y);
cout << "After swap: x = " << x << ", y = " << y << endl;
int numbers[] = {1, 2, 3, 4, 5};
PRINT_ARRAY(numbers, 5);
}
Conditional Compilation and Symbol Constants
#include <iostream>
using namespace std;
// Debug mode switch
#define DEBUG_MODE 1
#define VERSION "1.2.3"
void demonstrateConditionalCompilation() {
cout << "\nConditional compilation example:" << endl;
// Compile debug code based on DEBUG_MODE
#if DEBUG_MODE
cout << "[DEBUG] Program version: " << VERSION << endl;
cout << "[DEBUG] Entering main functionality..." << endl;
#endif
cout << "Program running normally..." << endl;
// Check if a macro is defined
#ifdef VERSION
cout << "Version information defined: " << VERSION << endl;
#endif
#ifndef RELEASE_MODE
cout << "This is not a release version" << endl;
#endif
}
Comparison with const Constants
#include <iostream>
using namespace std;
// Using #define to define
#define MAX_USERS 100
#define APP_NAME "MyApp"
// Using const to define
const int MAX_PRODUCTS = 50;
const string COMPANY = "Tech Company";
void compareWithConst() {
cout << "\nComparison between #define and const:" << endl;
cout << "Maximum users (macro): " << MAX_USERS << endl;
cout << "Maximum products (const): " << MAX_PRODUCTS << endl;
cout << "Application name (macro): " << APP_NAME << endl;
cout << "Company name (const): " << COMPANY << endl;
// Advantages of const constants
const double TAX_RATE = 0.08;
// TAX_RATE = 0.09; // Error: const constants cannot be modified
// Type safety
const int TIMEOUT = 5000; // Explicitly specify type as int
}
Practical Application Scenarios
#include <iostream>
#include <vector>
using namespace std;
// Configuration constants
#define CONFIG_MAX_CONNECTIONS 1000
#define CONFIG_TIMEOUT_MS 5000
#define CONFIG_LOG_LEVEL 2
#define CONFIG_ENABLE_CACHE 1
class DatabaseManager {
private:
vector<int> connections;
public:
DatabaseManager() {
connections.reserve(CONFIG_MAX_CONNECTIONS);
}
bool connect() {
#if CONFIG_LOG_LEVEL >= 1
cout << "[INFO] Attempting database connection..." << endl;
#endif
#if CONFIG_ENABLE_CACHE
cout << "[INFO] Cache enabled" << endl;
#endif
// Simulate connection
return true;
}
};
void demonstratePracticalUsage() {
cout << "\nPractical application scenario:" << endl;
DatabaseManager db;
db.connect();
// Error code definitions
#define ERROR_NONE 0
#define ERROR_FILE_NOT_FOUND 1
#define ERROR_PERMISSION_DENIED 2
#define ERROR_INVALID_DATA 3
int errorCode = ERROR_NONE;
cout << "Current error code: " << errorCode << " (No error)" << endl;
// State definitions
#define STATE_IDLE 0
#define STATE_RUNNING 1
#define STATE_PAUSED 2
#define STATE_STOPPED 3
}
Complete Example Program
#include <iostream>
#include <string>
using namespace std;
// Application configuration constants
#define APP_VERSION "2.1.0"
#define MAX_BUFFER_SIZE 1024
#define DEFAULT_PORT 8080
#define ENABLE_LOGGING 1
// Error codes
#define SUCCESS 0
#define ERROR_INVALID_INPUT -1
#define ERROR_FILE_IO -2
void demonstrateCompleteExample() {
cout << "=== Complete Preprocessor Constant Example ===" << endl;
cout << "Application version: " << APP_VERSION << endl;
cout << "Maximum buffer: " << MAX_BUFFER_SIZE << " bytes" << endl;
cout << "Default port: " << DEFAULT_PORT << endl;
#if ENABLE_LOGGING
cout << "[LOG] Logging system initialized" << endl;
#endif
// Using error codes
int result = SUCCESS;
cout << "Operation result: " << result << endl;
// Mathematical constants
#define DEG_TO_RAD(x) ((x) * 3.14159 / 180.0)
double angle = 45.0;
cout << angle << " degrees = " << DEG_TO_RAD(angle) << " radians" << endl;
}
int main() {
// Run all examples
demonstratePreprocessor();
demonstrateMacroFunctions();
demonstrateMacroPitfalls();
demonstrateMultilineMacros();
demonstrateConditionalCompilation();
compareWithConst();
demonstratePracticalUsage();
demonstrateCompleteExample();
return 0;
}
Important Knowledge Points Summary
Advantages of #define:
- Compile-time Replacement: Completed during the preprocessing stage, does not consume runtime resources
- Conditional Compilation: Can be used with
<span>#ifdef</span>,<span>#ifndef</span>, etc. - Cross-platform: Can define platform-specific constants
- Flexibility: Can define parameterized macro functions
Disadvantages of #define:
- No Type Checking: The preprocessor only performs text replacement, not type checking
- Scope Issues: Macros are valid throughout the entire file, which can easily cause naming conflicts
- Debugging Difficulty: The debugger sees the replaced code, not the original macro names
- Risk of Side Effects: Parameters may be evaluated multiple times, leading to unexpected behavior
Best Practices:
- Use Parentheses: Parameters in macro definitions and the entire expression should be surrounded by parentheses
- Avoid Side Effects: Do not use expressions with side effects in macro parameters
- Naming Conventions: Use uppercase letters and underscores to name macro constants
- Prefer const: In C++, prefer using
<span>const</span>and<span>constexpr</span>instead of<span>#define</span>
Applicable Scenarios:
- Header File Protection:
<span>#ifndef HEADER_H #define HEADER_H</span> - Conditional Compilation: Debug code, platform-specific code
- Simple Constants: Simple values that do not change and do not require types
- Compatibility with C Code: When needing to share header files with C code
In modern C++ programming, it is recommended to prioritize using <span>const</span>, <span>constexpr</span>, and <span>enum class</span> to define constants, as they provide better type safety and scope control. However, in certain cases (especially for conditional compilation and header file protection), <span>#define</span> remains a necessary tool.