Обмен технологиями

[Язык C] — Подробное объяснение предварительной обработки (часть 1)

2024-07-12

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina

1. Предопределенные символы

Есть некоторые настройки на языке C.Предопределенные символы, можно использовать напрямую, предопределенные символы:在预处理期间处理的

__FILE__    //进行编译的源文件
__LINE__	//文件当前的行号
__DATE__	//文件被编译日期
__TIME__	//文件被编译那一瞬的时间
__STDC__	//如果编译器遵循ANSI C(标准C),其值为1,否则未定义(报错)
  • 1
  • 2
  • 3
  • 4
  • 5

  
Пример:

#include<stdio.h>

int main()
{
	printf("%sn", __FILE__);
	printf("%dn", __LINE__);
	printf("%sn", __DATE__);
	printf("%sn", __TIME__);
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

  
результат операции:

Вставьте сюда описание изображения

Здесь хотелось бы упомянуть, что VS не полностью поддерживает ANSI C (стандарт C); gcc gccгкопий Поддерживает ANSI C (стандарт C)
  

два,# определить определитьгефве Определить константы (символы)

Основной синтаксис:

# define name stuff
  • 1

например:

#define MAX 100
#define reg register  // 为 register 这个关键字创建一个简短的名字
#define do_forever for(;;)  //用更形象的符号来替换一种实现
#define CASE break;case		//在写 case 语句的时候自动吧 break 写上

//如果定义的 stuff 过长,可以分成几行写,除了最后一行外,每行的后面都加一个反斜杠(续行符)
#define DEBUG_PRINT printf("file:%stline:%dt 
							date:%sttime:%sn" ,
							__FILE__,__LINE__ , 
							__DATE__,__TIME__ )
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • Второе предложение - лень, почему?
  • Третье предложение для дляфили Круговой初始化、判断、调整都可以省略, но если суждение опущено, это означает, что условие суждения всегда истинно, т.е.бесконечная петля
  • В четвертой игре этого лучше не делать, это запросто может навлечь беду.
  • Пятое предложениесимвол продолжения строкиИменно в предотвращении проблем после ветвления. Суть его в том.转义Назад回车符 , так что возврат каретки больше не является возвратом каретки.После символа продолжения строки ничего быть не может, нажмите“” Просто нажмите Enter, иначе продолжением не будет следующая строка кода.

  
Теперь возникает проблема: используйте # определить определитьгефве При определении идентификатора следует ли добавлять его в конце?

например:

#define MAX 1000
#define MAX 1000;

