- ANSYS Fluent 二次开发指南
- 胡坤编著
- 3428字
- 2025-02-28 19:42:09
1.6 C语言基础
Fluent UDF采用C语言进行编写,本节简单介绍在UDF中经常会用到的C语言知识,本节部分内容来自UDF手册。
1.6.1 C语言中的注释
C语言中的注释利用/*及*/来实现。例如:
/*这是一个注释*/
注释也可以跨行实现,如:
/*这是一个 跨行注释*/
注 意
在编写UDF的过程中,不能把DEFINE宏(如DEFINE_PROFILE)放置在注释中,否则会引起编译错误。
1.6.2 基本数据类型
Fluent UDF解释器支持的标准C数据类型如下:
Int:整型,存储形如1、2、3之类的整数。
long:长整型,存储数据与int类似,但范围更广。
float:浮点型,存储小数,如1.234等。
double:双精度浮点型,与float类似。
char:字符型,如’a’、’b’、’c’等。
Fluent UDF中还有real型,其实这是Fluent自定义的数据类型,在双精度求解器中,real类型与double类型相同,而在单精度求解器中,real类型等同于float类型。UDF自动进行转换,因此在需要浮点数时,可以全部采用real类型。
1.6.3 常数
在C语言中可以利用#define来定义常数。需要注意的是,定义为常数类型后,该变量的值不能改变。如:
#define WALL_ID 5 #define YMIN 0.0 #define YMAX 0.4
这样定义完毕后,WALL_ID的值不能再发生改变,因此如下的语句会引发编译错误:
WALL_ID = WALL_ID +1;
1.6.4 全局变量和局部变量
变量用于存储数据。所有变量都包含类型、名称以及值,有时候还包含存储标记,如静态变量和外部变量。C语言中所有的变量在使用之前都必须声明,这样C编译器才会知道该如何为此变量分配内存。
C语言中的全局变量定义在函数的外部,该变量可以被源文件中所有的函数引用。全局变量如果未被声明为静态变量,还可以被外部函数引用。如下面程序中的全局变量声明:
#include ”udf.h” real volume; /*此处定义的是全局变量*/ DEFINE_ADJUST(vol,domain) { /*此处可以访问变量volume*/ }
局部变量一般定义在函数体内,其只在函数体内起作用,在函数体外无法被访问到。如下面程序中的局部变量定义:
DEFINE_PROPERTY(cell_viscosity, cell, thread) { real mu_lam; /*局部变量 */ real temp = C_T(cell, thread); /* 局部变量 */ if (temp > 288.) mu_lam = 5.5e-3; else if (temp > 286.) mu_lam = 143.2135 - 0.49725 * temp; else mu_lam = 1.; return mu_lam; }
1.6.5 外部变量
当在某个源文件中定义了一个未加static的全局变量后,若想在另一个源文件中调用此变量,此时可以使用外部变量声明来实现。采用如下声明:
extern real volume;
注 意
extern声明只能用于编译型UDF中。
以下是一个利用extern的案例。
假设在源文件file1.c中定义了全局变量:
#include ”udf.h” real volume; DEFINE_ADJUST(compute_volume, domain) { volume = .... }
若其他的源文件想要利用此全局变量 volume,此时可以创建头文件,并将变量 volume声明为extern变量,如创建头文件extfile.h,写入内容:
extern real volume;
之后就可以在其他的源文件中使用此变量volume了,如在源文件file2.c中:
#include ”udf.h” #include ”extfile.h” DEFINE_SOURCE(heat_source,c,t,ds,eqn) { real total_source = ...; real source; source = total_source/volume; return source; }
◎ 提示:外部变量使用起来很麻烦,也很容易出错,如果对其不甚了解,建议不要使用。
1.6.6 静态变量
静态变量(声明时添加static关键字)在用于局部变量或全局变量时具有不同的作用。局部变量被声明为static时,当函数返回后变量并不销毁,变量的值依旧被保留。全局变量被声明为static时,该变量能够被此源文件中的所有函数调用,但不能被其他源文件中的函数调用。实际上是变量被隐藏了。
例如在文件mysource.c中有如下代码:
#include ”udf.h” static real abs_coeff = 1.0; /*静态全局变量*/ /* 此变量只能被本文件中的其他函数调用 */ DEFINE_SOURCE(energy_source, c, t, dS, eqn) { real source; /* 局部变量*/ int P1 = ....; /* 局部变量*/ /*变量只能被当前函数调用,但在函数返回时变量并不释放 */ dS[eqn] = -16.* abs_coeff; source =-abs_coeff *(4.* SIGMA_SBC ); return source; } DEFINE_SOURCE(p1_source, c, t, dS, eqn) { real source; int P1 = ...; dS[eqn] = -abs_coeff; source = abs_coeff *(4.* SIGMA_SBC); return source; }
◎ 提示:与全局变量类似,静态变量也尽量少用,容易造成不必要的麻烦。
1.6.7 用户自定义数据类型
C语言允许用户自己定义数据类型,通过使用结构体及typedef关键字。如定义类型:
typedef struct list { int a; real b; int c; }mylist; mylist x,y,z;
上例定义了一个结构体类型mylist,并定义了三个结构体变量x、y、z。
1.6.8 强制转换
在C语言中,有时需要对类型进行强制转换,如将浮点型强制转换为整型,如下例程序:
int x =1; real y=3.1415926; int z=x+(int)y;
计算完毕后,z=4。
1.6.9 函数
C语言中的函数执行独立的任务。函数能够被同一源文件中的其他函数调用,也可以由源文件之外的函数调用。
函数定义包含函数名以及被传递给函数的零个或多个参数列表。函数包含一个包含在大括号内的主体,主体中包含执行任务的指令。函数可以返回特定类型的值。
函数返回特定数据类型的值(例如,实数),如果类型为void,则不返回任何值。要确定DEFINE宏的返回数据类型,可查看udf.h文件中宏的相应#define语句。
1.6.10 数组
C语言中数组变量定义为name[size],其中name为数组变量的名称,size为数组中存储的单元数量。C语言中数组索引从0开始。
int a[10], b[10][10]; real rad[5]; a[0] = 1; rad[4] = 3.14159265; b[10][10] = 4;
1.6.11 指针
指针是一种存储变量内存地址的变量。换句话说,指针是一个变量,这个变量指向另外一个变量的内存地址。指针变量的声明:
int *ip;/*定义指针变量ip*/
定义了指针变量后,可以利用取址运算符将其他变量的地址赋予指针变量,如:
int *ip; ip =&a;
也可以为指针变量赋值,如:
*ip =4;
当指针作为函数的参数,此时为传址调用,在函数体内修改指针参数的值,会改变调用函数时传递的参数的值。此功能可以实现一个函数返回多个值。
如下的C程序:
#include <stdio.h> int add(int *a,int b) { int sum = 0; sum = *a + b; *a = 5; return sum; } int main() { int *ip; int a = 1; int b = 2; int sum = 0; ip = &a; sum = add(ip,b); printf(“sum=%d,a=%d\n”,sum,a); return 0; }
输出结果:
sum=3,a=5
传递的参数值被函数体内的程序改变。
1.6.12 流程控制
C语言中可以用逻辑判断和循环来进行流程控制。
(1)if语句
if语句用于逻辑判断。可写成:
if(逻辑判断表达式) { 语句块; }
例如:
if(q!=1) { a=0; b=1; }
若逻辑判断存在多个分支,可以采用if-else结构,如:
if(x<0) { y = x/50; } else(x>=0 && x<3) { x=-x; y = x/25; } else { x= 0; y = 0; }
(2)for循环
for语句常用于循环表达。
int i,j,n=10; for(i=1;i<n;i++) { j = i*i; printf(“%d%d\n”,i,j) }
除此以外,C语言中还包含while、do…while循环,以及switch开关判断等流程控制。关于此方面更详细内容可参阅专业的C语言类图书。
1.6.13 操作符
(1)常用的代数操作符(表1-1)
表1-1 常用的代数操作符

