C++ 类级别数据
🧑💻 User
In this lecture, we’re going to see how we can set up static member variables in our C plus plus program.We have seen that static member variables are variables that are not really attached to any class object.They are attached or tied to the class blueprint itself.And we have seen that they are really useful in that.For example, they can allow you to keep track of how many objects for your class are created.Let’s see how we can create them.Here is a simple example.We have our class point.And we have a few member variables inside, but the member variable of interest here is the point countmember variable here it’s type is size T, but notice that we have marked it as static here.What this is going to do, it’s going to tie this member variable to the class blueprint itself.The member variable is not going to be tied to any point object, but any point object is going to haveaccess to this member variable, but the lifetime of the member variable will be tied to the class blueprintitself.In fact, static member variables are even initialized before you create any object for the class.This may sound counterintuitive, but I am going to show you this in a minute when we head to VisualStudio code.For now, just know the syntax of our static member variable.The syntax is thus you prepend the static keyword to the declaration of your member variable.And from that point on it’s going to be a static member variable.Now you see that we haven’t really initialized our member variable here, so now it’s probably goingto contain some junk and we don’t want to do this.But Cplusplus doesn’t allow initializing static member variables like this in the header file.So if we use a pair of curly braces after this and use brace initialization, we’re going to get a compilererror.What c plus plus allows is to go in some CPP file and initialize this member variable and the linkerknows to go and hunt for this initialization.In any translation unit that you have in your application.The syntax is really simple.You say the class that the member variable belongs to using this scope resolution.Operator And then you say the variable name.After this, you can then use curly braces to initialize your member variable to whatever it is youwant.This is going to set up our static member variable and we can use it in our C plus plus program.Again, note that static member variables are initialized before your program runs.In fact, they are even initialized before the main function gets to run because they are special inthat way they are not tied to any object, so they need to be initialized before any object is createdin C plus plus.And we’re going to try and prove that when we hit Visual Studio code in a minute.But before we go, I want to introduce you to a new function or member function we’re going to introducein our class point.It is going to be a function to compute the length of a line, and a line is going to be made up oftwo points.So we’re going to use this formula you see down here and we’re going to take the X position for thesecond point and minus the X position for the first point, we’re going to elevate that to the powerof two here and we’re going to do the same computation on Y, and then we’re going to do a square rootof the result that we get inside here.So this is how we can compute the distance between two points.And we’re going to do that in our C plus plus program.This is one way I thought of we could make our points a little bit interesting because what we havebeen so far can really be boring knowing what we know about C plus plus.Now we want to make things a little interesting.Okay, so this is a possible representation of two points.We have the origin point, which is at the address zero zero, and then we have a point, for example,which lives at an X position of ten and the Y position of five.Suppose this point here is at X of ten, and the point here is at Y of five.So if we go like this, this is going to be the coordinate of our point.But what we want to do is to compute the distance between the two points.So we want to do something like this.And now the distance between this point here and this point here.And the formula to do that is what we see here in this point.What we’re going to do is treat the ten here as our x two.And the zero here is going to be our x one.Then the five is going to be our Y two, and the zero here is going to be our Y one.Then we’re going to do the computation and what we will get is going to be the distance between thesetwo points.This is a formula you can use and the translating this in C plus plus we can do something like this.So we’re going to use the square root function we have in the C plus plus standard library.So this is basically going to be our square root.And then we’re going to take the X position for the second point and minus the X position for the firstpoint here.And then we’re going to elevate that to the power of two.We’re going to do the.Same computation for Y.And that’s what we have here.And this is going to give us the distance between two points.And we can do this in our C plus plus program.So now that we know this, let’s head over to Visual Studio code and play with static member variables.Okay.So here we are in our working folder.The current project is static member variables.We’re going to grab our template files pretty quick.We are going to put them in place and we are going to open this in Visual Studio code by dragging anddropping like we always do here.This is going to open our folder.We’re going to open the Main.cpp file and we’re going to clean it up a little bit.Let’s do that.We’re going to create our files for the point class that we are going to use here.We’re going to put in a header file point dot h, and we’re going to put in a CPP file point dot CPP.We are going to go in our header file and put in our class.I’m going to put it in because I don’t want to watch me type all these things.We have our include guards on top and we have the end of our include guards and inside we have a bunchof constructors.In here we see a two parameter constructor.We have a one parameter constructor that we can use to construct a point which has the same x and Ycoordinates.We have a default constructor and we have a copy constructor.We also have a destructor which is really not going to do anything because we don’t have any pointermember variables in here.But if you happen to have any pointer member variables and you are doing some dynamic memory allocationin the constructors, you’re going to use this destructor here, but we’re going to put it in here justto be complete.So down here we have the meat of this example.Here we have a static member variable.This is going to be a member variable whose association is going to be with the class itself.It’s not going to be associated with any class object, but we’re going to prove this in a minute.For now, let’s head over in point CPP and try to implement many of these methods.I think we can actually grab them all.Let’s do that.We’re going to grab them all here and do them all one by one.And I would challenge you to try and implement these things yourself without watching anything becauseyou already have the tools to do that.So we’re going to include point dot H, our header, and then we’re going to implement this constructorhere.So we’re going to put in our scope resolution operator and we’re going to do our initializer list.Our member variables are called M, X and M Y.So we’re going to do that.We’re going to say M x is going to be initialized with X.Let’s use brace initialization here and m y is going to be initialized with Y.Let’s do that.And once we have this, we can jump into the body of our constructor here and it is already done.It is initializing our member variables.Let’s do the second one.So we’re going to put in our scope resolution.Operator And for this constructor we are going to delegate the construction to the first constructorwe did here because we don’t want to repeat ourselves.So what we’re going to do is delegate to the other constructor and we’re going to give it x y chordin the position for the x coordinate.And this is going to do what we want, and then we’re going to implement this constructor here.This constructor is going to be initializing our member variables.The member variables are called M, X and Y.So let’s go down here and put in our scope resolution.Operator.We’re going to say point, and then we’re going to use an initializer list to initialize our membervariables.We’re going to say m X, we’re going to use braced initialization here.So we’re going to say X and we’re going to say M Y and put in our initializer to be Y Here, it’s notX and then we’re going to put in our body and our constructor here is basically done.Now the remaining constructors here will be delegating the construction work to the constructor thatwe just did here.This is going to avoid us to do the same job in the body.This constructor here is going to take over that heavy lifting and the others are just going to be usingit.The way we do that, we’re going to use constructor delegation.You already know that.So let’s do that here and we’re going to delegate to that constructor.We’re going to say point and then we’re going to pass the X and Y coordinate.This constructor.Here is a single parameter constructor which is going to pass the same value for X and Y.So we’re going to use our parameter here.We’re going to say x y chord for the Y coordinate and then we’re going to go in our body.This is not autocompleting because we didn’t say that this constructor is from the point class we forgot.Our scope resolution operator.Let’s do that pretty quick.And all these squiggly lines should go away in a minute.Let’s wait.Okay.They’re not going away.But if there’s a problem, we’re going to figure this out.We’re going to do the other constructor here, which is a default constructor.We’re going to put in our scope resolution operator, and we’re going to grab the comment here and putthat on top.I think this is going to do let’s align this properly and we’re going to put our initializer.It is going to be delegating to the other constructor and we’re going to put in default parameters wewant to use.We’re going to use zero zero.Let’s do that 0.0 because it’s a double and then we’re going to do the body.Okay, so this constructor here is done.Next we’re going to do our copy constructor, which is going to be doing member Wise copy.It’s not going to do anything special because we don’t have any pointer member variables.So this is going to be basically the same thing as the default constructor that is generated by thecompiler.But we want to do it ourselves.So let’s delegate and we’re going to say point M x and we should use brace initialization.Here I am mixing things up, so we’re going to say point M X and then we’re going to say point M y andwe’re going to put in our body.This should do what we want and we need the scope resolution operator here.Let’s do that.And the destructor is really not going to do anything.So we’re going to put our scope resolution operator and we’re just going to put a body.Let’s do that.We’re going to take out this comment, put that on top a little bit, and we’re going to align hereand we’re going to put the body in here.And the remaining function here is going to compute the distance to the origin.And we have seen the formula we can use to do that.So let’s go down and put in our body.And this function is going to belong to the point class.We’re going to tell that to the compiler using our scope resolution operator and we’re going to moveour return value before the scope resolution.Operator Let’s be careful about this.We are going to go on top and include the math library from the C plus plus standard library.We’re going to grab Cmath and then we’re going to go down and put in the body for our function here.And it’s basically going to return the distance between the current object, which is the current pointto the origin.Again, if you have forgotten, this is the formula we use to compute the distance between points.Our point is going to be this point here and we’re going to be computing the distance to the originand we’re going to get to that as a double value.Okay, so by now our functions should be all implemented.And what we can do is try and build this program and see if we don’t have any problems.Let’s use GCC for that.We’re going to build with GCC and we’re going to have a problem and it’s going to say x, y not declaredin this scope point CPP at line nine.Let’s go there and do that x, y, underscore code.This is the problem here.We’re going to fix this and then we’re going to try and build again.Now the build is good, our class is working and we can create objects of this class and really seethat the memory locations for the member variables are different from different objects.So to really see this, we’re going to put in a print function, we’re going to make it public and we’regoing to say it’s going to be const because it’s not going to modify this class here or the member variablesfrom this class.And we’re going to say Stdcout point and we’re going to say address of X, That’s what we want to printhere.And we’re going to put a comma here and say address.That’s what we want to see and we are going to print this out.We’re going to say address of M y, and we’re going to put our closing square bracket.I think we should put one here.We don’t need a colon here.I think this is going to be better.And then we’re going to put our new line character.Now we can head over to Main and really play with this.We’re going to include our point class and we’re going to go down and create two points.We’re going to say point P one and we’re going to put in two values ten and 20.Why not?And we’re going to say point P two and we’re going to say 21.8 and 40.6.Why not?And then we’re going to say P one print info, we’re going to print the information and P two printable.Okay, So let’s build.What will we see when we print this information?We’re going to see the addresses for the X and Y member variables for this.Objects.And what we want to prove is that each object is going to have its own member variables and they aregoing to be completely independent of other objects.So let’s build.We’re going to build with GCC.And if we go up, you see that the build is good.Now we can clear and run Rooster.If we do that, you’re going to see that.Okay, so you see the output is saying zero for the x member variable for the first point and it issaying D8 for the second member variable of the first point.If we go to the second point, you see that we have DC zero and DC eight.You see the addresses are different.So the member variables are completely different because they live in different memory locations.So this is working fine.If you want variables to be constrained to objects, the default way we have to set up a member variablesis going to work.But if you want member variables to be associated with the class itself, you’re going to need to dosomething else.And to be able to achieve that, we will have to mark our member variables as static.So we have a member variable which is static here.Its type is size T, the name of the variable is M point count.So once you have it declared like this, the variable is not going to be properly initialized.If you want to initialize it, you will have to do it in a CPP file because if you try to do that inthe class definition like this, we’re going to get a compiler error.C plus plus doesn’t allow this.Okay, So you see, we do this, we get a squiggly line.If we try to build with GCC, let’s do that.We’re going to get a compiler error and the compiler error is saying in class initialization of non-conststatic member point here is not allowed.This is what the error is saying here.We’re not allowed to do this.If we want to initialize our static member variable, we will need to do this in the CPP file and wecan really do it in any CPP file.But we have pointed at CPP here, so we can just do that here.We’re going to say initialize static member var and the way you do that, you say the class where themember variable belongs and then you’re going to say the name of the member variable and then you’regoing to initialize it with whatever you want to initialize that with.Let’s say we want this to be zero by default and we’re going to put a semicolon here and in front weneed to say the type of this thing.So it is of size t type and this is the syntax we use to initialize static member variables.If we do this, this is going to be properly initialized.Now we have our static member variable.It is initialized in a CPP file.How do we use it in our C plus plus program?Well, because this guy is public, we can really use a convenient syntax to access this.We can even access this before any object is created for a point.So let’s go back in the main CPP file and really try to do this.We have already used this point.I don’t think we need them anymore, so we’re going to take them out and we’re going to say print thepoint count in our program and we are going to say Stdcout, like we say always and we’re going to saypoint count and notice the syntax we’re going to use.We’re going to say point and then end point count.This is going to give us direct access to our static member variable.And this is really cool.Notice that we are able to do this before any point object is created.And this is really cool.This proves that the static member variable is initialized even before the main function runs becausethis is what C plus plus does.We are able to get to the point count before we even create any point object.Notice that no constructor has been called.We didn’t set up any point object in Main.We just printed the point count and we’re going to see that if we print this, we’re going to get zerobecause that’s what we initialized our static variable with and this is really cool.So let’s try and build our program.We’re going to use GCC to do that.You see build finished successfully.So this is cool.We can clear and run.Rooster point count is zero.We are able to access our static member variable and it is attached to the class itself.It’s not attached to any member variable.Now let’s try and change what we initialize this guy with so that you really believe that this is goingto come here.The value we print, we’re going to build again, let’s use GCC and the build is good.We’re going to clear.If we run Rooster, we’re going to see the value we initialized our static variable with and this iswhat static member variables are really about.They are variables that are attached to the class itself and they are not attached to any object ofyour class.Now let’s use this member variable to really do some cool.We are going to go in our heavy lifter constructor and any point and after we create any point object,we’re going to increment our static variable to properly keep count of how many point objects we havein our program.So we’re going to increment and point count, and whenever the destructor runs, our point object willdie.So we’re going to decrement the point count.So we’re going to go in our destructor and we’re going to minus, minus M point out.And now we have the option and now we have the capability to really get how many points we have createdin our programs.We can print the point count here.It’s going to be zero because we don’t have any point objects created.But now we can create a new point object.Let’s put in ten and 20, and if we print the point count again, we’re going to see that we’re goingto get a one.Let’s do that.We’re going to build with GCC.The world is good, so we can clear and run.Rooster.You see point count 33.Why do we have 33 here?We need to go back and change what we initialize our point count with.So we’re going to initialize this to zero and then we’re going to run.Why aren’t we incrementing here when we call our constructor here?Let’s say point constructor called here so that we know that the constructor is being called and we’regoing to pass double parameters here.Let’s make sure we pass what the compiler expects.So we’re going to pass 20.4 here, for example, and then we’re going to build with GCC.The build is going to be good.We can clear and run Rooster.Now we see that the point count is zero.Why is this?After we create a point here, we’re going to increment point count and we should get a one.After we do this, we can print the point count here and we’re going to get zero because at this pointwe don’t have any point objects created.But we can create a point here.We’re going to say point P one and we’re going to put in 10.0, for example, and a 20 point.And if we do this and print the point count again, let’s copy this and we’re going to put this downand we’re going to make this a semicolon, not a colon.And if we try to build, we’re going to see that here.We’re going to get the zero run.But the moment we hit this point here, we’re going to initialize, but the moment we hit line ninehere, we’re going to create a new point object.This is going to call our constructor here and it is going to increment M point count.If we print this, we’re going to get a one.Let’s be sure that our constructor is ridiculed.So we’re going to say point constructor calls and this is the constructor that is going to be calledregardless of how we Build our object, because all the other constructors are delegating the work toit.You notice this is what we are calling for.Any constructor that we have through constructor delegation and this is really cool.So if we print here, we’re going to get a zero.And if we print here, we’re going to get a one.This is what we expect.Let’s build and we’re going to build just fine.The build is good.We can clear and run Rooster and we’re going to see that for the first time.The count is zero.Now, point is one, and this is really cool.We can set up an array of points.We can do that.Let’s say, for example, zero point and we’re going to say point and we’re going to put in a pair ofsquare brackets and then we’re going to initialize this with a pair of points.Let’s put in, for example, one one.We can really do whatever we want with this.We’re going to put a point here which is going to call the default constructor.Notice that we are building these guys in place.You can really do this if you want.And then we’re going to put another point, which is going to call the one parameter constructor andwe’re going to put in four.Why not?Now?If we print the count for points, we’re going to get a good number for points.We have P one here, which is our first point.We have two points, three points and four points.So now if we print, we expect to get a four printed out and hopefully this is going to prove that weare really keeping track of the points that we create in our C plus plus program through the help ofour static member variable, which is really not attached to any object we create in the main functionhere.It’s just attached to the class itself.So we’re going to build, let’s use GCC to do that.The world is going to be good.We can clear and run, Rooster.Now you see that we have a few constructors called four times, and each of these constructor callsis going to increment count by one.And when all these points are created, we’re going to have four points in total.And this is really cool.Okay.Now that we have already played with our static variable, I wanted to make it super clear that it ispossible to really initialize this static variable in any translation unit or in any CPP file to beexact.Let’s go on the left here and create another CPP file.So we’re going to head to the Add new file button here and we’re going to say some other file and we’regoing to CPP.This is going to be a CPP file and it is in the wrong folder.So I think we can grab this and bring it out.Let’s see if this works and we have the file here and we can grab the initialization of our static variableand really move that there.So we’re going to save this, but to make this work, we need to include the point class, because atthis point, this line here is not going to make sense to the compiler because the compiler is not goingto know what the point is.So we’re going to include the point here.And at the moment we do this now the compiler is going to know what pointers and it is going to initializethe static variable that is declared in our class here.So notice that we don’t have this initialization in point dot CPP anymore, but if we build, this isgoing to build just fine.Let’s build with GCC.You’re going to see that the world is good and we can clear and run rooster and we’re going to get theoutput that we expect.Another thing I want to be super clear is that a static variable doesn’t belong to any object and wecan really modify our static variables if we want in any cast member functions.So this is going to be counterintuitive, but note that it is possible to modify the static variable.Remember the const keyword after a member function is going to prevent you from modifying any membervariable in the class or in the object because member variables are associated with objects.But a static variable is not associated to any object.It belongs to the class as a whole.So the const keyword in your member function is not really going to protect it.So to prove this, we’re going to go in our const member function here and we are, for example, goingto increment M point count, you’re going to see that this is going to build and some of you are goingto say, What are you doing here?You are trying to modify a member variable, but this member variable doesn’t belong to any point object.It belongs to the entire class as a whole.The const keyword here is going to prevent you from modifying any member variable that belongs to theobject.And when I say object, I mean object like P1 here.So if we go down here and for example, say 0.1, get point count, just calling the function here,we’re going to head to the body of this function.We’re going to increment and then we’re going to return.If we try to print what we return, let’s say size t result and assign that to and give that the valuewe get from our function call here.And if we try to print that out, we’re going to get a five because we have already changed our membervariable through the call to this function here, get point count.I really hope you understand this.We’re going to build.And the world should be good.And if we print, the result should be five and it is a five here.And if we print the point, count through the syntax we used here, let’s do that.We’re going to put this here.We’re also going to get a five because this is the same variable.Let’s do that.We’re going to build with GCC.The world is going to be good.We can clear and run Rooster.Now we have a five here and this is really cool.Now, if you go back in our class, you’re going to notice that the static variable is in the publicscope.And I did this on purpose because I wanted the syntax.Here we have used saying point colon, colon and point count to access the static variable before anyobject is created.Work.I wanted this syntax here to work, but if you bring your static variable in the private scope, let’stry and take this out and put this back together with the other variables.We’re going to go back in main and we’re going to see that this is no longer working.The syntax here is only going to work if your static variable is living in the public scope.If you don’t have it in the public scope, you can access that through any function or member functionthat is returning that.Let’s take out this call here because you already know that you can modify static variables in castmember functions.So now we can have access to our variable through the member function here.If we want, we can do that.So let’s go back and really fix this.We don’t want this compiler errors, so we’re going to comment this out because at this point we don’thave any objects created.But the moment we have an object created, we can go through the object and get how many objects arecreated for that object.So here we’re going to say P one get point count, we’re going to call the function here or the memberfunction.We can take this out because we don’t need this anymore.And down here we’re going to say p one get point count and we’re going to print that for the first callhere.We’re going to get a one.We’re going to have a four here and we don’t need the second one here.So let’s take this out.If we do this, we’re going to get four printed out because we have four points created at this pointhere.We can copy this and go on top here and paste this in place.And at this point, we’re going to see a want to print it out because we only have one point created.Let’s build and really see this in action.We’re going to use GCC.The world should be good.We’re going to clear and run Rooster.Now we have zero point constructor called We have one point.We’re going to call the constructor three times because we have three points stored in our array hereand the total point count is going to be four as we see here.And we are going through the syntax here.So again, the message is if your static variable happens to be living in the private scope of yourclass, you’re not going to be able to use this syntax here to get access to your static variable.You have to go through some function to get it if you really want that.This is the message here.Okay.I think this is really all I had to share in this lecture.Raising your awareness about static variables.It should be clear that static variables or static member variables belong to the class as a whole.They don’t belong to any object of your class that you create.We have seen that we can use this syntax here to get access to static member variables if they happento live in the public scope of our class and if they live in the private scope, you have to go throughan object and use some member function that returns the static variable and that’s going to work.Also, we have seen that it is not possible to initialize your static member variable inside the class.If you try to do that, you’re going to get a compiler error.If you want to initialize that, you will have to go in some CPP file.And here and here we are doing that through a CPP file called some other file and the linker is goingto pick up this definition or initialization that we have here and it is going to work.This is really all I had to share.I hope you found this lecture interesting.We are going to stop here in this one.In the next one, we’re going to see a technique introduced in C plus plus 17 to allow us to initializeour static member variables inside the class declaration, if that’s what we want.So go ahead and finish up here and meet me there.
🤖 Assistant
好的,非常荣幸能为您讲解 C++ 中的静态成员变量(static member variables)。作为您学习 C++ 之旅的向导,我将尽力用最清晰、最易懂的方式来呈现这些知识。
C++ 静态成员变量:掌握类级别数据的精髓
摘要: 本课程将深入探讨 C++ 中静态成员变量的定义、用途、声明与初始化方法,以及如何访问它们。我们将通过详细的代码示例,理解静态成员变量与普通成员变量的区别,并揭示其在对象计数等场景中的强大作用。
1. 什么是静态成员变量?
在 C++ 中,我们通常会定义类(class)来创建对象(object)。每个对象都有自己独立的一套成员变量(member variables),就像每个学生都有自己的课本一样。但是,有时候我们需要一种特殊的变量,它不属于任何特定的对象,而是属于整个类本身。无论你创建了多少个该类的对象,甚至一个对象都没创建,这个变量都只有一个副本,并且所有对象共享这同一个副本。这种变量就是静态成员变量。
关键概念:
- 不依附于对象: 静态成员变量不存储在类的每个对象中。这意味着当你创建多个
Point
对象时,每个Point
对象都会有自己的m_x
和m_y
副本,但所有的Point
对象将共享同一个m_point_count
静态成员变量。 - 依附于类本身: 它们是“类级别”(class-level)的变量,你可以把它们看作是与类的蓝图(blueprint)本身紧密绑定的数据。
- 生命周期: 静态成员变量的生命周期与整个程序的生命周期相同。它们在
main
函数运行之前就被初始化,并在程序结束时才被销毁。 - 用途: 静态成员变量非常适合用于跟踪类中所有对象共享的信息,例如:
- 对象计数: 统计某个类有多少个对象被创建了,正如本例中
m_point_count
的作用。 - 共享配置: 存储所有对象都需要的共同配置或常量。
- 资源管理: 管理所有对象共享的唯一资源(例如,数据库连接池的大小)。
- 对象计数: 统计某个类有多少个对象被创建了,正如本例中
2. 如何声明静态成员变量?
声明静态成员变量非常简单,只需要在成员变量的类型前面加上 static
关键字即可。
语法:
1 | class ClassName { |
示例(Point.h
文件中):
1 | // Point.h |
解释:
在 Point.h
文件的 Point
类定义中,我们声明了一个名为 m_point_count
的 static size_t
类型变量。这个 static
关键字告诉编译器:m_point_count
不属于 Point
类的任何特定对象,而是属于 Point
类本身。
重要提示: 在类定义内部,你只能声明静态成员变量,而不能进行初始化(除非是 const static
整型或 C++17 inline
变量,这超出了本次课程范围)。如果你尝试在类定义内使用花括号 {}
或等号 =
进行初始化,编译器会报错。这是 C++ 的一个规则,因为类的定义通常放在头文件(header file)中,头文件可能被多个源文件(source file)包含,如果在头文件中初始化,可能会导致重复定义(multiple definitions)的问题。
3. 如何初始化静态成员变量?
由于静态成员变量不属于任何对象,它们的初始化方式也与普通成员变量不同。你必须在类定义之外,通常是在一个 .cpp
源文件中,为它进行定义和初始化。
语法:
1 | // 在某个 .cpp 文件中 |
示例(Point.cpp
文件中):
1 | // Point.cpp |
解释:
- 在
Point.cpp
文件中,我们写下了size_t Point::m_point_count = 0;
这行代码。size_t
:表示m_point_count
的类型。Point::
:这是作用域解析运算符(scope resolution operator),它告诉编译器m_point_count
是Point
类的成员。= 0;
:这是对m_point_count
进行的初始赋值。
- 初始化时机: 静态成员变量的初始化发生在
main
函数运行之前。这意味着即使你的程序没有创建任何Point
对象,m_point_count
也会被正确地初始化为0
。链接器(linker))会负责在编译(compilation)过程中找到这个唯一的初始化定义。 - 在构造函数和析构函数中更新: 为了实现对象计数,我们在
Point
类的所有构造函数中增加了++m_point_count;
,并在析构函数中增加了--m_point_count;
。这样,每次创建Point
对象时,计数就会增加;每次Point
对象被销毁时,计数就会减少。
4. 如何访问静态成员变量?
访问静态成员变量有两种主要方式:
a. 通过类名直接访问 (推荐且常见):
如果静态成员变量是 public
(公共)的,你可以直接使用类名和作用域解析运算符 ::
来访问它。这种方式最常用,也最能体现静态成员变量“属于类”的特性。
语法:
1 | ClassName::static_member_variable_name |
示例:
1 | // main.cpp |
b. 通过对象访问 (不推荐,但有时需要通过成员函数间接访问):
你也可以通过类的任何一个对象来访问静态成员变量(但如果静态变量是 private
的,你必须通过一个公共的成员函数来访问)。虽然语法上允许 object.static_member_variable_name
(如果 static_member_variable_name
是 public
),但这种方式容易让人误以为静态成员变量是某个特定对象的属性,从而造成误解。更推荐的做法是定义一个静态成员函数(static member function)来访问 private
静态成员变量。
静态成员函数:
- 静态成员函数也使用
static
关键字声明。 - 它们不接收隐式的
this
指针,因此它们不能访问非静态的成员变量或成员函数(因为非静态成员变量和函数是依附于对象的)。 - 它们主要用于操作静态成员变量或执行与类本身相关的操作。
示例(Point.h
中声明,Point.cpp
中实现):
1 | // Point.h |
在 main.cpp
中,我们就是通过 Point::get_point_count()
来访问 m_point_count
的,因为 m_point_count
被声明为 private
。
5. 静态成员变量与 const
成员函数
这是一个容易混淆的点。const
成员函数(const member function)的目的是保证不会修改调用它的对象的任何非静态成员变量。但是,const
成员函数可以修改静态成员变量。
原因:
因为静态成员变量不属于任何特定的对象。它们是类级别的,不构成对象状态的一部分。const
关键字是针对对象实例的常量性(constness),而静态成员变量是独立于对象实例的。
示例:
在 Point.h
中,我们声明了一个 const
成员函数 increment_static_count_via_const_func()
:
1 | // Point.h |
在 Point.cpp
中实现:
1 | // Point.cpp |
在 main.cpp
中调用:
1 | // main.cpp |
这证明了 const
成员函数可以修改静态成员变量。
6. 完整代码示例
为了让您更好地理解,我们将上面所有的代码片段组合成一个完整的项目。
Point.h
:
1 | // Point.h |
Point.cpp
:
1 | // Point.cpp |
main.cpp
:
1 | // main.cpp |
编译和运行:
你需要将这三个文件(Point.h
, Point.cpp
, main.cpp
)放在同一个文件夹中。然后使用 C++ 编译器(如 g++)进行编译:
1 | g++ Point.cpp main.cpp -o my_point_app -std=c++17 # -std=c++17 可选,确保编译器支持 C++17 或更高版本 |
运行结果会清晰地展示 m_point_count
如何随着对象的创建和销毁而变化,以及它在 main
函数运行前就已经初始化,并且 const
成员函数可以修改它的特性。
4. QA 闪卡 (QA Flash Cards)
Q1: 什么是静态成员变量?它与普通成员变量有什么主要区别?
A1: 静态成员变量是属于类本身的变量,不依附于任何特定的对象。所有对象共享这同一个副本。而普通成员变量属于每个对象,每个对象都有自己的独立副本。
Q2: 如何声明一个静态成员变量?
A2: 在类定义中,在成员变量类型前加上 static
关键字。例如:static int my_static_var;
Q3: 静态成员变量应该在哪里进行初始化?为什么不能在类定义中初始化?
A3: 静态成员变量必须在类定义之外的 .cpp
源文件中进行定义和初始化。例如:int MyClass::my_static_var = 0;
。不能在类定义中初始化是因为避免重复定义的问题,头文件可能被多个源文件包含。
Q4: 静态成员变量是在程序的哪个阶段被初始化的?
A4: 它们在 main
函数运行之前就被初始化,生命周期与整个程序相同。
Q5: const
成员函数可以修改静态成员变量吗?为什么?
A5: 可以。因为 const
成员函数只承诺不修改调用它的对象的非静态成员变量。静态成员变量不属于任何特定对象的状态,而是属于类本身,因此不受对象 const
性的限制。
5. 常见误解或错误
- 忘记在
.cpp
文件中初始化静态成员变量: 这是一个非常常见的错误。只在.h
文件中声明static int my_static_var;
是不够的,你必须在.cpp
文件中写上int MyClass::my_static_var = 0;
。否则,链接器会报错,因为它找不到该变量的定义。 - 在
.h
文件(类定义内部)尝试初始化非const
静态成员变量: 除非你使用的是 C++17 的inline
关键字,否则在类定义内部直接初始化static int my_static_var = 0;
会导致编译错误。这是因为旧版本 C++ 规定,非const
静态成员变量的定义和初始化必须在外部。 - 认为静态成员变量是每个对象独立拥有的: 这是对“静态”最核心的误解。静态成员变量是所有对象共享的,只有一个副本。
- 在静态成员函数中访问非静态成员变量: 静态成员函数不接收
this
指针,因此它们无法直接访问类的非静态成员变量(如m_x
,m_y
),因为这些变量是与特定对象实例绑定的。 - 对
const
成员函数修改静态成员变量感到困惑: 这是对const
语义理解不深造成的。记住const
是针对对象实例的,不是针对类的。
6. 编程练习
现在,是时候动手实践一下了!
任务:
我们将扩展 Point
类。除了 m_point_count
之外,我们还希望追踪 当前活动(即未被销毁)的 Point
对象的总面积。假设每个 Point
对象代表一个边长为 m_x
的正方形。我们需要一个静态成员变量来累加所有活动 Point
对象的面积。
请完成以下代码片段,实现这个功能:
Point.h
(已提供部分代码,你需要添加新的静态成员声明):
1 | // Point.h |
Point.cpp
(已提供部分代码,你需要完成 TODO 部分):
1 | // Point.cpp |
main.cpp
(你需要在这里测试你的实现):
1 | // main.cpp |
请您在提供的代码中填入 TODO
标记处的代码,然后编译并运行,看看是否能得到预期的结果。完成后,请将您的解决方案发给我!
🧑💻 User
Point.h:
1 | // Point.h |
Point.cpp
1 | // Point.cpp |
🤖 Assistant
恭喜您,您的解决方案非常棒!您已经掌握了静态成员变量的核心概念及其使用方法。
C++ 静态成员变量练习:您的解决方案评估
总体评价:
您的解决方案非常出色!您准确地理解了如何在头文件 (.h
) 中声明静态成员变量和静态成员函数,以及如何在源文件 (.cpp
) 中定义和初始化它们。您也正确地在构造函数中增加了总面积,并在析构函数中减少了总面积,这表明您对静态成员变量作为类级别共享数据的理解非常到位。
唯一需要微调的地方:
在面积计算的部分,您根据 m_x * m_y
来计算面积。根据练习的描述,”假设每个 Point
对象代表一个边长为 m_x
的正方形“,这意味着面积应该是 m_x
的平方,即 m_x * m_x
。这是一个小小的逻辑错误,与静态成员变量本身的知识点无关,更多是关于问题描述的理解。
让我们来详细看看具体的修改点。
正确答案与详细解释
1. Point.h
文件
您的 Point.h
代码完全正确,成功声明了新的静态成员变量 m_total_area
和静态成员函数 get_total_area()
。
1 | // Point.h |
解释:
static double m_total_area;
:这行代码在类内部声明了一个名为m_total_area
的double
类型静态成员变量。static
关键字表示它属于Point
类本身,而不是Point
类的任何特定对象。static double get_total_area();
:这行代码声明了一个名为get_total_area
的静态成员函数。由于它将访问静态成员变量m_total_area
,并且与特定对象实例无关,因此将其声明为static
是非常合适的。
2. Point.cpp
文件
除了面积计算的逻辑外,您的 Point.cpp
也非常接近完美。
1 | // Point.cpp |
解释:
double Point::m_total_area = 0.0;
:这行代码在Point.cpp
文件中定义并初始化了m_total_area
静态成员变量。这是 C++ 中为静态成员变量分配存储空间并赋予初始值的标准做法。m_total_area += m_x * m_x;
:在构造函数中,我们增加了m_total_area
。根据题目要求,每个Point
代表一个边长为m_x
的正方形,所以面积应该是m_x
的平方。m_total_area -= m_x * m_x;
:在析构函数中,当Point
对象被销毁时,我们需要将它所贡献的面积从m_total_area
中减去,以保持总面积的准确性。
3. main.cpp
文件
以下是修改后的 main.cpp
,用于测试您的实现,并提供预期输出:
1 | // main.cpp |
编译与运行:
1 | g++ Point.cpp main.cpp -o area_tracker |
运行上述代码,您将看到 m_total_area
随着对象的创建和销毁而正确地增加和减少。
进一步学习与思考
您已经很好地掌握了 C++ 静态成员变量的基础知识。为了更深入地理解并应对实际开发中的挑战,我建议您继续探索以下主题:
- C++17
inline
静态成员变量:- 在 C++17 及更高版本中,你可以使用
inline
关键字在类定义(即头文件)内部直接初始化静态成员变量,例如static inline double m_total_area = 0.0;
。这简化了代码,避免了在.cpp
文件中单独初始化。了解它的用途和何时使用它。
- 在 C++17 及更高版本中,你可以使用
const
静态成员变量的特殊性:- 对于
const
且为整型(包括枚举类型)的静态成员变量,即使在 C++17 之前,也可以在类定义内部进行就地(in-class)初始化。例如:static const int MAX_POINTS = 100;
。了解这种特例以及它的应用场景。
- 对于
- 单例模式(Singleton Pattern)与静态成员:
- 静态成员变量和静态成员函数在实现单例模式时非常有用,单例模式确保一个类只有一个实例,并提供一个全局访问点。这对于需要全局唯一资源的场景非常有用,例如日志管理器、配置读取器等。
- 静态成员变量的线程安全性(Thread Safety):
- 当程序涉及多线程时,多个线程可能会同时尝试访问和修改静态成员变量。这可能导致数据竞争(race condition)和不可预测的行为。了解如何使用互斥量(mutex)或其他同步机制来确保静态成员变量在多线程环境下的安全访问。
希望这些扩展问题能帮助您进一步巩固和拓展您的 C++ 知识!保持学习的热情,您会越来越棒!