int main()
{
	int n = MAX;
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

Вставьте сюда описание изображения

Приведенный выше код плюс, это кажется немного излишним, но на работу программы это не влияет.
Кажется, добавлено Или не добавляй Все будет в порядке?
это действительно так?
  
Давайте посмотрим на следующий пример:

//例一
int main()
{
	printf("%dn", MAX);
	return 0;
}

//例二
int mian()
{
	int max = 0;
	if (1)
		max = MAX;
	else
		max = 1;
	return 0
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

После замены:

printf("%dn", 1000;);
  • 1

Распечатать1000;Что значит?

if (1)
	max = 1000;
	;
else
	max = 1;
  • 1
  • 2
  • 3
  • 4
  • 5

еще ещеелсе Кому соответствовать?
  
Понимаете, здесь что-то не так, так чтоиспользовать # определить определитьгефве При определении индикатора не добавляйте его после

  

три,# определить определитьгефве Определить макрос

  # определить определитьгефве Этот механизм включает положение,允许把参数替换到文本中, эту реализацию часто называютМакрос ( Марко ) ( Марко )маркоили Определить макрос ( definemacro ) ( определить макрос )гефвемакро
Разница между макросами и идентификаторами определений макросов, приведенными выше, заключается в следующем:Макрос имеет параметры
  
Вот как объявляется макрос:

#define name(parament - list) stuff
  • 1

один из них парамент параментпарявляюсьент - список списокляст(список параметров) — это список символов, разделенных запятыми, которые могут встречаться в вещи вещисттыфф середина
Примечание: парамент параментпарявляюсьент - список списокляст(список параметров)Левая скобкаДолжно быть с имя имянаме рядом с, если между ними есть пробелы, список аргументов интерпретируется как вещи вещисттыфф часть.
  

Пример:

//实现一个宏,计算一个数的平方
#define SQUARE(x) x*x

int main()
{
	int a = 5;
	int ret = SQUARE(a);
	printf("%dn", ret);
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

  
результат операции:

Вставьте сюда описание изображения

Как видите, квадрат 5 рассчитан правильно.

Но на самом деле в приведенном выше коде есть проблема. См. следующий фрагмент кода:

int a = 5;
printf("%dn", SQUARE(a + 1));
  • 1
  • 2

  
результат операции:

Вставьте сюда описание изображения

Почему это так? Результат 5+1 равен 36, а 6 ∗ * 6 должно быть 36. Как вы получили 11?

Проблема в макросах, мы знаем宏是直接替换, то результатом непосредственной замены приведенного выше кода будет:

printf("%dn", a+1*a+1);
  • 1

5+6+1, результат естественно 11

Мы можем легко решить эту проблему, добавив круглые скобки с обеих сторон определения макроса.

#define SQUARE(x) (x)*(x)
  • 1

Так нет ли проблем с этим определением?Давайте посмотрим на следующее определение макроса:

#define DOUBLE(X) (X)+(X)
  • 1

Мы использовали круглые скобки в определении, чтобы избежать предыдущей проблемы, но этот макрос может вызвать новые ошибки.

int a = 5;
printf("%dn", 10 * DOUBLE(a));
  • 1
  • 2

  
результат операции:

Вставьте сюда описание изображения

Результат вывода не 100, а 55. Причина аналогична вышеуказанной. Приоритетный вопрос
  
Решение:

#define DOUBLE(X) ((X)+(X))
  • 1

Таким образом, при использовании макросовНикогда не экономьте на скобкахчтобы избежать непредвиденных взаимодействий между операторами в параметрах или соседними операторами при использовании макросов.

  

4. Параметры макроса с побочными эффектами

Если параметр макроса встречается в определении макроса более одного раза, если параметр имеетпобочный эффект, то при использовании этого макроса вы можете подвергнуться опасности, что приведет к不可预测 результат. Побочные эффекты — это постоянные эффекты при вычислении выражения.
  
Например:

x + 1; //不带副作用
x++//带副作用
  • 1
  • 2

Следующий макрос MAX демонстрирует проблемы, вызванные параметрами с побочными эффектами.

#define MAX(a,b) ((a) > (b) ? (a):(b))

int main()
{
	int x = 5;
	int y = 8;
	int z = MAX(x++, y++);
	printf("x=%d, y=%d, z=%dn", x, y, z);
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

  
результат операции:

Вставьте сюда описание изображения

Почему это так?Давайте проанализируем это вместе

z = ((X++) > (y++) ? (x++) : (y++))
  • 1
  • Сначала вынеси решение: ххИкс++ с ггу++ суждение, потому что это постфикс ++, при суждении ххИкс 5, ггу равно 8, 8 &gt; 5
  • После суждения ххИкс 6, ггу за 9
  • Затем выполните ггу ++, поскольку постфикс ++, результат 9
  • Затем продолжайте ггу Выполнить автоинкремент, ггу Окончательный результат — 10.

мы будем ххИкс и ггу При передаче в макрос результаты изменились, особенно ггу, после двух изменений ты сказал, что это не страшно
Если в макрос передается параметр с побочными эффектами, и этот параметр появляется в макросе более одного раза, побочный эффект параметра также проявляется более одного раза.
  

5. Правила замены макросов

Развернуть в программе № определить определитьгефве При определении символов и макросов необходимо выполнить несколько шагов.

  • При вызове макроса сначала参数进行检查, посмотрите, содержит ли он # определить определитьгефве Определенный标识符 .Если да, то их заменяют в первую очередь.
  • Текст замены следует被插入в исходное место в программе, заменяя имена параметров макросов их значениями
#define MAX(a,b) ((a) > (b) ? (a):(b))
#define M 10

int main()
{
	int x = 5;
	int z = MAX(x, M);
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • МАКС ( х , М ) МАКС(х, М)МАИкс(Икс,М) середина МММ сначала заменяется на 10, а 10 вставляется в оригинал МММ расположение
  • Наконец, результаты сканируются еще раз, чтобы увидеть, содержат ли они что-либо, вызванное # определить определитьгефве определенный символ, если да, повторите описанный выше процесс

MAX в приведенном выше коде также обозначается # определить определитьгефве ОпределенныйМакрос .При последней проверке его параметр M был заменен. На этот раз пришло время его заменить.
   МАКС ( х , 10 ) МАКС(х, 10)МАИкс(Икс,10) заменяется на ( ( х ) &gt; ( 10 ) ((х) &gt; (10)((Икс)>(10) ? ? ? ( х ) : ( 10 ) ) (х):(10))(Икс):(10))
  
конечно,宏里面嵌套宏это тоже нормально

MAX(x, MAX(2, 3))
  • 1

В это время сначала замените макрос в параметре, а затем замените весь макрос
Но следует отметить, что это不是递归, который представляет собой просто макрос, служащий аргументом для другого макроса.递归是宏内部又调用了宏本身
Кроме того, когда препроцессор ищет # определить определитьгефве При определении символаСодержимое строковых констант не ищется
Что это значит?Просто дайте каштан и все поймете.

#define M 10

int main()
{
	int x = 5;
	int z = MAX(x, MAX(2, 3));
	printf("M = %d", x);
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

В приведенном выше кодеprintf("M = %d", x);середина МММ не будет заменено на 10

  

6. Сравнение макросов и функций

Приведенный выше макрос используется для поиска большего значения двух чисел. Мы можем полностью записать его как функцию.

int Max(int x, int y)
{
	return x > y ? x : y;
}
  • 1
  • 2
  • 3
  • 4

Мы обнаружили, что все они выполняют одну и ту же функцию.А вот для функции «найти большее из двух чисел» написание макроса будет更有优势Некоторый
  
Есть две причины:

  • используется для调用函数и函数返回изкодможет занять больше времени, чем фактическое выполнение этого небольшого расчета (при вызове функцииСоздать кадр стека ).Поэтому функция макросоотношения есть в программе规模и速度Лучше, чем когда-либо
  • Важнее函数的参数必须声明类型 , что приводит к использованию функции только для выражений соответствующего типа.Напротив, этот макрос можно применять ко многим типам: можно использовать целое число, длинное целое число, с плавающей запятой и т. д.
    > Сравнивать.Параметры макроса:Тип независимыйиз

Всегда ли мы будем использовать макросы в будущем?Фактически макросы используются только дляПростой расчет, не подходит для сложных и больших операций и функций по сравнению с макросами Недостатки

  1. Каждый раз, когда используется макрос, в программу вставляется копия кода определения макроса.Если макрос не относительно короткий, он может大幅度增加程序的长度
  2. макрос это没法调试из
  3. Макросы не зависят от типа, то есть不够严谨
  4. Макросы могут вызвать проблемы с приоритетом операций, в результате чего программа容易出错

Но иногда макросы могут делать то, чего не могут функции.
  
Например:

int* p = (int*)malloc(10 * sizeof * (int));
  • 1

Нам не нравится так писать талок талокмаллос Функция слишком громоздкая, я хочу大小и类型Передайте его, чтобы освободить место

Malloc(10, int);
  • 1

Могут ли функции сделать это?Нет потому чтоФункции не могут передавать типы
И макросы могут это сделать, потому что макросы этого не делают.Не проверятьваши параметрытипиз

#define Malloc(n, type) (type*)malloc(n * sizeof(type))

int main()
{
	int* p = Malloc(10, int);
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

  
Сравнение макросов и функций:

Атрибуты# определить определитьгефвеОпределить макросфункция
длина кодаПри каждом использовании в программу будет вставляться код макроса. За исключением очень маленьких макросов, длина программы значительно увеличивается.Значение кода функции появляется в одном месте, и каждый раз, когда функция используется, в этом месте вызывается один и тот же код.
Скорость выполненияБыстрееСуществуют дополнительные накладные расходы на вызовы и возвраты функций (открытие фреймов стека), поэтому давайте начнем с более медленных.
приоритет оператораПараметры макроса оцениваются в контексте всех окружающих выражений. Если не включены круглые скобки, приоритет соседних операторов может иметь непредсказуемые последствия. Поэтому рекомендуется писать макросы с большим количеством скобок.Параметры функции оцениваются только один раз при вызове функции, а ее значение результата передается функции, что делает результат вычисления выражения более предсказуемым.
Параметры с побочными эффектамиПараметры могут быть заменены на несколько позиций в макросе. Если параметры макроса оцениваются несколько раз, оценка параметров с побочными эффектами может привести к непредсказуемым результатам.Параметры функции вызываются только один раз при передаче параметров, и результаты легче предсказать.
Тип параметраПараметры макроса не имеют никакого отношения к типу. Пока операция над параметрами разрешена, его можно использовать с любым типом параметра.Параметры функции связаны с типом. Если параметры имеют разные типы, требуются разные функции, даже если они выполняют одну и ту же задачу.
отлаживатьМакросы неудобно отлаживатьФункции можно отлаживать шаг за шагом.
рекурсияМакросы не могут быть рекурсивнымиФункции могут быть рекурсивными

Есть ли способ объединить их преимущества?
Представлено в C++встроенная функция встроенный встроенныйвлве —— Имеет преимущества как макросов, так и функций
Он выполняется так же быстро, как макрос, но имеет тот же эффект, что и функция.