(2)常用的逻辑操作符(表1-2)
表1-2 常用的逻辑操作符

1.6.14 C语言库函数
C语言中包含了一些常用的库函数,这些函数可以在UDF中直接调用。
(1)常用的三角函数
double acos (double x); double asin (double x); double atan (double x); double atan2 (double x, double y); double cos (double x); double sin (double x); double tan (double x); double cosh (double x); double sinh (double x); double tanh (double x);
(2)常用的数学函数
double sqrt (double x); double pow(double x, double y); double exp (double x); double log (double x); double log10 (double x); double fabs (double x); double ceil (double x); double floor (double x);
(3)常用的标准输入输出函数
FILE *fopen(char *filename, char *mode); int fclose(FILE *fp); int printf(char *format,...); int fprintf(FILE *fp, const char *format,...); int fscanf(FILE *fp, char *format,...);
1.6.15 预处理命令
在UDF的各种头文件中(文件路径D:\Program Files\ANSYS Inc\v180\fluent\fluent18.0.0\src),存在各种以#开头的语句,如图1-3所示。
#ifndef _FLUENT_UDF_H
#define _FLUENT_UDF_H
#ifdef __cplusplus
extern ”C” {
#endif
#define _UDF 1
#define _CRT_SECURE_NO_DEPRECATE 1
#define _CRT_NONSTDC_NO_DEPRECATE 1
#ifdef UDFCONFIG_H
# include UDFCONFIG_H
#endif
#include ”global.h”
图1-3 头文件示例
这些以#开头的语句就是C语言的预处理命令。
C语言的预处理工作由一个预处理程序来完成。任何C系统都有一个预处理程序,负责处理源程序中的所有预处理命令,从而生成不含预处理命令的源程序。C语言的预处理目的是为了方便编程。
预处理命令以独立的预处理命令行的形式出现在源程序中,# 是其特殊的引导符号。如果源程序中某一行的第一个非空格符号是#,这就是一个预处理命令行。预处理命令的作用是要求预处理程序完成一些操作。
(1)文件包含命令
文件包含命令是以#include开始的行,其作用是把特定文件的内容复制到当前源文件中。其存在两种形式如下:
# include <文件名> # include ”文件名”
两者的差异在于文件搜索方式的不同。
第一种形式,预处理程序直接到系统指定的某些目录中去查找所需文件,目录指定方式由具体系统确定,通常指定几个系统目录。
第二种形式,预处理程序先在源文件所在目录中查找,若没找到文件,则再到系统指定的目录中去查找。
文件包含命令的处理过程:首先查找所需文件,找到后就用该文件的内容取代这个包含命令行。替换进来的文件中若有预处理命令,也将被处理。
(2)宏定义和宏替换
以#define开始的行称为宏定义命令行。宏定义包含两种形式:简单宏定义;带参数的宏定义。
① 简单宏定义
简单宏定义的形式为:
#define 宏名字 替代文本
其中宏名字是任意标识符,替代文本可以是任意一段正文,其中可以包括程序中能出现的任何字符(包括空格等),一直延续到本行结束。如果需要写多行的替代文本,可以在行末写一个反斜杠\,这将使下一行内容继续被当作替代文本。
宏定义的作用就是为宏名字定义替代。
如果一个宏名字的替代文本是数值或可以静态求值的表达式,当这个宏名字在程序某处出现,就相当于在那里写了这个数值或表达式。
例如,如果进行了如下定义:
#define SLD static long double
此后,宏名字SLD就代表static long double。若程序中出现:
SLD x=2.4, y=9.16;
经过预处理后,源代码被翻译为:
static long double x=2.4,y=9.16;
预处理并不检查宏定义中的替代文本是否为合法的C语言结构,也不检查替换之后的结果是否为正确的C语言程序段,其只是简单地完成文本替换工作。
② 带参数的宏定义
带参数的宏定义形式为:
#define 宏名字(参数列表)替代正文
使用带参数的宏定义时,不但要给出宏的名字,还要用类似函数实参的形式给出各宏参数的替代段,多个替代段之间用逗号分隔。这种形式也成为一个宏调用。
对宏调用的替换分两部分进行:首先用替代代码段填充宏参数,然后将替换的结果(展开后的替代正文)代入到主程序中实现程序代码的替换。
例如,定义求两个数据中较小数,可定义宏:
#define min(A,B) ((A)<(B)?(A):(B))
若程序中出现如下语句:
z = min(x+y,x*y)
则宏展开后为:
z = ((x+y)<(x*y)?(x+y):(x*y));
带参数的宏定义与函数看起来很类似,但实际上有很大的不同。切记宏定义只是简单的文本替换。
(3)条件编译命令
条件编译的作用是在源程序中划出一些片段,使预处理程序可根据条件保留或丢掉一段,或从几段中选择一段保留。实现条件编译的预处理命令有四个,分别是:
#if #else #elif #endif
其中,#if和#elif命令以一个能静态求出整型值的表达式为参数,另外两个没有参数。条件编译命令的常见使用形式有三种。
① 形式一
#if 整型表达式 …… /*代码片段,条件成立时保留*/ #endif
② 形式二
#if 整型表达式 …… /*条件成立时保留*/ #else …… /*条件不成立时保留*/ #endif
③ 形式三
#if 整型表达式 …… /*条件成立时保留*/ #elif 整型表达式 …… /*elif部分,可以有多个*/ #elif 整型表达式 …… #else …… /*条件都不成立时保留*/ #endif
其中整型表达式是预处理条件,值为0表示条件不成立,否则条件成立。这里常用==、!=等做判断,例如判断宏定义的符号是不是等于某个值等。
为了方便,C语言提供了一个特殊谓词define,其使用形式有两种:
define 标识符 define (标识符)
当标识符是有定义的宏名字时,define(标识符)将得到1,否则得到0。这种表达式常被作为条件编译的条件。此外还有两个预处理命令#ifdef和ifndef,它们相当于#if和#define混合的简写形式。
#ifdef 标识符 /*相当于#if define(标识符)*/ #ifndef 标识符 /*相当于#if !define(标识符)*/