- VisualStudio2022插件的安装及使用-编程手把手系列文章
- pprof-在现网场景怎么用
- C#实现的下拉多选框,下拉多选树,多级节点
- 【学习笔记】基础数据结构:猫树
函数重载( overload ) 。
函数重写(覆写,overrride) 。
编译器会根据实参的类型来⾃动确定调⽤哪个重载函数 。
修饰关键字:inline 。
作用:编译时直接将函数替换为一堆代码,减少函数调用带来的开销.
比#define安全 。
成员函数默认内联,即使不写inline;外部函数必须加inline才能内联.
每一个函数在栈空间上都有一段栈帧,保存这当前函数所需的变量等。当函数出栈时,这些成员也随之销毁.
const修饰的对象、变量,在运行过程中不能修改其值.
const int a = 1;
a = 2; // 错误!
// 会报错:error: assignment of read-only variable 'a'
注意:常量必须在声明时被初始化 const int a; // 错误! 。
int main() {
const int a = 1;
int* add = &a; // 这一句有语法问题:invalid conversion from 'const int*' to 'int*'
*add = 2;
return 0;
}
编译器在做隐式转换的时候不会添加或删除const修饰,如果类型不匹配会报语法错误.
正确做法:使用强制类型转换,消除语法问题 。
int main() {
const int a = 1;
int* add = (int*)&a; // 正确
*add = 2;
return 0;
}
编译器不会自作主张修改const修饰,因为可能有潜在风险,但是如果人为地强制转化,说明是程序员告诉编译器:“就这么干,我说的!“。编译器就会很放心地让程序员来承担责任,不会报错 /doge 。
注意:这里a为局部变量,即使被const修饰,也保存在栈上(假设没有常量折叠)。如果换做全局变量,被const修饰后存储在常量区中,为只读属性,没有办法修改的.
常量指针 。
int main() {
char array[] = {'a', 'b', 'c', 'd', '\0'};
char* const name = array; // 注意这里的 * 和 const的前后位置
name[0] = 'H'; // 正确
name = (char*)"Hi"; // 错误! assignment of read-only variable 'name'
return 0;
}
指向常量的指针 。
int main() {
char array[] = {'a', 'b', 'c', 'd', '\0'};
char const* name = array; // 注意这里的 * 和 const的前后位置
name[0] = 'H'; // 错误!
name = (char*)"Hi"; // 正确
return 0;
}
这里可以这样子去记忆:const负责在他左边的东西,如果左边是char*,说明地址是常量,如果左边是char,说明字符是常量.
常规的静态分配内存:int a[10] = {0},
数组a在编译的时候就被分配了固定大小的内存.
int n = 10;
int b[x]; // 对于多数编译器是不允许的,即使允许也会有⻛险,如多线程编程
而使用动态定义变量更灵活 。
int n = 10;
int* b = new int[n]();
注意:最后记得delete 。
使用类的静态成员 。
解除被隐藏全局函数或变量 。
int num = 1; // 全局变量
main() {
int num = 2; // 局部变量
cout << num << endl; // 显示局部变量
cout << ::num << endl; // 显示全局变量
}
注意点:
(C++11之前)在类体中不允许对数据成员进行初始化 。
class A {
int a = 0; // 在一些老版本的编译环境上会出错
};
类中的成员不能是自身类的对象 。
否则会导致无限递归,最终栈溢出,因此会报编译错误.
但是可以放自身类的指针或引用.
若类A中含有成员类B,则类B需要提前声明 。
数据成员不能用auto、register、extern修饰;成员函数不能用extern修饰 。
注意点:
形参必须是对象的引用,&不能省略,否则会向上文所述的无限递归.
编译构造优化 。
要特别注意浅拷贝的问题 。
举个例子 。
class A {
char* name;
~A() { delete[] name; }
};
当调用拷贝构造函数的时候,编译器会无脑复制一份数据到新对象,此时两个对象中name指向同一个地址,当两个类调用析构函数时,name会被delete两次,第二次会出错.
这种情况必须自己写拷贝构造函数.
对象作为参数传递时,用引用或者指针好一点,可以避免一次拷贝构造,减少开销 。
作用:为了解决同类对象间的数据共享问题,实际就是共享变量 。
静态成员在整个内存中只有一份,位于静态区.
在编译器遇到初始化语句时,就为其分配空间.
两种初始化方法:
静态成员函数只能访问静态成员,因为非静态成员有对象才存在;相反,非静态成员函数可任意访问静态、非静态成员。在⼀般的成员函数中都隐含有⼀个this指针,⽤来指向对象自身,而在静态成员函数中没有this指针,这是无法访问非静态成员的技术原因。但是若向静态函数中传入对象,则可以访问对象中的非静态成员.
允许⼀个类授权另⼀个类的对象(友元类)、某个成员函数(友元成员)或外部函数(友元函数)访问其对象的非公有成员,而不允许整个程序访问。是类的封装性与执行效率的折中.
Java保留了静态成员,舍弃了友元机制.
比如类A中有成员变量类B,则在实例化A的时候,先调用B的构造函数,再调用A的构造函数(先里后外) 。
另外,在A的构造函数中,推荐使用构造函数初始化列表,来初始化B.
重定义(隐藏)redefining:派生类中重新定义基类成员,包括数据成员、成员函数,可参考 重写(覆盖)overload:是重定义,但使用虚函数,实现了多态 在java中两者无区别 。
派生类重新定义了print,则派生类中有两个print,但隐藏了继承来的print——用派生类对象a调用print,即a.print(),调用的是重新定义的,除非a.person::print();或者用基类指针指向a,用指针调用print。如果是重写,则根据虚表直接找到重写的print.
这里比较绕,分三步说明 。
基类的公有和保护成员都能够被派生类的成员访问 。
基类的私有成员不能被派生类成员直接访问,但可以通过基类的公有成员函数间接访问,比如基类的get、set 。
指的是实例化派生类后,能否通过a.print()的方式访问类内成员 。
从类外看,派生类和基类是融为一体的,就当作一个普通的类操作,所以当然是私有不能访问,公有可以访问,而具体是私有还是公有见 1)中分析 。
综上所述,派生方式只改变类外访问权限 。
派生类不继承构造函数,因为不符合构造方法的命名规则。但调用派生类构造函数时,编译器也要调用基类构造函数,这不是真正意义上的继承。析构函数也是类似。 但基类析构函数可以在子类中用基类名显式调用 。
注意:C++基类的析构函数一般定义为虚函数,防止内存泄漏 。
创建派生类对象: (1)先执行基类的构造函数 (2)执行派生类的构造函数 。
销毁派生类对象时,相反 。
派生类有多个基类,派生方式可以各不相同 。
A 基类
/ \
B C
\ /
D
构造顺序为 ABACD
但是多继承可能有模糊性(菱形继承的二义性) 。此外,从不同类继承同名成员,也会出现歧义性 。
class 派生类名 : virtual 派生方式 基类名{
// .....
};
编译器保证从虚基类中继承的成员只有一个拷贝,避免模糊性 。
A 基类
/ \
B C BC继承A时需要虚继承
\ /
D D不需要虚继承
也就是说,虚继承可以作用于一分为二的继承,避免基类成员也一分为二。而在合二为一的情况下,虚继承没有用 。
在D的构造函数中,需要用参数化列表调用ABC的构造函数,即在直接继承BC基础上,加一个虚基类(祖先)的构造函数。因此,BC在继承A的时候,必须是公有继承,不然D无法调用A的构造函数,因此虚继承必须是公有继承.
上图调用顺序为ABCD,虚基类(祖先)第一个调用,并且只会调用一次,从而保证其成员只会被初始化一次。当然,如果虚基类还有父类,那应该按正常流程先调用父类,再子类。然后,虚继承的类先构造,非虚继承的类后构造 。
即:
析构顺序相反 。
A
|
B (C) D (E)
\ | | /
F
其中F虚继承于CE,用括号表示,并包含一个M类成员
class F : public B, virtual public C, public D, virtual public E {
// ...
M mem1;
}
则构造顺序为:CEABDMF 。
class A {
public:
int a;
A() { a = 10; }
};
class A1 : virtual public A {
public:
A1() { a++; }
};
class A2 : virtual public A {
public:
A2() { a++; }
};
class B : public A1, public A2 {
public:
B() { a++; }
void print() { cout << a << endl; }
};
int main() {
B b;
b.print();
return 0;
}
结果输出:13 。
虚基表 。
C++对象模型详解(整理) 。
图说C++对象模型:对象内存布局详解 。
C++:30 --- C++类成员,成员函数的内存布局 。
虚继承和虚基类的对象模型 。
大部分编译器都提供了查看C++代码中类内存分布的工具,比如vs。参考 。
在C++中: 函数重载(含运算符重载、重新定义、模版)是通过静态联编实现的 虚函数多态性(重写)是通过动态联编来实现的,使用虚函数表在运行时实现 。
用来体会一下静态联编 。
class Point {
public:
Point(double i, double j) {
x = i;
y = j;
}
double Area() const { return 0.0; } //
private:
double x, y;
};
class Rectangle : public Point {
public:
Rectangle(double i, double j, double k, double l);
double Area() const { return w * h; } // 在派生类中重新定义
private:
double w, h;
};
Rectangle::Rectangle(double i, double j, double k, double l) : Point(i, j) {
w = k;
h = l;
}
void fun(Point& s) { // 外部函数
cout << s.Area() << endl;
}
void main() {
Rectangle rec(3.0, 5.2, 15.0, 25.0);
fun(rec);
rec.Area();
}
注意fun函数的形参为Point&,即基类引用,因此会窄化,fun始终调用基类的Area()。若参数改为:Rectangle &s,也同样只能调用Rectangle中的Area() 。
不同函数使用同一个函数名,由编译器来选择具体由哪个函数来执行 。
一般用于操作类,比如两个自定义类的 “+” 运算 。
显然,重载的运算符函数必须要能访问类中元素,因此运算符重载 要么是成员函数,要么是外部友元函数。如果用成员函数,那形参只有一个,如果是友元函数,形参有两个。特别的,( )、[ ]、->、=,必须用成员函数实现运算符重载,<<(输出)必须用友元函数实现运算符重载.
有一些运算符是不能重载的,共5个:. .* :: ?:(三目运算符) sizeof 。
对于[]、<<,都是两目,但区别是第一个参数是本类对象或其它类对象,所以采用不同实现方法,原因:
如果用成员函数实现,类的this指针会被绑定到运算符的左侧对象,防止出现异常的写法。这是与this的背后实现有关:比如a.f(),编译器先用this指向对象a,然后调用函数f,函数中访问数据成员时,编译器通过this到对象中找。对于a[i]的重载,[]是函数,所以仍遵循上述原理.
如果是友元,则没有this指针,双目运算符左右对象无限制,比如第一形参是整型(下标),第二形参是对象。可能导致:3[a] 。可参考 。
每个非静态成员函数都有一个隐式参数(编译器编译器编译时自动添加的),这个参数就是成员函数中this指针的来源,并且是第一个参数。 比如:void A::f() {} ,实际上是:void A::f(A *const this) {} 编译器在编译时会自动传递参数,使得成员函数可以访问对象的数据成员。因为数据成员分配在对象中,而成员函数本类对象共用,分配在别的地方。 << 运算符重载函数要求第一个参数必须是std::ostream & ,所以必须是非成员函数。但不一定非得是友元,除非访问私有成员 。
在值返回的时候会有所区别 。
Integer operator+(const Integer& a) { // 情况1:返回局部对象
return Integer(val + a.val);
}
Integer operator+(const int x) { // 情况2:返回局部对象
Integer tmp;
tmp.val += x;
return tmp;
}
Integer operator+(const char x) { // 情况3:用*this返回
return *this;
}
Integer operator-(const Integer x) { // 情况4:返回形参对象
return x;
}
在上述12两种返回局部对象的情况时,按理来说,应该是在return时创建一个匿名对象,再调用拷贝构造函数将其赋值给调用运算符的左值。而编译器会对此优化,不需要拷贝构造.
因此,上述四种情况分别会 。
Integer& operator++( ); //前缀增量运算符
Integer operator++(int); //后缀增量运算符
前缀与后缀在调用时其实形式一样,为了在调用时能够区分前、后缀,c++后来(开始时前后缀无区分)规定:后缀函数加一个没有实际意义的int参数,这样编译器在判断出前后缀时,就知道调用哪一个了。在调用后缀函数时,自动传给参数int的值是0,所以该参数的变量名都可以省略 。
前缀按语义可连接,后缀不允许。此外后缀要先返回后加加,故返回对象。非要返回引用当然也可以,但会造成连续加加,不符合语义.
通过构造函数进行(简单类型 -> 类对象) 。
通过类型转换运算符重载进行(类对象-> 简单类型) 。
如operator int(),
Integer::operator int() {
cout << “Type changed to int” << endl;
return value; // 没返回类型,但要返回
}
注意:没有返回类型、参数 。
如果不重载,编译器会默认一个,其默认行为是复制对象的数据成员,就和默认拷贝构造函数一样,但这会导致浅拷贝 。
赋值运算符重载不能继承。实际是可继承,但因为派生类没定义赋值重载,则编译器默认一个,隐藏了继承来的 。
动态联编实现的运行时多态 。
通常用基类指针(或引用)指向公有派生类对象。必须是公有派生,如果私有派生的话,从基类继承的成员都会私有,再拿基类指针指,首先会窄化,结果什么都访问不到。因此如果用基类指针指向私有、保护派生类对象,会直接在编译的时候报错 。
在基类中被关键字virtua修饰,派生类中virtual可省略 。
这里的virtual和前文的虚继承一点关系都没有,同名纯属巧合 。
一个类有虚函数,则会定义虚函数表,本类只有一个。本类对象中都有虚函数表指针。虚函数表是函数指针数组,指向每个虚函数.
派生类继承了虚函数,则也继承了虚函数表(复制了虚函数表)。如果重写虚函数,则修改虚表中函数指针,否则仍指向基类虚函数.
编译阶段创建对象及类的虚表。运行时用基类指针访问到派生类对象,找到其虚表指针,查表找到虚函数执行。所以编译时只绑定了指针(包括对象中的虚函数表指针、虚函数表中的函数指针),运行时根据指针才能绑定虚函数。 所以无论基类指针指向基类对象还是派生类对象,能做到不同对象对相同消息做出不同响应。 当基类指针指向派生类对象时,编译器以为是基类对象,但因为是虚函数,所以编译器建立虚函数表并在对象中存放虚函数指针,导致运行时调用重写的虚函数.
对于p->vfunc1(),因为p是基类指针,则编译器已经将所指派生类对象隐式转为基类对象,所以无法判定调用哪个函数 。
针对第二点析构的,打个比方A *p = new B;,new了一个派生类对象,但是用的基类指针,也就是说最后调用了基类的析构,会导致派生类中新定义的成员无法销毁。因此析构函数也必须重写.
每个类的析构函数负责销毁自己新定义的成员,派生类只需要销毁掉新定义的即可,至于继承来的成员,会调用基类的析构销毁。整个流程就是:基类指针调用析构(虚函数),派生类析构,由于继承关系,基类析构(这里和虚函数没关系,纯属是“继承”的特性) 。
析构的虚函数有其特殊性,比如在基类和派生类中函数名不同,但就运行时多态而言,与其他虚函数性质一样.
详细研究虚析构函数在虚函数表情况,参见:c++虚析构函数在虚函数表中吗?
纯虚函数是一个在基类中说明的虚函数,它在该基类中仅定义了函数首部,必须在派生类中重写 。
定义:virtual 返回类型 函数名(参数表)=0,
有纯虚函数的类称为抽象类,其无法被实例化,不能做函数参数、返回类型或转换类型,但抽象类指针、引用可以 。
分为函数模板和类模板,是实现代码重用机制的一种工具 。
使用函数模板时需要实例化,可以显式、隐式实例化 。
template <typename T>
void swap(T &a, T &b){...}
//隐式
int main(){
int x=1,y=2;
swap<int>(x,y);
//或swap(x,y);
....
}
//显式
template
void swap(int &a,int &b);
int main(){
int x=1,y=2;
swap(x,y);
....
}
函数模板中没有隐式的类型转换(形参到实参),但是重载后的模板函数(也就成了普通函数)是可以隐式转换的 。
template <class T>
T max(T x, T y) {
return (x > y) ? x : y;
}
void func(int i, char c) {
max(i, i); // 正确 调用max(int,int)
max(c, c); // 正确 调用max(char,char)
max(i, c); // 错误 类型不匹配
max(c, i); // 错误 类型不匹配
}
解决方法:
采用强制类型转换,如将上述max(i,c);改写为max(i,int(c)),
用非模板函数去重载函数模板 。
char max(int x, char y) {
return (x > y) ? x : y;
}
这样就会调用这个普通的重载函数 。
优先级为:
例如 。
template <class T>
T myMax(T x, T y) { // 模板
// ...
}
int myMax(int x, int y) { // 重载1
// ...
}
char myMax(int x, char y) { // 重载2
// ...
}
int main() {
int i = 10;
char c = ‘a’;
float f = 43.74;
cout << myMax(i, i) << endl; // 重载1
cout << myMax(c, c) << endl; // 模板
cout << myMax(i, c) << endl; // 重载2
cout << myMax(c, i) << endl; // 重载2 (隐式转换)
cout << myMax(f, f) << endl; // 模板
cout << myMax(f, i) << endl; // 隐式转换是必须的,但是既可以转成重载1,又可以转成重载2
// 此时选择哪一个由编译器决定,不同编译器可能有不同选择
return 0;
}
dddd 。
有一个String类,只有一个成员变量 。
class String {
char* array;
}
1)重载[] 。
这里为了理解面向对象的一些点,采用了很奇怪的写法,仅仅保证语法无误 。
关注点:返回值应该是引用 。
char& operator[](int i) {
if (i < 0 || i >= strlen(array)) {
cout << "Index out of range." << endl;
static char dummy = '\0';
return dummy;
}
return array[i];
}
2)重载+ 。
如果要做String + String的运算 。
这是一种返回临时对象的写法 。
String operator+(const String& s) { // 形参中的const和引用都可省略,语法上没问题
String temp(array);
temp.array = new char[strlen(array) + strlen(s.array) + 1];
strcpy(temp.array, array);
strcat(temp.array, s.array);
return temp;
}
如果要做String + char*的运算 。
一种方法是重载+运算符,接收一个char*类型的参数 。
一种方法是使用类型转换 。
比如把char*转为String,用构造函数就可以实现,运算时编译器会自动隐式转换,从而转为String + String.
需要注意的是,这种方法会把char*转为一个临时对象String,这个临时对象作为+函数参数传入时,函数形参必须声明为const。也就是说,运算符重载函数接收一个临时对象时,必须是const.
若把String转为char*,则需要重载char*() 。
但是这两种方法不能同时使用,即不能同时有char* -> String和 String -> char*,否则会有二义性:既可以转为char* + char*,又可以转为String + String 。
整个思路是:A重载了+运算符,但是只能和A相加,因此这里给B提供了一个转化为A的类型转换重载,编译器在计算a+b时,会自动把b隐式转换为a 。
class A {
int a;
public:
A() { a = 0; }
A(int i) { a = i; }
int operator+(const A& aa) { return a + aa.a; }
};
class B {
int b;
public:
B() { b = 0; }
operator A() { return A(b); }
};
int main() {
A a;
B b;
cout << a + b << endl;
return 0;
}
如正文所讲,<<只能采用友元函数重载,并且返回值得是引用,第一个参数得是引用,第二个参数中如果要接收一个临时对象,必须用const修饰 。
friend ostream& operator<<(ostream& out, const String& s) {
out << s.array;
return out;
}
class A {
int x;
public:
A(int i = 0) { // 构造
x = i;
cout << "construct";
}
A(const A& a) { // 拷贝构造
cout << "copy";
}
A operator+(const A& a) { // 重载+
cout << "add";
return A(x + a.x);
}
void operator=(const A& a) { // 重载=
cout << "equals";
}
};
int main() {
A a, b;
A c = a;
A d = a + b;
return 0;
}
这段代码最终输出为:con con copy add con 。
分析:
A a, b;
两次默认构造A c = a;
标准的拷贝构造A d = a + b;
显然先有一次 +
重载,之后编译器会优化,效果等价于 A d(a + b);
也就是一次普通构造怎么区分 = 是拷贝构造还是赋值运算呢?只有在初始化对象的时候,才会使用拷贝构造,如果对象已经被创建了,之后的=都是赋值 。
题目:
有四种几何图形:三角形、矩形、正方形和圆。 求四种几何图形的面积之和.
定义一个包含两个虚函数的类 。
class Shape {
public:
virtual float Area(void) = 0; // 求面积
virtual void SetData(float, float = 0) = 0; // 设置几何形状参数
// 不同集合形状需要1个或2个参数,故采用默认参数值
};
公有派生各类,重写两个函数,这里只展示一个类,其余同理.
class Triangle : public Shape {
float W, H; // 三角形边长为W,高为H
public:
Triangle(float w = 0, float h = 0) {
W = w;
H = h;
}
float Area(void) { // 重写虚函数
return W * H / 2;
}
void Setdata(float w, float h = 0) { // 重写虚函数
W = w;
H = h;
}
};
这里使用了一个指针数组,来实现多态;如果采用对象数组,则无法实现多态 。
class Compute {
Shape** s; // 指向基类的指针数组
public:
Compute() { // 给几何图形设置参数
s = new Shape*[4];
s[0] = new Triangle(3, 4);
s[1] = new Rectangle(6, 8);
s[2] = new Square(6.5);
s[3] = new Circle(5.5);
}
float SumArea(void);
~Compute();
void Setdata(int n, float a, float b = 0) { s[n]->Setdata(a, b); }
};
float Compute::SumArea(void) {
float sum = 0;
for (int i = 0; i < 4; i++)
sum += s[i]->Area(); // 通过基类指针实现多态性
return sum;
}
Compute::~Compute() {
for (int i = 0; i < 4; i++)
delete s[i];
delete[] s;
}
最后此篇关于C++面向对象的文章就讲到这里了,如果你想了解更多关于C++面向对象的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我的一位教授给了我们一些考试练习题,其中一个问题类似于下面(伪代码): a.setColor(blue); b.setColor(red); a = b; b.setColor(purple); b
我似乎经常使用这个测试 if( object && object !== "null" && object !== "undefined" ){ doSomething(); } 在对象上,我
C# Object/object 是值类型还是引用类型? 我检查过它们可以保留引用,但是这个引用不能用于更改对象。 using System; class MyClass { public s
我在通过 AJAX 发送 json 时遇到问题。 var data = [{"name": "Will", "surname": "Smith", "age": "40"},{"name": "Wil
当我尝试访问我的 View 中的对象 {{result}} 时(我从 Express js 服务器发送该对象),它只显示 [object][object]有谁知道如何获取 JSON 格式的值吗? 这是
我有不同类型的数据(可能是字符串、整数......)。这是一个简单的例子: public static void main(String[] args) { before("one"); }
嗨,我是 json 和 javascript 的新手。 我在这个网站找到了使用json数据作为表格的方法。 我很好奇为什么当我尝试使用 json 数据作为表时,我得到 [Object,Object]
已关闭。此问题需要 debugging details 。目前不接受答案。 编辑问题以包含 desired behavior, a specific problem or error, and the
我听别人说 null == object 比 object == null check 例如: void m1(Object obj ) { if(null == obj) // Is thi
Match 对象 提供了对正则表达式匹配的只读属性的访问。 说明 Match 对象只能通过 RegExp 对象的 Execute 方法来创建,该方法实际上返回了 Match 对象的集合。所有的
Class 对象 使用 Class 语句创建的对象。提供了对类的各种事件的访问。 说明 不允许显式地将一个变量声明为 Class 类型。在 VBScript 的上下文中,“类对象”一词指的是用
Folder 对象 提供对文件夹所有属性的访问。 说明 以下代码举例说明如何获得 Folder 对象并查看它的属性: Function ShowDateCreated(f
File 对象 提供对文件的所有属性的访问。 说明 以下代码举例说明如何获得一个 File 对象并查看它的属性: Function ShowDateCreated(fil
Drive 对象 提供对磁盘驱动器或网络共享的属性的访问。 说明 以下代码举例说明如何使用 Drive 对象访问驱动器的属性: Function ShowFreeSpac
FileSystemObject 对象 提供对计算机文件系统的访问。 说明 以下代码举例说明如何使用 FileSystemObject 对象返回一个 TextStream 对象,此对象可以被读
我是 javascript OOP 的新手,我认为这是一个相对基本的问题,但我无法通过搜索网络找到任何帮助。我是否遗漏了什么,或者我只是以错误的方式解决了这个问题? 这是我的示例代码: functio
我可以很容易地创造出很多不同的对象。例如像这样: var myObject = { myFunction: function () { return ""; } };
function Person(fname, lname) { this.fname = fname, this.lname = lname, this.getName = function()
任何人都可以向我解释为什么下面的代码给出 (object, Object) 吗? (console.log(dope) 给出了它应该的内容,但在 JSON.stringify 和 JSON.parse
我正在尝试完成散点图 exercise来自免费代码营。然而,我现在只自己学习了 d3 几个小时,在遵循 lynda.com 的教程后,我一直在尝试确定如何在工具提示中显示特定数据。 This code
我是一名优秀的程序员,十分优秀!