代码之家  ›  专栏  ›  技术社区  ›  Cheeso

C支持使用静态局部变量吗?

  •  20
  • Cheeso  · 技术社区  · 14 年前

    相关: How do I create a static local variable in Java?


    对不起,如果这是一个复制品,我很肯定这会被问到之前,我看了但没有找到一个复制品。

    我是否可以在C中创建静态局部变量?如果是,如何?

    我有一个很少使用的静态私有方法。静态方法使用一个正则表达式,我想初始化它 一旦 只有在必要的时候。

    在C语言中,我可以用一个局部静态变量来实现这一点。我能用C语言做这个吗?

    当我试图编译此代码时:

        private static string AppendCopyToFileName(string f)
        {
            static System.Text.RegularExpressions.Regex re =
                new System.Text.RegularExpressions.Regex("\\(copy (\\d+)\\)$");
        }
    

    …它给了我一个错误:

    错误CS0106:修饰符“static”对此项无效


    如果没有局部静态变量,我想我可以通过创建一个小的新私有静态类,并将方法和变量(字段)插入到类中来近似我想要的内容。这样地:

    public class MyClass 
    {
        ...
        private static class Helper
        {
            private static readonly System.Text.RegularExpressions.Regex re =
                new System.Text.RegularExpressions.Regex("\\(copy (\\d+)\\)$");
    
            internal static string AppendCopyToFileName(string f)
            {
                // use re here...
            }
        }
    
        // example of using the helper
        private static void Foo() 
        {
           if (File.Exists(name)) 
           {
               // helper gets JIT'd first time through this code
               string newName = Helper.AppendCopyToFileName(name);
           }
        }
        ...
    }
    

    再考虑一下,使用这样一个助手类,会在效率上产生更大的净节省,因为除非必要,否则助手类不会被jit'd或加载。对吗?

    13 回复  |  直到 5 年前
        1
  •  11
  •   Henk Holterman    6 年前

    不,C不支持这一点。你可以接近:

    private static System.Text.RegularExpressions.Regex re =
             new System.Text.RegularExpressions.Regex("\\(copy (\\d+)\\)$");
    
    private static string AppendCopyToFileName(string f)
    {
    
    }
    

    这里唯一的区别是“re”的可见性。它不仅向方法公开,还向类M公开。

    这个 re 变量将在第一次以某种方式使用包含类时初始化。所以把这个放在一个专门的小班里。

        2
  •  5
  •   user151323    14 年前

    不幸的是,没有。我真的很喜欢C的这种可能性。

    我知道你能做什么。

    创建一个类,该类将提供对特定于实例的值的访问,这些值将静态地保留。

    像这样:

    class MyStaticInt
    {
        // Static storage
        private static Dictionary <string, int> staticData =
            new Dictionary <string, int> ();
    
        private string InstanceId
        {
            get
            {
                StackTrace st = new StackTrace ();
                StackFrame sf = st.GetFrame (2);
                MethodBase mb = sf.GetMethod ();
    
                return mb.DeclaringType.ToString () + "." + mb.Name;
            }
        }
    
        public int StaticValue
        {
            get { return staticData[InstanceId]; }
    
            set { staticData[InstanceId] = value; }
        }
    
        public MyStaticInt (int initializationValue)
        {
            if (!staticData.ContainsKey (InstanceId))
                staticData.Add (InstanceId, initializationValue);
        }
    }
    

    可以这样使用…

    class Program
    {
        static void Main (string[] args)
        {
            // Only one static variable is possible per Namespace.Class.Method scope
            MyStaticInt localStaticInt = new MyStaticInt (0);
    
            // Working with it
            localStaticInt.StaticValue = 5;
            int test = localStaticInt.StaticValue;
        }
    }
    

    这不是一个完美的解决方案,而是一个有趣的玩具。

    每个namespace.class.method作用域只能有一个此类静态变量。在属性方法中不起作用-它们都解析为相同的名称-get_instanceid。

        3
  •  5
  •   Drew Noakes    6 年前

    为什么不创建一个 static readonly 类中的成员并在静态构造函数中初始化它?

    这将为您提供相同的性能优势-它只会初始化一次。

        4
  •  4
  •   Ondřej    8 年前

    不在C_中,仅在Visual Basic.NET中:

    Sub DoSomething()
      Static obj As Object
      If obj Is Nothing Then obj = New Object
      Console.WriteLine(obj.ToString())
    End Sub  
    

    vb.net有很多C_没有的好东西,这就是我选择vb.net的原因。

        5
  •  2
  •   Cheeso    14 年前

    那么这个呢,因为您只希望它在使用时被初始化:

    private static System.Text.RegularExpressions.Regex myReg = null;
    public static void myMethod()
    {
        if (myReg == null)
            myReg = new Regex("\\(copy (\\d+)\\)$");
    }
    
        6
  •  1
  •   Nick Alexeev    13 年前

    C不支持静态局部变量。除了上面发布的内容外,这里还有一个链接指向有关此主题的msdn博客条目:
    http://blogs.msdn.com/b/csharpfaq/archive/2004/05/11/why-doesn-t-c-support-static-method-variables.aspx

        7
  •  1
  •   dathompson    10 年前

    按照Henk和Barretj的回答,我认为您可以避免初始化成本,通过使用一个属性更接近于此。

    private Regex myReg = null;
    private Regex MyReg
    {
        get {
            if (myReg == null)
                myReg = new Regex("\\(copy (\\d+)\\)$");
            return myReg;
        }
    }
    

    然后只要在代码中的任何地方使用myleg(注意myleg中的大写字母“m”)。这个解决方案的好处在于(尽管getter是一个引擎盖下的函数调用),属性的语义意味着您可以像myleg是一个变量一样编写代码。

    上面是我如何设置“运行时常量”,这些常量需要在运行时进行一次性初始化。

    我也使用可以为空的类型做同样的事情。例如,

    private bool? _BoolVar = null;
    private bool BoolVar
    {
        get {
            if (_BoolVar.HasValue)
                return (bool)_BoolVar;
            _BoolVar = /* your initialization code goes here */;
            return (bool)_BoolVar;
        }
    }
    

    然后在代码中像普通的bool一样使用boolvar。我不使用内部的boolvar(boolvar属性的后备存储),因为我不需要,记住这就像一个运行时常量,所以没有setter。但是,如果出于某种原因需要更改运行时常量的值,我将直接在可以为空的变量boolvar上进行更改。

    初始化可能相当复杂。但它只执行一次,而且只在第一次访问该属性时执行。您可以选择通过将boolvar设置回空值来强制重新初始化运行时常量值。

        8
  •  0
  •   Thomas    14 年前

    当然。只需在方法外部声明私有静态变量即可。

        private static readonly System.Text.RegularExpressions.Regex re = new System.Text.RegularExpressions.Regex( "\\(copy (\\d+)\\)$" );
        private static string AppendCopyToFileName( string f )
        {
            //do stuff.
        }
    

    这实际上就是您所做的,唯一的区别是“re”对整个类具有可见性,而不仅仅是方法。

        9
  •  0
  •   Iddillian    11 年前

    我还没有看到一个好的通用解决方案,所以我想我会想出自己的。不过,我要注意的是,在大多数情况下(并非总是如此),需要静态局部变量可能是一种迹象,表明您应该根据许多人所说的原因重构代码;状态是对象的东西,而不是方法。但我确实喜欢限制变量范围的想法。

    无需进一步说明:

    public class StaticLocalVariable<T>
    {
        private static Dictionary<int, T> s_GlobalStates = new Dictionary<int, T>();
    
        private int m_StateKey;
    
        public StaticLocalVariable()
        {
            Initialize(default(T));
        }
    
        public StaticLocalVariable( T value )
        {
            Initialize(value);
        }        
    
        private void Initialize( T value )
        {
            m_StateKey = new StackTrace(false).GetFrame(2).GetNativeOffset();
    
            if (!s_GlobalStates.ContainsKey(m_StateKey))
            {                
                s_GlobalStates.Add(m_StateKey, value);
            }
        }
    
        public T Value
        {
            set { s_GlobalStates[m_StateKey] = value; }
            get { return s_GlobalStates[m_StateKey]; }
        }
    }
    

    当然,这不是线程安全的,但这样做不需要太多的工作。它可以这样使用:

    static void Main( string[] args )
    {
        Console.WriteLine("First Call:");
        Test();
        Console.WriteLine("");
        Console.WriteLine("Second Call:");
        Test();
        Console.ReadLine();
    }
    
    public static void Test()
    {
        StaticLocalVariable<int> intTest1 = new StaticLocalVariable<int>(0);
        StaticLocalVariable<int> intTest2 = new StaticLocalVariable<int>(1);
        StaticLocalVariable<double> doubleTest1 = new StaticLocalVariable<double>(2.1);
        StaticLocalVariable<double> doubleTest2 = new StaticLocalVariable<double>();
    
        Console.WriteLine("Values upon entering Method: ");
        Console.WriteLine("    intTest1 Value: " + intTest1.Value);
        Console.WriteLine("    intTest2 Value: " + intTest2.Value);
        Console.WriteLine("    doubleTest1 Value: " + doubleTest1.Value);
        Console.WriteLine("    doubleTest2 Value: " + doubleTest2.Value);
    
        ++intTest1.Value;
        intTest2.Value *= 3;
        doubleTest1.Value += 3.14;
        doubleTest2.Value += 4.5;
    
        Console.WriteLine("After messing with values: ");
        Console.WriteLine("    intTest1 Value: " + intTest1.Value);
        Console.WriteLine("    intTest1 Value: " + intTest2.Value);
        Console.WriteLine("    doubleTest1 Value: " + doubleTest1.Value);
        Console.WriteLine("    doubleTest2 Value: " + doubleTest2.Value);            
    }
    
    
    // Output:
    // First Call:
    // Values upon entering Method:
    //     intTest1 Value: 0
    //     intTest2 Value: 1
    //     doubleTest1 Value: 2.1
    //     doubleTest2 Value: 0
    // After messing with values:
    //     intTest1 Value: 1
    //     intTest1 Value: 3
    //     doubleTest1 Value: 5.24
    //     doubleTest2 Value: 4.5
    
    // Second Call:
    // Values upon entering Method:
    //     intTest1 Value: 1
    //     intTest2 Value: 3
    //     doubleTest1 Value: 5.24
    //     doubleTest2 Value: 4.5
    // After messing with values:
    //     intTest1 Value: 2
    //     intTest1 Value: 9
    //     doubleTest1 Value: 8.38
    //     doubleTest2 Value: 9
    
        10
  •  0
  •   nawfal Donny V.    10 年前

    在内部类中嵌套相关的成员,如您在问题中所示,这可能是最干净的。如果静态变量能够以某种方式获取调用方信息,则不需要将父方法推送到内部类中。

    public class MyClass 
    {
        ...
        class Helper
        {
            static Regex re = new Regex("\\(copy (\\d+)\\)$");
            string caller;
    
            internal Helper([CallerMemberName] string caller = null)
            {
                this.caller = caller;
            }
    
            internal Regex Re
            {
                //can avoid hard coding
                get
                {
                    return caller == "AppendCopyToFileName" ? re : null;
                }
                set
                {
                    if (caller == "AppendCopyToFileName")
                        re = value;
                }
            }
        }
    
    
        private static string AppendCopyToFileName(string f)
        {
            var re = new Helper().Re; //get
            new Helper().Re = ...; //set
        }
    
    
        private static void Foo() 
        {
            var re = new Helper().Re; //gets null
            new Helper().Re = ...; //set makes no difference
        }
    }
    
    1. 可以使用一些表达式树技巧避免在属性中对方法名进行硬编码。

    2. 您可以避免helper构造函数并使属性为静态的,但是您需要通过使用在属性中获取调用者信息 StackTrace .

    最后,总有 const 在一个方法内部是可能的,但在第一个方法中,它不是变量,只允许使用两个编译时常量。只是说说而已。

        11
  •  0
  •   AlfredBr    9 年前

    三年后…

    您可以用捕获的局部变量来近似它。

     class MyNose
        {
            private static void Main()
            {
                var myNose= new MyNose();
                var nosePicker = myNose.CreatePicker();
    
                var x = nosePicker();
                var y = nosePicker();
                var z = nosePicker();
            }
    
            public Func<int> CreatePicker()
            {
                int boog = 0;
    
                return () => boog++;
            }
        }
    
        12
  •  0
  •   buchWyrm    8 年前

    我开发了一个静态类,它以相当简单的方式处理这个问题:

    using System.Collections.Generic;
    using System.Runtime.CompilerServices;
    
    public static class StaticLocal<T>
    {
        static StaticLocal()
        {
            dictionary = new Dictionary<int, Dictionary<string, Access>>();
        }
    
        public class Access
        {
            public T Value { get; set; }
    
            public Access(T value)
            {
                Value = value;
            }
    
        }
    
        public static Access Init(T value, [CallerFilePath]string callingFile = "",
                                           [CallerMemberName]string callingMethod = "",
                                           [CallerLineNumber]int lineNumber = -1)
        {
            var secondKey = callingFile + '.' + callingMethod;
            if (!dictionary.ContainsKey(lineNumber))
                dictionary.Add(lineNumber, new Dictionary<string, Access>());
            if (!dictionary[lineNumber].ContainsKey(secondKey))
                dictionary[lineNumber].Add(secondKey, new Access(value));
            return dictionary[lineNumber][secondKey];
        }
    
        private static Dictionary<int, Dictionary<string, Access>> dictionary;
    
    }
    

    它可以在如下方法中实现:

    var myVar = StaticLocal<int>.Init(1);
    Console.Writeline(++myVar.Value);
    

    在对方法的每个后续调用中,myvar.value中包含的值将是最后一个被设置为的值,因此重复调用将导致它输出一个自然数序列。init()函数仅在以前未初始化的情况下设置该值。否则,它只返回对包含该值的对象的引用。

    它使用[CallerFieldPath]、[CallerMemberName]和[CallerLinEnumber]属性跟踪字典中引用的项。这样就消除了具有相同名称的方法或来自相同行号的调用之间发生冲突的可能性。

    关于其使用的一些注意事项:

    • 正如其他人所说,有必要考虑您所做的工作是否真的需要使用静态局部变量。它们的使用有时可能是您的设计有缺陷的迹象,并可能使用一些重构。
    • 这种处理问题的方法涉及到几个间接层,从而减慢了程序的执行速度。只有在合理的成本下才能使用。
    • 静态局部变量可以帮助您处理类中声明的成员过多的问题,从而在使用它们的地方对它们进行划分。这应该与执行时间成本进行权衡,但有时是值得的。另一方面,在一个类中声明如此多的成员可能表明设计问题值得考虑。
    • 因为这些值在其方法完成执行后继续保留在内存中,所以必须注意,使用它们存储大的内存块将阻止垃圾收集,直到程序完成,从而减少可用资源。

    对于大多数希望使用静态局部变量的实例来说,这种方法可能是多余的。它使用间接方法来处理单独的文件、方法和行对于您的项目来说可能是不必要的,在这种情况下,您可以简化它以满足您的需要。

        13
  •  0
  •   nrofis    5 年前

    它是复制自 Why doesn't C# support local static variables like C does?

    不过,我想你会发现我的 answer 有用。