精华内容
下载资源
问答
  • C++ 运算符重载

    万次阅读 多人点赞 2018-12-26 21:39:21
    有的时候,两个对象相等,从实际应用的含义上来讲,指的并不应该是两个对象的每个字节都相同,而是有其他解释,这时就需要对 = 进行重载。 上节我们定义了 String 类,并重载了 = 运算符,使得 char * 类型的字符串...

    前言

    本文引用于“C语言中文网”,我整理出来放在博客,方便大家共同学习。所有知识点和代码均已亲测可用,如有疑问,可提出,一起讨论学习。

    本章内容:

    1. C++运算符重载的概念和原理
    2. C++重载=(C++重载赋值运算符)
    3. C++深拷贝和浅拷贝(C++深复制和浅复制)
    4. C++运算符重载为友元函数
    5. C++实现可变长度的动态数组
    6. C++重载<<和>>(C++重载输出运算符和输入运算符)
    7. C++重载()(强制类型转换运算符)
    8. C++重载++和--(自增和自减运算符)
    9. C++运算符重载注意事项以及汇总

    1 C++运算符重载的概念和原理 

    如果不做特殊处理,C++ 的 +、-、*、/ 等运算符只能用于对基本类型的常量或变量进行运算,不能用于对象之间的运算。

    有时希望对象之间也能用这些运算符进行运算,以达到使程序更简洁、易懂的目的。例如,复数是可以进行四则运算的,两个复数对象相加如果能直接用+运算符完成,不是很直观和简洁吗?

    利用 C++ 提供的“运算符重载”机制,赋予运算符新的功能,就能解决用+将两个复数对象相加这样的问题。

    运算符重载,就是对已有的运算符赋予多重含义,使同一运算符作用于不同类型的数据时产生不同的行为。运算符重载的目的是使得 C++ 中的运算符也能够用来操作对象。

    运算符重载的实质是编写以运算符作为名称的函数。不妨把这样的函数称为运算符函数。运算符函数的格式如下:

    返回值类型  operator  运算符(形参表)
    {
        ....
    }

    包含被重载的运算符的表达式会被编译成对运算符函数的调用,运算符的操作数成为函数调用时的实参,运算的结果就是函数的返回值。运算符可以被多次重载。

    运算符可以被重载为全局函数,也可以被重载为成员函数。一般来说,倾向于将运算符重载为成员函数,这样能够较好地体现运算符和类的关系。来看下面的例子:

    #include <iostream>
    using namespace std;
    class Complex
    {
        public:
        double real, imag;
        Complex(double r = 0.0, double i = 0.0) : real(r), imag(i) { }
        Complex operator - (const Complex & c);
    };
    Complex operator + (const Complex & a, const Complex & b)
    {
        return Complex(a.real + b.real, a.imag + b.imag); //返回一个临时对象
    }
    Complex Complex::operator - (const Complex & c)
    {
        return Complex(real - c.real, imag - c.imag); //返回一个临时对象
    }
    int main()
    {
        Complex a(4, 4), b(1, 1), c;
        c = a + b; //等价于 c = operator + (a,b);
        cout << c.real << "," << c.imag << endl;
        cout << (a - b).real << "," << (a - b).imag << endl; //a-b等价于a.operator - (b)
        return 0;
    }

    程序的输出结果是:

    5,5
    3,3

    程序将+重载为一个全局函数(只是为了演示这种做法,否则重载为成员函数更好),将-重载为一个成员函数。

    运算符重载为全局函数时,参数的个数等于运算符的目数(即操作数的个数);运算符重载为成员函数时,参数的个数等于运算符的目数减一。

    如果+没有被重载,第 21 行会编译出错,因为编译器不知道如何对两个 Complex 对象进行+运算。有了对+的重载,编译器就将a+b理解为对运算符函数的调用,即operator+(a,b),因此第 21 行就等价于:

    c = operator+(a, b);

    即以两个操作数 a、b 作为参数调用名为operator+的函数,并将返回值赋值给 c。

    第 12 行,在 C++ 中,“类名(构造函数实参表)”这种写法表示生成一个临时对象。该临时对象没有名字,生存期就到包含它的语句执行完为止。因此,第 12 行实际上生成了一个临时的 Complex 对象作为 return 语句的返回值,该临时对象被初始化为 a、b 之和。第 16 行与第 12 行类似。

    由于-被重载为 Complex 类的成员函数,因此,第 23 行中的a-b就被编译器处理成:

    a.operator-(b);

    由此就能看出,为什么运算符重载为成员函数时,参数个数要比运算符目数少 1 了。

    2 C++重载=(C++重载赋值运算符)

    赋值运算符=要求左右两个操作数的类型是匹配的,或至少是兼容的。有时希望=两边的操作数的类型即使不兼容也能够成立,这就需要对=进行重载。C++ 规定,=只能重载为成员函数。来看下面的例子。

    要编写一个长度可变的字符串类 String,该类有一个 char* 类型的成员变量,用以指向动态分配的存储空间,该存储空间用来存放以\0结尾的字符串。String 类可以如下编写:

    #include <iostream>
    #include <cstring>
    using namespace std;
    class String {
    private:
        char * str;
    public:
        String() :str(NULL) { }
        const char * c_str() const { return str; };
        String & operator = (const char * s);
        ~String();
    };
    String & String::operator = (const char * s)
    //重载"="以使得 obj = "hello"能够成立
    {
        if (str)
            delete[] str;
        if (s) { //s不为NULL才会执行拷贝
            str = new char[strlen(s) + 1];
            strcpy(str, s);
        }
        else
            str = NULL;
        return *this;
    }
    String::~String()
    {
        if (str)
            delete[] str;
    };
    int main()
    {
        String s;
        s = "Good Luck,"; //等价于 s.operator=("Good Luck,");
        cout << s.c_str() << endl;
        // String s2 = "hello!"; //这条语句要是不注释掉就会出错
        s = "Shenzhou 8!"; //等价于 s.operator=("Shenzhou 8!");
        cout << s.c_str() << endl;
        return 0;
    }

    程序的运行结果:

    Good Luck,
    Shenzhou 8!

    第 8 行的构造函数将 str 初始化为 NULL,仅当执行了 operator= 成员函数后,str 才会指向动态分配的存储空间,并且从此后其值不可能再为 NULL。在 String 对象的生存期内,有可能从未执行过 operator= 成员函数,所以在析构函数中,在执行delete[] str之前,要先判断 str 是否为 NULL。

    第 9 行的函数返回了指向 String 对象内部动态分配的存储空间的指针,但是不希望外部得到这个指针后修改其指向的字符串的内容,因此将返回值设为 const char*。这样,假定 s 是 String 对象,那么下面两条语句编译时都会报错,s 对象内部的字符串就不会轻易地从外部被修改了 :

    char* p = s.c_str ();
    strcpy(s.c_str(), "Tiangong1");

    第一条语句出错是因为=左边是 char* 类型,右边是 const char * 类型,两边类型不匹配;第二条语句出错是因为 strcpy 函数的第一个形参是 char* 类型,而这里实参给出的却是 const char * 类型,同样类型不匹配。

    如果没有第 13 行对=的重载,第 34 行的s = "Good Luck,"肯定会因为类型不匹配而编译出错。经过重载后,第 34 行等价s.operator=("Good Luck,");,就没有问题了。

    在 operator= 函数中,要先判断 str 是否已经指向动态分配的存储空间,如果是,则要先释放那片空间,然后重新分配一片空间,再将参数 s 指向的内容复制过去。这样,对象中存放的字符串就和 s 指向的字符串一样了。分配空间时,要考虑到字符串结尾的\0,因此分配的字节数要比 strlen(s) 多 1。

    需要注意一点,即使对=做了重载,第 36 行的String s2 = "hello!";还是会编译出错,因为这是一条初始化语句,要用到构造函数,而不是赋值运算符=。String 类没有编写参数类型为 char * 的构造函数,因此编译不能通过。

    就上面的程序而言,对 operator= 函数的返回值类型没有什么特别要求,void 也可以。但是在对运算符进行重载时,好的风格是应该尽量保留运算符原本的特性,这样其他人在使用这个运算符时才不容易产生困惑。赋值运算符是可以连用的,这个特性在重载后也应该保持。即下面的写法应该合法:

    a = b = c;

    假定 a、b、c 都是 String 对象,则上面的语句等价于下面的嵌套函数调用:

    a.operator=( b.operator=(c) );

    如果 operator= 函数的返回值类型为 void,显然上面这个嵌套函数调用就不能成立。将返回值类型改为 String 并且返回 *this 可以解决问题,但是还不够好。因为,假设 a、b、c 是基本类型的变量,则

    (a =b) = c;

    这条语句执行的效果会使得 a 的值和 c 相等,即a = b这个表达式的值其实是 a 的引用。为了保持=的这个特性,operator= 函数也应该返回其所作用的对象的引用。因此,返回值类型为 String & 才是风格最好的写法。在 a、b、c 都是 String 对象时,(a=b)=c;等价于

    ( a.operator=(b) ).operator=(c);

    a.operator=(b) 返回对 a 的引用后,通过该引用继续调用 operator=(c),就会改变 a 的值。

    3 C++深拷贝和浅拷贝(C++深复制和浅复制) 

    同类对象之间可以通过赋值运算符=互相赋值。如果没有经过重载,=的作用就是把左边的对象的每个成员变量都变得和右边的对象相等,即执行逐个字节拷贝的工作,这种拷贝叫作“浅拷贝”。

    有的时候,两个对象相等,从实际应用的含义上来讲,指的并不应该是两个对象的每个字节都相同,而是有其他解释,这时就需要对=进行重载。

    上节我们定义了 String 类,并重载了=运算符,使得 char * 类型的字符串可以赋值给 String 类的对象。完整代码如下:

    #include <iostream>
    #include <cstring>
    using namespace std;
    class String {
    private:
        char * str;
    public:
        String() :str(NULL) { }
        const char * c_str() const { return str; };
        String & operator = (const char * s);
        ~String();
    };
    String & String::operator = (const char * s)
    //重载"="以使得 obj = "hello"能够成立
    {
        if (str)
            delete[] str;
        if (s) { //s不为NULL才会执行拷贝
            str = new char[strlen(s) + 1];
            strcpy(str, s);
        }
        else
            str = NULL;
        return *this;
    }
    String::~String()
    {
        if (str)
            delete[] str;
    };
    int main()
    {
        String s;
        s = "Good Luck,"; //等价于 s.operator=("Good Luck,");
        cout << s.c_str() << endl;
        // String s2 = "hello!"; //这条语句要是不注释掉就会出错
        s = "Shenzhou 8!"; //等价于 s.operator=("Shenzhou 8!");
        cout << s.c_str() << endl;
        return 0;
    }

    对于上面的代码,如果让两个 String 对象相等(把一个对象赋值给另一个对象),其意义到底应该是什么呢?是两个对象的 str 成员变量都指向同一个地方,还是两个对象的 str 成员变量指向的内存空间中存放的内容相同?如果把 String 对象理解为存放字符串的对象,那应该是后者比较合理和符合习惯,而前者不但不符合习惯,还会导致程序漏洞。

    按照上面代码中 String 类的写法,下面的程序片段会引发问题:

    String s1, s2;
    s1 = "this";
    s2 = "that";
    s2 = s1;

    执行完上面的第 3 行后,s1 和 s2 的状态如图 1 (a) 所示,它们的 str 成员变量指向不同的存储空间。
     


                                                                                   图1:浅拷贝导致的错误

    s2=s1;执行的是浅拷贝。执行完s2=s1;后,s2.str s1.str 指向同一个地方, 如图 1 (b) 所示。这导致 s2.str 原来指向的那片动态分配的存储空间再也不会被释放,变成内存垃圾。

    此外,s1 和 s2 消亡时都会执行delete[] str;,这就使得同一片存储空间被释放两次,会导致严重的内存错误,可能引发程序意外中止。

    而且,如果执行完s1=s2;后 又执行s1 = "some";,则会导致 s2.str 也被释放。

    为解决上述问题,需要对做=再次重载。重载后的的逻辑,应该是使得执行s2=s1;后,s2.str 和 s1.str 依然指向不同的地方,但是这两处地方所存储的字符串是一样的。再次重载=的写法如下:

    String & String::operator = (const String & s)
    {
        if(str == s.str)
            return * this;
        if(str)
            delete[] str;
        if(s.str){ //s. str不为NULL才执行复制操作
            str = new char[ strlen(s.str) + 1 ];
            strcpy(str, s.str);
        }
        else
            str = NULL;
        return * this;
    }

    经过重载,赋值号=的功能不再是浅拷贝,而是将一个对象中指针成员变量指向的内容复制到另一个对象中指针成员变量指向的地方。这样的拷贝就叫“深拷贝”。

    程序第 3 行要判断 str==s.str,是因为要应付如下的语句:

    s1 = s1;

    这条语句本该不改变s1的值才对。s1=s1;等价于s.operator=(s1);,如果没有第 3 行和第 4 行,就会导致函数执行中的 str 和 s.str 完全是同一个指针(因为形参 s 引用了实参 s1,因此可以说 s 就是 s1)。第 8 行为 str 新分配一片存储空间,第 9 行从自己复制到自己,那么 str 指向的内容就不知道变成什么了。

    当然,程序员可能不会写s1=s1;这样莫名奇妙的语句,但是可能会写rs1=rs2;,如果 rs1 和 rs2 都是 String 类的引用,而且它们正好引用了同一个 String 对象,那么就等于发生了s1=s1;这样的情况。

    思考题:上面的两个 operator= 函数有什么可以改进以提高执行效率的地方?

    重载了两次=的 String 类依然可能导致问题。因为没有编写复制构造函数,所以一旦出现使用复制构造函数初始化的 String 对象(例如,String 对象作为函数形参,或 String 对象作为函数返回值),就可能导致问题。最简单的可能出现问题的情况如下:

    String s2;
    s2 = "Transformers";
    String s1(s2);

    s1 是以 s2 作为实参,调用默认复制构造函数来初始化的。默认复制构造函数使得 s1.str 和 s2.str 指向同一个地方,即执行的是浅拷贝,这就导致了前面提到的没有对=进行第二次重载时产生的问题。因此还应该为 String 类编写如下复制构造函数,以完成深拷贝:

    String::String(String & s)
    {
        if(s.str){
            str = new char[ strlen(s.str) + 1 ];
            strcpy(str, s.str);
        }
        else
            str = NULL;
    }
    
    最后,给出 String 类的完整代码:
    class String {
    private:
        char * str;
    public:
        String() :str(NULL) { }
        String(String & s);
        const char * c_str() const { return str; };
        String & operator = (const char * s);
        String & operator = (const String & s);
        ~String();
    };
    String::String(String & s)
    {
        if (s.str) {
            str = new char[strlen(s.str) + 1];
            strcpy(str, s.str);
        }
        else
            str = NULL;
    }
    String & String::operator = (const String & s)
    {
        if (str == s.str)
            return *this;
        if (str)
            delete[] str;
        if (s.str) { //s. str不为NULL才执行复制操作
            str = new char[strlen(s.str) + 1];
            strcpy(str, s.str);
        }
        else
            str = NULL;
        return *this;
    }
    String & String::operator = (const char * s)
    //重载"="以使得 obj = "hello"能够成立
    {
        if (str)
            delete[] str;
        if (s) { //s不为NULL才会执行拷贝
            str = new char[strlen(s) + 1];
            strcpy(str, s);
        }
        else
            str = NULL;
        return *this;
    }
    String::~String()
    {
        if (str)
            delete[] str;
    };

    4 C++运算符重载为友元函数

    一般情况下,将运算符重载为类的成员函数是较好的选择。但有时,重载为成员函数不能满足使用要求,重载为全局函数又不能访问类的私有成员,因此需要将运算符重载为友元。

    例如,对于复数类 Complex 的对象,希望它能够和整型以及实数型数据做四则运算,假设 c 是 Complex 对象,希望c+55+c这两个表达式都能解释得通。

    将+重载为 Complex 类的成员函数能解释c+5,但是无法解释5+c。要让5+c有意义,则应对+进行再次重载,将其重载为一个全局函数。为了使该全局函数能访问 Complex 对象的私有成员,就应该将其声明为 Complex 类的友元。具体写法如下:

    class Complex
    {
        double real, imag;
    public:
        Complex(double r, double i):real(r), imag(i){};
        Complex operator + (double r);
        friend Complex operator + (double r, const Complex & c);
    };
    Complex Complex::operator + (double r)
    { //能解释c+5
        return Complex(real+r, imag);
    }
    Complex operator + (double r, const Complex & c)
    { //能解释5+c
        return Complex (c.real+r, c.imag);
    }

     5 C++实现可变长度的动态数组

    实践中经常碰到程序需要定义一个数组,但不知道定义多大合适的问题。按照最大的可能性定义,会造成空间浪费;定义小了则无法满足需要。

    如果用动态内存分配的方式解决,需要多少空间就动态分配多少,固然可以解决这个问题,但是要确保动态分配的内存在每一条执行路径上都能够被释放,也是一件头疼的事情。

    因此需要编写一个长度可变的数组类,该类的对象就能存放一个可变长数组。该数组类应该有以下特点:

    • 数组的元素个数可以在初始化该对象时指定。
    • 可以动态往数组中添加元素。
    • 使用该类时不用担心动态内存分配和释放问题。
    • 能够像使用数组那样使用动态数组类对象,如可以通过下标访问其元素。

    程序代码如下:

    #include <iostream>
    #include <cstring>
    using namespace std;
    class CArray
    {
    	int size; //数组元素的个数
    	int* ptr; //指向动态分配的数组
    public:
    	CArray(int s = 0); //s代表数组元素的个数
    	CArray(CArray & a);
    	~CArray();
    	void push_back(int v); //用于在数组尾部添加一个元素 v
    	CArray & operator = (const CArray & a); //用于数组对象间的赋值
    	int length() const { return size; } //返回数组元素个数
    	int & operator[](int i)
    	{ //用以支持根据下标访问数组元素,如“a[i]=4;”和“n=a[i];”这样的语句
    		return ptr[i];
    	};
    };
    CArray::CArray(int s) : size(s)
    {
    	if (s == 0)
    		ptr = NULL;
    	else
    		ptr = new int[s];
    }
    CArray::CArray(CArray & a)
    {
    	if (!a.ptr) {
    		ptr = NULL;
    		size = 0;
    		return;
    	}
    	ptr = new int[a.size];
    	memcpy(ptr, a.ptr, sizeof(int) * a.size);
    	size = a.size;
    }
    CArray::~CArray()
    {
    	if (ptr) delete[] ptr;
    }
    CArray & CArray::operator=(const CArray & a)
    { //赋值号的作用是使 = 左边对象中存放的数组的大小和内容都与右边的对象一样
    	if (ptr == a.ptr) //防止 a=a 这样的赋值导致出错
    		return *this;
    	if (a.ptr == NULL) { //如果a里面的数组是空的
    		if (ptr)
    		delete[] ptr;
    		ptr = NULL;
    		size = 0;
    		return *this;
    	}
    	if (size < a.size) { //如果原有空间够大,就不用分配新的空间
    		if (ptr)
    			delete[] ptr;
    		ptr = new int[a.size];
    	}
    	memcpy(ptr, a.ptr, sizeof(int)*a.size);
    	size = a.size;
    	return *this;
    }
    void CArray::push_back(int v)
    { //在数组尾部添加一个元素
    	if (ptr) {
    		int* tmpPtr = new int[size + 1]; //重新分配空间
    		memcpy(tmpPtr, ptr, sizeof(int) * size); //复制原数组内容
    		delete[] ptr;
    		ptr = tmpPtr;
    	}
    	else //数组本来是空的
    		ptr = new int[1];
    	ptr[size++] = v; //加入新的数组元素
    }
    int main()
    {
    	CArray a; //开始的数组是空的
    	for (int i = 0; i<5; ++i)
    		a.push_back(i);
    	CArray a2, a3;
    	a2 = a;
    	for (int i = 0; i<a.length(); ++i)
    		cout << a2[i] << " ";
    	a2 = a3; //a2 是空的
    	for (int i = 0; i<a2.length(); ++i) //a2.length()返回 0
    		cout << a2[i] << " ";
    	cout << endl;
    	a[3] = 100;
    	CArray a4(a);
    	for (int i = 0; i<a4.length(); ++i)
    		cout << a4[i] << " ";
    	return 0;
    }

    程序的输出结果为:

    0 1 2 3 4
    0 1 2 100 4

    []是双目运算符,有两个操作数,一个在里面,一个在外面。表达式 a[i] 等价于 a.operator[](i)。按照[]原有的特性,a[i]应该能够作为左值使用,因此 operator[] 函数应该返回引用。

    思考题:每次在数组尾部添加一个元素都要重新分配内存并且复制原有内容,显然效率是低下的。有什么办法能够加快添加元素的速度呢?

    6 C++重载<<和>>(C++重载输出运算符和输入运算符)

    在 C++ 中,左移运算符<<可以和 cout 一起用于输出,因此也常被称为“流插入运算符”或者“输出运算符”。实际上,<<本来没有这样的功能,之所以能和 cout 一起使用,是因为被重载了。

    cout 是 ostream 类的对象。ostream 类和 cout 都是在头文件 <iostream> 中声明的。ostream 类将<<重载为成员函数,而且重载了多次。为了使cout<<"Star War"能够成立,ostream 类需要将<<进行如下重载:

    ostream & ostream::operator << (const char* s)
    {
        //输出s的代码
        return * this;
    }

    为了使cout<<5;能够成立,ostream 类还需要将<<进行如下重载:

    ostream & ostream::operator << (int n)
    {
        //输出n的代码
        return *this;
    }

    重载函数的返回值类型为 ostream 的引用,并且函数返回 *this,就使得cout<<"Star War"<<5能够成立。有了上面的重载,cout<<"Star War"<<5;就等价于:

    ( cout.operator<<("Star War") ).operator<<(5);

    重载函数返回 *this,使得cout<<"Star War"这个表达式的值依然是 cout(说得更准确一点就是 cout 的引用,等价于 cout),所以能够和<<5继续进行运算。

    cin 是 istream 类的对象,是在头文件 <iostream> 中声明的。istream 类将>>重载为成员函数,因此 cin 才能和>>连用以输入数据。一般也将>>称为“流提取运算符”或者“输入运算符”。

    例题:假定 c 是 Complex 复数类的对象,现在希望写cout<<c;就能以 a+bi 的形式输出 c 的值;写cin>>c;就能从键盘接受 a+bi 形式的输入,并且使得 c.real = a, c.imag = b。

    显然,要对<<>>进行重载,程序如下:

    #include <iostream>
    #include <string>
    #include <cstdlib>
    using namespace std;
    class Complex
    {
    	double real,imag;
    public:
    	Complex( double r=0, double i=0):real(r),imag(i){ };
    	friend ostream & operator<<( ostream & os,const Complex & c);
    	friend istream & operator>>( istream & is,Complex & c);
    };
    ostream & operator<<( ostream & os,const Complex & c)
    {
    	os << c.real << "+" << c.imag << "i"; //以"a+bi"的形式输出
    	return os;
    }
    istream & operator>>( istream & is,Complex & c)
    {
    	string s;
    	is >> s; //将"a+bi"作为字符串读入, "a+bi" 中间不能有空格
    	int pos = s.find("+",0);
    	string sTmp = s.substr(0,pos); //分离出代表实部的字符串
    	c.real = atof(sTmp.c_str());//atof库函数能将const char*指针指向的内容转换成 float
    	sTmp = s.substr(pos+1, s.length()-pos-2); //分离出代表虚部的字符串
    	c.imag = atof(sTmp.c_str());
    	return is;
    }
    int main()
    {
    	Complex c;
    	int n;
    	cin >> c >> n;
    	cout << c << "," << n;
    	return 0;
    }

    程序的运行结果:

    13.2+133i 87
    13.2+133i,87

    因为没有办法修改 ostream 类和 istream 类,所以只能将<<>>重载为全局函数的形式。由于这两个函数需要访问 Complex 类的私有成员,因此在 Complex 类定义中将它们声明为友元。

    cout<<c会被解释成operator<<(cout, c),因此编写 operator<< 函数时,它的两个参数就不难确定了。

    第 13 行,参数 os 只能是 ostream 的引用,而不能是 ostream 对象,因为 ostream 的复制构造函数是私有的,没有办法生成 ostream 参数对象。operator<< 函数的返回值类型设为 ostream &,并且返回 os,就能够实现<<的连续使用,如cout<<c<<5。在本程序中,执行第 34 行的cout<<c进入 operator<< 后,os 引用的就是 cout,因此第 34 行就能产生输出。

    用 cin 读入复数时,对应的输入必须是 a+bi 的格式,而且中间不能有空格,如输入 13.2+33.4i。第 21 行的is>>s;读入一个字符串。假定输入的格式没有错误,那么被读入 s  的就是 a+bi 格式的字符串。

    读入后需要将字符串中的实部 a 和虚部 b 分离出来,分离的办法就是找出被+隔开的两个子串,然后将两个字符串转换成浮点数。第 24 行调用了标准库函数 atof 来将字符串转换为浮点数。该函数的原型是float atof(const char *),它在 <cstdlib> 头文件中声明。

    7 C++重载()(强制类型转换运算符)

    在 C++ 中,类型的名字(包括类的名字)本身也是一种运算符,即类型强制转换运算符。

    类型强制转换运算符是单目运算符,也可以被重载,但只能重载为成员函数,不能重载为全局函数。经过适当重载后,(类型名)对象这个对对象进行强制类型转换的表达式就等价于对象.operator 类型名(),即变成对运算符函数的调用。

    下面的程序对 double 类型强制转换运算符进行了重载。

    #include <iostream>
    using namespace std;
    class Complex
    {
        double real, imag;
    public:
        Complex(double r = 0, double i = 0) :real(r), imag(i) {};
        operator double() { return real; } //重载强制类型转换运算符 double
    };
    int main()
    {
        Complex c(1.2, 3.4);
        cout << (double)c << endl; //输出 1.2
        double n = 2 + c; //等价于 double n = 2 + c. operator double()
        cout << n; //输出 3.2
    }

    程序的输出结果是:

    1.2
    3.2

    第 8 行对 double 运算符进行了重载。重载强制类型转换运算符时,不需要指定返回值类型,因为返回值类型是确定的,就是运算符本身代表的类型,在这里就是 double。

    重载后的效果是,第 13 行的(double)c等价于c.operator double()

    有了对 double 运算符的重载,在本该出现 double 类型的变量或常量的地方,如果出现了一个 Complex 类型的对象,那么该对象的 operator double 成员函数就会被调用,然后取其返回值使用。

    例如第 14 行,编译器认为本行中c这个位置如果出现的是 double 类型的数据,就能够解释得通,而 Complex 类正好重载了 double 运算符,因而本行就等价于:

    double n = 2 + c.operator double();

    8 C++重载++和--(自增和自减运算符)

    自增运算符++、自减运算符--都可以被重载,但是它们有前置、后置之分。

    ++为例,假设 obj 是一个 CDemo 类的对象,++objobj++本应该是不一样的,前者的返回值应该是 obj 被修改后的值,而后者的返回值应该是 obj 被修改前的值。如果如下重载++运算符:

    CDemo & CDemo::operator ++ ()
    {
        //...
        return * this;
    }

    那么不论obj++还是++obj,都等价于obj.operator++()无法体现出差别。

    为了解决这个问题,C++ 规定,在重载++--时,允许写一个增加了无用 int 类型形参的版本,编译器处理++--前置的表达式时,调用参数个数正常的重载函数;处理后置表达式时,调用多出一个参数的重载函数。来看下面的例子:

    #include <iostream>
    using namespace std;
    class CDemo {
    private:
    	int n;
    public:
    	CDemo(int i=0):n(i) { }
    	CDemo & operator++(); //用于前置形式
    	CDemo operator++( int ); //用于后置形式
    	operator int ( ) { return n; }
    	friend CDemo & operator--(CDemo & );
    	friend CDemo operator--(CDemo & ,int);
    };
    CDemo & CDemo::operator++()
    {//前置 ++
    	n ++;
    	return * this;
    }
    CDemo CDemo::operator++(int k )
    { //后置 ++
    	CDemo tmp(*this); //记录修改前的对象
    	n++;
    	return tmp; //返回修改前的对象
    }
    CDemo & operator--(CDemo & d)
    {//前置--
    	d.n--;
    	return d;
    }
    CDemo operator--(CDemo & d,int)
    {//后置--
    	CDemo tmp(d);
    	d.n --;
    	return tmp;
    }
    int main()
    {
    	CDemo d(5);
    	cout << (d++ ) << ","; //等价于 d.operator++(0);
    	cout << d << ",";
    	cout << (++d) << ","; //等价于 d.operator++();
    	cout << d << endl;
    	cout << (d-- ) << ","; //等价于 operator-(d,0);
    	cout << d << ",";
    	cout << (--d) << ","; //等价于 operator-(d);
    	cout << d << endl;
    	return 0;
    }

    程序运行结果:

    5,6,7,7
    7,6,5,5

    本程序将++重载为成员函数,将--重载为全局函数。其实都重载为成员函数更好,这里将--重载为全局函数只是为了说明可以这么做而已。

    调用后置形式的重载函数时,对于那个没用的 int 类型形参,编译器自动以 0 作为实参。 如第 39 行,d++等价于d.operator++(0)

    对比前置++和后置++运算符的重载可以发现,后置++运算符的执行效率比前置的低。因为后置方式的重载函数中要多生成一个局部对象 tmp(第21行),而对象的生成会引发构造函数调用,需要耗费时间。同理,后置--运算符的执行效率也比前置的低。

    前置++运算符的返回值类型是 CDemo &,而后置++运算符的返回值类型是 CDemo,这是因为运算符重载最好保持原运算符的用法。C++ 固有的前置++运算符的返回值本来就是操作数的引用,而后置++运算符的返回值则是操作数值修改前的复制品。例如:

    int a = 5;
    (++a) = 2;

    上面两条语句执行后,a 的值是 2,因为 ++a 的返回值是 a 的引用。而

    (a++) = 2;

    这条语句是非法的,因为 a++ 的返回值不是引用,不能作为左值。

    --运算符的返回值类型的设定和++运算符一样。

    在有的编译器(如Visual Studio)中,如果没有后置形式的重载,则后置形式的自增或自减表达式也被当作前置形式处理。而在有的编译器(如Dev C++)中,不进行后置形式的重载,则后置形式的表达式就会编译出错。

    9 C++运算符重载注意事项以及汇总

    在 C++ 中进行运算符重载时,有以下问题需要注意:

    • 重载后运算符的含义应该符合原有用法习惯。例如重载+运算符,完成的功能就应该类似于做加法,在重载的+运算符中做减法是不合适的。此外,重载应尽量保留运算符原有的特性。
    • C++ 规定,运算符重载不改变运算符的优先级。
    • 以下运算符不能被重载:..*::? :sizeof
    • 重载运算符()[]->、或者赋值运算符=时,只能将它们重载为成员函数,不能重载为全局函数。

    运算符重载的实质是将运算符重载为一个函数,使用运算符的表达式就被解释为对重载函数的调用。

    运算符可以重载为全局函数。此时函数的参数个数就是运算符的操作数个数,运算符的操作数就成为函数的实参。

    运算符也可以重载为成员函数。此时函数的参数个数就是运算符的操作数个数减一,运算符的操作数有一个成为函数作用的对象,其余的成为函数的实参。

    必要时需要重载赋值运算符=,以避免两个对象内部的指针指向同一片存储空间。

    运算符可以重载为全局函数,然后声明为类的友元。

    <<和>>是在 iostream 中被重载,才成为所谓的“流插入运算符”和“流提取运算符”的。

    类型的名字可以作为强制类型转换运算符,也可以被重载为类的成员函数。它能使得对象被自动转换为某种类型。

    自增、自减运算符各有两种重载方式,用于区别前置用法和后置用法。

    运算符重载不改变运算符的优先级。重载运算符时,应该尽量保留运算符原本的特性。

    展开全文
  • win10自动修复重启失败,无法进入桌面,解决办法

    万次阅读 多人点赞 2019-05-20 00:08:19
    请仔细对比是否是同类错误:因为无法验证某个文件的数字签名。 4.然后就重启成功了,进入上图路径种删除该文件  我在看其他博客的时候发现,大部分人提示的文件是360的,一般是装了360杀毒的,我装的是...

    鉴于有些盆友说照着该方法做完后还是没办法重启,我就在开头强调一下吧,该方法适用于同样的错误类型,且最好有一定的计算机基础的人,能明白我下面的解释的盆友,不然自己搞到哪一步都不知道。看了解释还不明白每一步为什么要这样做的就不要瞎搞了。

    解释一下我下面提到的两种错误类型,这两种报错的驱动文件都是第三方驱动文件,我的是金山的驱动文件,我看网上还有很多人是360的驱动文件之类的,所以Windows自动修复没有办法成功修复,需要我们自己进行修复。

    第一种报错很简单,无法验证该驱动文件的签名,那直接跳过验证即可重启;

    第二种报错,驱动文件损坏或丢失,这种情况其实有两种办法:

    一是通过命令行cd到报错文件夹下,ls文件,看是否有报错文件,有的话直接删除,但是我的进去后因为损坏了,没有办法显示该驱动文件,于是就直接采用最简单粗暴的方法u盘重启;(通过命令行操作需要有基础的盆友,不懂的直接用下面的u盘启动)

    (鉴于很多盆友不是很理解的样子,说明一下)u盘重启实际上就相当于将u盘作为系统盘,进入系统后,原来的c盘在这时候就相当于一个存储盘,所以这时候进去c盘删除损坏的驱动就没有任何阻碍了。另外删除驱动的时候不放心可以右键查看驱动信息,看是不是Windows签名的驱动,确认是第三方驱动文件后再删。


    原文:

    本来在搞虚拟机,作死的开了三台,就蓝屏了,然后重启一直显示自动修复失败之类的,打电话给技术支持就只知道说重装,搞了一晚上,终于弄好了,将我的方法分享一下:

     

    1.选择高级选项

    2.后面忘了拍照片,就找到命令行工具,点进去,直接就是管理员运行

       命令行输入:bcdboot c:\windows /l zh-cn

      回车后没什么动静,直接手动重启

    3.重启后进入这个画面,f8进入下一个界面后,选择F7,禁用标签进行启动的选项,跨过数字标签的验证

    注意!请仔细对比是否是同类错误:因为无法验证某个文件的数字签名。

    4.然后就重启成功了,进入上图路径种删除该文件

       我在看其他博客的时候发现,大部分人提示的文件是360的,一般是装了360杀毒的,我装的是金山毒霸,直接卸载就欧克了

     

     

    ps:只要问维修人员,他们一定说重装,也是醉了,还是自己多钻研吧,搞了一晚上,进入桌面的一瞬间我感觉自己的都要哭了

     


    错误提示二的解决办法:

    这个驱动不是Windows自己的驱动,多半是损坏了,所以读不出,我这个还是坑爹的金山的驱动= =

    解决办法:利用u盘制作一个启动盘(搜老毛桃);

    开机启动进入BIOS设置u盘启动,不同品牌用的方式不同,自己在网上搜;

    插入u盘再开机大概是这种页面,一般选择01,02之类的运行什么系统,进入桌面后找到你电脑的系统盘,按照第一张图错误提示路径删除驱动就可以关机了;

    取下u盘,将BIOS的设置还原,再开机;

    若还提示某个驱动报错,说明还没删完,继续重复上述步骤,把报错的驱动删完就行了;

     

     

     

     

    展开全文
  • 为满足煤田地质和地球物理教学实验的需要,...与同类解释软件相比,其优点是界面友好、操作简便、便于移植、易于扩充.该系统目前已获得软件著作权登记,适合在煤炭系统高等院校地质和地球物理学等相关专业教学实践使用.
  • 而且自己有很多直观想法也只是浅显而表面的(自己也不确定对于有的操作解释是否正确)。 那就大体记录下我浏览的部分网站,以及相关的思考和小归纳。 学习重装系统相关链接选记 1.重装系统流程操作: ...

    前言

    原本是想写一篇如何重装系统的文章的,但考虑到自己的水平真的有限,没法做到完整的讲清楚一些装系统的细节问题(真的要深究许多装系统的差异和细节问题的确不是件容易的事)。

    而且自己有很多直观想法也只是浅显而表面的(自己也不确定对于有的操作解释是否正确)。

    那就大体记录下我浏览的部分网站,以及相关的思考和小归纳。

    学习重装系统相关链接选记

    1.重装系统流程操作:

    https://blog.csdn.net/ca1m0921/article/details/79313050

    算是我看到的第一篇较完整介绍和普及重装系统知识的文章了。

    看了这篇文章能对装系统的流程有个较清楚的认识:

    选择系统一>下载镜像文件一>U盘制作启动盘一>电脑开机快捷键进入BIOS一>选择U盘启动一>安装系统一>重启电脑一>下载或选择驱动安装一>下载软件一>进入程序管理卸载 流氓软件

    小结(看了更多文章,也有一些实际操作后的想法):

    这篇文章制作U盘制作启动盘提到的 大白菜 或者 老毛桃(以及其他多数同类软件)会在装系统的时候带“私货”(修改主页,预装软件什么的),所以会有卸载流氓软件的操作。当然如果不是太流氓,这些制作软件确实也挺省事的,要是想装完不会带流氓软件的系统,这里推荐使用:微PE工具箱(http://www.wepe.com.cn/download.html)

    值得一提的是,这篇文章最后推荐的激活软件什么的确实挺不错的

    2.电脑重装系统有哪些方法?4种电脑重装方法集锦

    http://www.kqidong.com/help/2327.html

    这篇文章介绍了当今几个主流的重装系统方法的大体归纳,虽说是篇软文(为了推广自己产品所写的文章),但确实介绍的还不错,让人对于重装系统方式差异有了个基本认识。

    体会
    首先,我是知道一键重装是会带些东西的,但确实是简单方便易操作。所以我第一次实现重装系统就是通过快启动一键重装的。

    当然,结果不出所料,给我绑定了360全家桶,尝试了一些方式删除,但效果不是很好。要是真的不想要360系列,直接设置360不要打开也行,最后,我直接换了个方法重装了(应该是能删掉的,但要多试些方法才知道怎么删才真的彻底,这里我就不研究了)

    3.重装系统 (小白版)

    https://blog.csdn.net/Damon___/article/details/79012265

    这篇文章介绍了用微PE工具箱制作U盘重启器的重装系统办法,总体思路清晰,介绍也比较详细,用的也是没有流氓软件的微PE,但我完全照的这个方法装系统失败了?

    操作体会
    在点击“开始安装”的时候出现了“应用系统映像失败”的相关问题,当时看觉得可能是因为下载的镜像文件没解压的原因,故解压后重试。

    然后还是这个问题报错…当时想着随便点点试一试好了,点了个别的什么的,这倒是没报错了,重启后电脑也打不开了?

    最后在意识到可能是因为“选择引导驱动器”的时候选择C盘导致的(很多电脑选择引导驱动器就是默认的Z盘或别的什么的)。顺便一提,镜像文件不一定要解压,不分区的情况下有人也建议要先将系统盘格式化

    4.使用微PE安装Windows系统——写给小白

    https://blog.csdn.net/TroisJay/article/details/81115853

    不同思路观点都了解一点,我们才能将问题看的更透彻。

    这篇文章总体框架和文章3差不多,不同之处主要就在于具体细节操作,当然这个也是最不好把握的。

    其中(我觉得)比较关键的细节问题有:iso镜像是否有解压的必要?引导驱动器是个啥,怎么选?

    5.Win10问题篇:使用微PE装机。(完整版教程)

    https://blog.csdn.net/qq_15020543/article/details/80842210

    这篇博客前面部分思路和文章3、4差不多,区别在于用的是CGI备份还原(所以下的镜像文件和之前应该也有较明显区别,这里推荐就按博客中的下载方式下系统好了)

    感觉按这篇文章装系统会简单一些(没尝试,推测)

    介绍这篇文章主要是想说明,就算是同个方法同个思路装系统,在具体细节实现上也会有较大差异,所以可见重装系统乃至计算机知识的“深刻”而“繁杂”。

    当然对于我们来说,很多时候“不求甚解”确定也是一个对待问题不错的选择。

    最近愈发的意识到,自己学的越多,越觉得自己知道的太少太少,越发认识到学无止境的具体意义。(已知圈越大,接触未知圈越多嘛)

    6.重装系统哪家最干净?实测结果让你傻眼

    https://pcedu.pconline.com.cn/641/6412336.html

    关于介绍重装系统知识的链接就挂五个好了,不得不承认我们很多时候没有那么多心思在短时间内去很深入一些知识。ㄟ( ▔, ▔ )ㄏ

    这篇文章比较有意思,作者比较有心地去测试了一些重装系统的方式,找出了一些“流氓软件”,让我们更全面看待关于重装系统的问题。

    7.Windows不平凡的25年,经典已成为历史,哪款才是你的最爱?

    https://baijiahao.baidu.com/s?id=1606242583196720621&wfr=spider&for=pc

    再挂一个有意义的链接。

    这篇简短的文章基本介绍清楚了windows的主流产品及其发布时间和用户认可情况,值得一看。

    总结

    其实随便一想想,还有不少问题自己还没了解清楚:例如,Ghost重装系统的介绍,不同镜像文件之间的区别,装完系统后安装驱动的问题等等。

    这暂时先这样吧ㄟ( ▔, ▔ )ㄏ

    展开全文
  • 测试开发需要学习的知识结构

    万次阅读 多人点赞 2018-04-12 10:40:58
    努力成为一个优秀的测试开发从业者,加油!!! 一些视频链接:我这有一些软件测试的视频,你可以点开看看。转行互联网测试需要哪些技能? - 假装在测试的回答 - 知乎作为一名软件测试人员,有哪些网站是你应该多多...

     努力成为一个优秀的测试开发从业者,加油!!!   

    目录

    一、白盒与黑盒测试什么区分

    1、黑盒测试

    2、白盒测试

    3、白盒测试&黑盒测试对比

    4、白盒测试&黑盒测试详细介绍

    黑盒测试

    白盒测试

    二、测试相关经验

    三、测试能力培养

    一、业务分析能力

    二、缺陷洞察能力

    三、团队协作能力

    四、专业技术能力

    五、逻辑思考能力

    六、问题解决能力

    七、沟通表达能力

    八、宏观把控能力


    借楼发个招聘信息:
    【2021 MEGQA-用户质量效能部校园提前批开始啦】
    工作职责:
    -负责百度核心产品的测试工作,如信息流、搜索、百度APP、小程序、好看视频、贴吧等
    -参与产品需求、系统设计和程序代码的评审工作并提出改进意见
    -评估项目质量风险并制定项目测试方案,设计并执行测试用例,跟踪定位产品软件中的缺陷或问题,保证项目质量和进度
    -根据产品和项目特点,提出合理的自动化解决方案,并负责产品线特色化的测试框架和测试工具,运用技术手段提升代码交付的质量和效率
    -参与互联网产品整个工程生产、发布过程中的技术创新,包括研发敏捷研发工具、线上监控系统、性能测试和监督工具等精确评估线上系统表现,以创新的工作模式提升产品的用户价值
    职位要求:
    -计算机相关专业,本科及以上学历
    -能熟练地应用以下一门或几门技术进行相关开发:C/C++/Java/object-c、Linux/Unix Shell、Perl/Python/PHP、JavaScript/Html/Ajax、MySql/Oracle及相关数据库技术等
    -具备快速的产品及业务学习能力,敏捷全面的逻辑思维能力
    -有责任心、敢于担当,工作积极主动,具备良好的团队合作精神,能融入多功能团队并与其他部门同事进行良好的沟通及合作
    -热爱互联网,对互联网相关业务或技术充满好奇及热情;在软件测试领域,对发现、分析及解决问题的工作有浓厚兴趣

    感兴趣的同学可以将简历投递至liujunping@baidu.com

     

    ========================================================================================

    一些视频链接:我这有一些软件测试的视频,你可以点开看看。

    转行互联网测试需要哪些技能? - 假装在测试的回答 - 知乎

    作为一名软件测试人员,有哪些网站是你应该多多关注的,哪些书籍是你必须要看的? - 假装在测试的回答 - 知乎

    一、白盒与黑盒测试什么区分

    1、黑盒测试

    黑盒测试也称功能测试或数据驱动测试,它是在已知产品所应具有的功能,通过测试来检测每个功能是否都能正常使用,在测试时,把程序看作一个不能打开的黑盆子,在完全不考虑程序内部结构和内部特性的情况下,测试者在程序接口进行测试,它只检查程序功能是否按照需求规格说明书的规定正常使用,程序是否能适当地接收输入数锯而产生正确的输出信息,并且保持外部信息(如数据库或文件)的完整性。黑盒测试方法主要有等价类划分、边值分析、因—果图、错误推测等,主要用于软件确认测试。 “黑盒”法着眼于程序外部结构、不考虑内部逻辑结构、针对软件界面和软件功能进行测试。“黑盒”法是穷举输入测试,只有把所有可能的输入都作为测试情况使用,才能以这种方法查出程序中所有的错误。实际上测试情况有无穷多个,人们不仅要测试所有合法的输入,而且还要对那些不合法但是可能的输入进行测试。

    2、白盒测试

    白盒测试也称结构测试或逻辑驱动测试,它是知道产品内部工作过程,可通过测试来检测产品内部动作是否按照规格说明书的规定正常进行,按照程序内部的结构测试程序,检验程序中的每条通路是否都有能按预定要求正确工作,而不顾它的功能,白盒测试的主要方法有逻辑驱动、基路测试等,主要用于软件验证。

    “白盒”法全面了解程序内部逻辑结构、对所有逻辑路径进行测试。“白盒”法是穷举路径测试。在使用这一方案时,测试者必须检查程序的内部结构,从检查程序的逻辑着手,得出测试数据。贯穿程序的独立路径数是天文数字。但即使每条路径都测试了仍然可能有错误。第一,穷举路径测试决不能查出程序违反了设计规范,即程序本身是个错误的程序。第二,穷举路径测试不可能查出程序中因遗漏路径而出错。第三,穷举路径测试可能发现不了一些与数据相关的错误。

    软件人员使用白盒测试方法,主要想对程序模块进行如下的检查:
    – 对程序模块的所有独立的执行路径至少测试一次;
    – 对所有的逻辑判定,取 “ 真 ” 与取 “ 假 ” 的两种情况都至少测试一次;
    – 在循环的边界和运行界限内执行循环体;
    – 测试内部数据结构的有效性,等。
    具体包含的逻辑覆盖有: – 语句覆盖 – 判定覆盖 – 条件覆盖 – 判定-条件覆盖 – 条件组合覆盖 – 路径覆盖。

    3、白盒测试&黑盒测试对比

    白盒测试技术 (White Box Testing) : 深入到代码一级的测试,使用这种技术发现问题最早,效果也是最好的。该技术主要的特征是测试对象进入了代码内部,根据开发人员对代码和对程序的熟悉程度,对有需要的部分进行在软件编码阶段,开发人员根据自己对代码的理解和接触所进行的软件测试叫做白盒测试。这一阶段测试以软件开发人员为主,在 JAVA 平台使用 Xunit 系列工具进行测试, Xunit 测试工具是类一级的测试工具对每一个类和该类的方法进行测试。

    黑盒测试技术( Black Box Testing ):黑盒测试的内容主要有以下几个方面,但是主要还是功能部分。主要是覆盖全部的功能,可以结合兼容,性能测试等方面进行,根据软件需求,设计文档,模拟客户场景随系统进行实际的测试,这种测试技术是使用最多的测试技术涵盖了测试的方方面面,可以考虑以下方面:

    1正确性 (Correctness) :计算结果,命名等方面

    2可用性 (Usability) :是否可以满足软件的需求说明。

    3边界条件 (Boundary Condition) :输入部分的边界值,就是使用一般书中说的等价类划分,试试最大最小和非法数据等等。

    4性能 (Performance) : 正常使用的时间内系统完成一个任务需要的时间,多人同时使用的时候响应时间在可以接受范围内。 J2EE 技术实现的系统在性能方面更是需要照顾的,一般原则是 3 秒以下接受, 3-5 秒可以接受, 5 秒以上就影响易用性了。如果在测试过程中发现性能问题,修复起来是非常艰难的,因为这常常意味着程序的算法不好,结构不好,或者设计有问题。因此在产品开发的开始阶段,就要考虑到软件的性能问题

    5压力测试 (Stress) : 多用户情况可以考虑使用压力测试工具,建议将压力和性能测试结合起来进行。如果有负载平衡的话还要在服务器端打开监测工具 , 查看服务器 CPU 使用率,内存占用情况,如果有必要可以模拟大量数据输入,对硬盘的影响等等信息。如果有必要的话必须进行性能优化 ( 软硬件都可以 ) 。这里的压力测试针对的是某几项功能。

    6错误恢复 (Error Recovery) :错误处理,页面数据验证,包括突然间断电,输入脏数据等。

    7安全性测试 (Security) :这个领域正在研究中,防火墙、补丁包、杀毒软件等的就不必说了,不过可以考虑。破坏性测试时任意看了一些资料后得知 , 这里面设计到的知识 内容可以写本书了 , 不是一两句可以说清的,特别是一些商务网站,或者跟钱有关,或者和公司秘密有关的 web 更是需要这方面的测试,在外国有一种专门干这一行的人叫安全顾问,可以审核代码,提出安全建议,出现紧急事件时的处理办法等,在国内没有听说哪里有专门搞安全技术测试的内容。

    4、白盒测试&黑盒测试详细介绍

    黑盒测试

      · 等价类划分方法
      · 边界值分析
      · 错误推测
      · 因果图方法
      · 判定表驱动分析方法
      · 正交实验设计方法:取正交的测试用例组合
      · 功能图分析方法
    1)等价类划分:
      把所有可能的输入数据,即程序的输入域划分成若干部分,然后从每一个子集中选取少数具有代表性的数据作为测试用例,该方法是一种重要的,常用的黑盒测试 用例设计方法。等价类划分可有两种不同的情况:有效等价类和无效等价类。
      有效等价类:对于程序的规格说明来说是合理的,有意义的输入数据构成的集合。利用有效等价类可检验程序是否实现了规格说明中所规定的功能和性能。
      无效等价类:与有效等价类的定义相反。
    2)边界值分析法:
      边界值分析方法是对等价类划分方法的补充。长期的测试 工作经验告诉我们,大量的错误是发生在输入或者输出范围的边界上,而不是发生在输入输出范围的内部,因此针对各种边界情况设计测试用例,可以查出更多的错误。
      使用边界值分析方法设计测试用例,首先应确定边界情况,通常输入和输出等价类的边界,就是应着重测试的边界情况,应当选取正好等于,刚刚大于或刚刚小于边界的值作为测试数据,而不是选取边界类中的典型值或任意值作为测试数据。
    3)错误推测法:
      基于经验和直觉推测程序中所有可能存在的各种错误,从而有针对性的设计测试用例的方法。
      列举出程序中所有可能有的错误和容易发生错误的特殊情况,根据他们选择测试用例。例如,在 单元测试时列出的许多在模块中常见的错误,以前产品测试中经常发现的错误等,这些就是经验的总结。还有,输入数据和输出数据为零的情况;输入表格为空格或者输入表格只有一行,这些都是容易发生错误的情况,可选这些情况下的例子作为测试用例。
    4)因果图方法:
      前面介绍的等价类划分方法和边界值分析方法,都是着重考虑输入条件,但未考虑输入条件之间的联系。考虑输入条件之间的相互组合,可能会产生一些新的情况,但要检查输入条件的组合意识一件容易的事情,因此必须考虑采用一种适合于描述对于多种条件的组合,相应产生多个动作的形式来考虑设计测试用例,这就需要利用因果图。
    因果图方法最终生成的是判定表,它适合于检查程序输入条件之间的各种组合情况。
    利用因果图生成测试用例的基本步骤:
      (1) 分析软件规格说明描述中, 那些是原因(即输入条件或输入条件的等价类),那些是结果(即输出条件), 并给每个原因和结果赋予一个标识符.
      (2) 分析软件规格说明描述中的语义.找出原因与结果之间, 原因与原因之间对应的关系. 根据这些关系,画出因果图.
      (3) 由于语法或环境限制, 有些原因与原因之间,原因与结果之间的组合情况不不可能出现. 为表明这些特殊情况, 在因果图上用一些记号表明约束或限制条件.
      (4) 把因果图转换为判定表.
      (5) 把判定表的每一列拿出来作为依据,设计测试用例.
      从因果图生成的测试用例(局部,组合关系下的)包括了所有输入数据的取TRUE与取FALSE的情况,构成的测试用例数目达到最少,且测试用例数目随输入数据数目的增加而线性地增加.
      前面因果图方法中已经用到了判定表.判定表(Decision Table)是分析和表达多逻辑条件下执行不同操作的情况下的工具.在程序设计发展的初期,判定表就已被当作编写程序的辅助工具了.由于它可以把复杂的逻辑关系和多种条件组合的情况表达得既具体又明确.
    5)判定表通常由四个部分组成.
      条件桩(Condition Stub):列出了问题得所有条件.通常认为列出得条件的次序无关紧要.
      动作桩(Action Stub):列出了问题规定可能采取的操作.这些操作的排列顺序没有约束.
      条件项(Condition Entry):列出针对它左列条件的取值.在所有可能情况下的真假值.
      动作项(Action Entry):列出在条件项的各种取值情况下应该采取的动作.
      规则:任何一个条件组合的特定取值及其相应要执行的操作.在判定表中贯穿条件项和动作项的一列就是一条规则.显然,判定表中列出多少组条件取值,也就有多少条规则,既条件项和动作项有多少列.
       判定表的建立步骤:(根据软件规格说明)
      ①确定规则的个数.假如有n个条件.每个条件有两个取值(0,1),故有 种规则.
      ②列出所有的条件桩和动作桩.
      ③填入条件项.
      ④填入动作项.等到初始判定表.
      ⑤简化.合并相似规则(相同动作)
      B. Beizer 指出了适合使用判定表设计测试用例的条件:
      ①规格说明以判定表形式给出,或很容易转换成判定表.
      ②条件的排列顺序不会也不影响执行哪些操作.
      ③规则的排列顺序不会也不影响执行哪些操作.
      ④每当某一规则的条件已经满足,并确定要执行的操作后,不必检验别的规则.
      ⑤如果某一规则得到满足要执行多个操作,这些操作的执行顺序无关紧要.

    白盒测试

    白盒测试的方法:总体上分为静态方法和动态方法两大类。

    静态分析是一种不通过执行程序而进行测试的技术。静态分析的关键功能是检查软件的表示和描述是否一致,没有冲突或者没有歧义。

    动态分析的主要特点是当软件系统在模拟的或真实的环境中执行之前、之中和之后 , 对软件系统行为的分析。动态分析包含了程序在受控的环境下使用特定的期望结果进行正式的运行。它显示了一个系统在检查状态下是正确还是不正确。在动态分析技术中,最重要的技术是路径和分支测试。下面要介绍的六种覆盖测试方法属于动态分析方法。

    本文介绍六种白盒子测试方法:(强度由低到高)语句覆盖、判定覆盖、条件覆盖、判定条件覆盖、条件组合覆盖、路径覆盖。

    1)所谓语句覆盖:就是设计若干个测试用例,运行被测程序,使得每一可执行语句至少执行一次。这里的“若干个”,意味着使用测试用例越少越好。语句覆盖率的公式可以表示如下:

    语句覆盖率=被评价到的语句数量/可执行的语句总数 x 100%

    2判定覆盖:使设计的测试用例保证程序中每个判断的每个取值分支(t or f)至少经历一次

    [优点]:判定覆盖具有比语句覆盖更强的测试能力,而且具有和语句覆盖一样的简单性,无需细分每个判定就可以得到测试用例。

    [缺点]:往往大部分的判定语句是由多个逻辑条件组合而成(如,判定语句中包含AND、OR、CASE),若仅仅判断其整个最终结果,而忽略每个条件的取值情况,必然会遗漏部分测试路径。

      例如:

      int a,b;

      if(a || b)

      执行语句1

      else

      执行语句2

    要达到这段程序的判断覆盖,我们采用测试用例:1)a = true , b = true ;2)a = flase, b = flase

    3条件覆盖:条件覆盖是指选择足够的测试用例,使得运行这些测试用例时,判定中每个条件的所有可能结果至少出现一次,但未必能覆盖全部分支

    条件覆盖要检查每个符合谓词的子表达式值为真和假两种情况,要独立衡量每个子表达式的结果,以确保每个子表达式的值为真和假两种情况都被测试到。

    4 判定条件覆盖:判定-条件覆盖就是设计足够的测试用例,使得判断中每个条件的所有可能取值至少执行一次,同时每个判断的所有可能判断结果至少执行,即要求各个判断的所有可能的条件取值组合至少执行一次。

    5) 条件组合覆盖:在白盒测试法中,选择足够的测试用例,使所有判定中各条件判断结果的所有组合至少出现一次,满足这种覆盖标准成为条件组合覆盖。

    6路径覆盖:是每条可能执行到的路径至少执行一次;

     说明:其中语句覆盖是一种最弱的覆盖,判定覆盖和条件覆盖比语句覆盖强,满足判定/条件覆盖标准的测试用例一定也满足判定覆盖、条件覆盖和语句覆盖,条件组合覆盖是除路径覆盖外最强的,路径覆盖也是一种比较强的覆盖,但未必考虑判定条件结果的组合,并不能代替条件覆盖和条件组合覆盖。

    举例:

    if A and B then Action1

    if C or D then Action2

    1)语句覆盖最弱,只需要让程序中的语句都执行一遍即可 。上例中只需设计测试用例使得A=true B=true C=true 即可。

    2)分支覆盖又称判定覆盖:使得程序中每个判断的取真分支和取假分支至少经历一次,即判断的真假均曾被满足。上例需要设计测试用例使其分别满足下列条件即可(1)A=true,B=true,C=true,D=false(2)A=true,B=false,C=false,D=false。

    3)条件覆盖:要使得每个判断中的每个条件的可能取值至少满足一次。上例中第一个判断应考虑到A=true,A=false,B=true,B=false第二个判断应考虑到C=true,C=false,D=true,D=false,所以上例中可以设计测试用例满足下列条件(1)A=true,B=true,C=true,D=true(2)A=false,B=false,C=false,D=false。

    4) 路径覆盖:要求覆盖程序中所有可能的路径。所以可以设计测试用例满足下列条件(1)A=true,B=true,C=true,D=true(2)A=false,B=false,C=false,D=false(3)A=true,B=true,C=false,D=false(4)A=false,B=false,C=true,D=true。

    二、测试相关经验

          测试流程方面我的组长是一位经验丰富的老测试了,到目前已经9年了,我在她的带领下,从最开始的分析需求开始,逐步地跟着项目走完整个测试流程,包括纯手工测试,包含了自动化的测试流程,包含了性能测试的测试流程,直至每一个测试报告的最终形成。使我完全理解了一个科学,正确,严谨,正规化的测试流程。

           测试方法方面我个人特别注重理论知识和实际操作相结合,在理论知识方面,我主要是购买一些书籍,从最基础的软件测试理论到各种各样的程序设计语言,再到自动化测试,包括Java语言的自动化测试,Python语言的自动化测试,到性能测试的各项性能指标的分析,数据分析都是我自己提供书籍上的知识来获得的,在淘宝上面有各种各样的书籍和视频教程,我基本上都看了个遍,到目前为止,我的各种学习资料用了1T的移动硬盘来装,书籍也有一百多本了,在实际操作方面,我主要向我的组长请教,她是因为女生,特别注重细节,当我有不懂得地方就去请教她,我会问她为什么要这么操作,然后我会对比理论和实际的区别,为什么有这种区别。就这样我就通过一个个的项目来夯实理论知识和实际操作,每一次做完项目我都会进行一个总结,自己学到了哪些新的技术和方法?遇到了哪些新的问题?以后再遇到怎么处理?

           新的知识补充方面:随着项目的不同,所运用的知识也不同,每一次学习不同的知识既是工作项目的需要,也是自己学习新知识的契机,比如说学习python语言,本来我们测试人员是不用写代码的,或者说可以用Java写,但是目前市面上都在用python语言来写自动化测试脚本,肯定是有它的道理的,那么我当时给自己的目标并不是仅仅为了满足写自动化脚本那么简单,我还想把python语言全部学会,我下定决心之后就立即着手执行,因为我本来就是开发出身,会代码,所有的语言都是相通的,都有变量,流程控制语句,和方法三大内容。JavaScript和Python都是弱类型,解释性的语言,所以在学习的时候我就在对比起来学习,很快学会了这门语言,所以我个人觉得,不管做什么,我们不仅仅要会用它,而且要知道它为什么这样用?最好是能够精通,对我们的测试工作是十分有利的。

           知识结构方面我们作为一个测试人员,不仅仅要做好本职工作,把自己的测试技术练好,而且还要一个广泛涉猎,对前台,后台,硬件知识,网络知识都应该去学习,对我们快速定位bug,提出有效针对性的修改硬件非常有好处,如果有条件的话,尽量向全栈发展。开发的发展方向是向深度和精度发展,而测试是一个向广度发展的岗位,需要不同的知识来融合,因为我们测试的是一个集成的,有多种技术融合而成的系统项目,就需要我们广泛涉猎和学习,所以从职业规划和寿命度上面来看,测试的工作也是非常的不错,所以不断的学习才是硬道理!

           团队的氛围方面我本人是军人出身,历来重视团结的重要性,所以和开发人员,测试人员,需求人员以及上级相处要从大局出发,我们的每一个人员都是一个项目不可或缺的一份子,必须团结起来,才能为最后产品的顺利交付打好基础条件,所以同事之间的相处是最需要拿捏分寸的,特别是开发人员,人和人都是相互的,只要讲道理,相信别人是会理解的,总之一句话:从整个项目的大局出发,把工作做好。

           回首测试经历,我总结了以下几点:

           1.不断学习,不能丧失对新知识学习的渴望,对旧的知识形成体系,夯实基础,测试理论知识基本上这么多年以来没有变过,主要是一些方法和工具的改变和升级,广泛涉猎相关知识,为测试工作服务;

           2.搞好内部团结,建立起亲密的同事关系,不仅是对个人社交能力还是对自己的工作上的能力都是一个提升,都是百利而无一害的!

    三、测试能力培养

    一、业务分析能力

    1.分析整体业务流程

    不了解整个公司的业务,根本就没办法进行测试

    2.分析被测业务数据

    了解整个业务里面所需的数据有哪些?哪些是需要用户提供的?哪些是自己提供的?有哪些可以是假数据?有哪些必须是真数据?添加数据的时候可以用哪个库?

    明白了整个软件的数据库架构,才能知道哪一个数据是从哪一个表里头带出来的,它的逻辑是什么,有没有连带关系。

    3.分析被测系统架构

    用什么语言开发的?用的是什么服务器?测试它的话需要用什么样的环境进行测试?整体的测试环境是什么样的?

    如果缺少了,需要进行环境搭建,架构搭建。一般去一家新公司之后,架构是搭建好的,了解它即可,熟悉之前的这些老员工们使用什么样的架构去做的。

    4.分析被测业务模块

    整个软件有哪些模块,比如说首页面、注册页面、登录页面、会员页面、商品详情页面、优惠券页面等等

    明白有多少个模块需要测试,每个模块之间的连带关系,进而怎样进行人员分工

    5.分析测试所需资源

    我需要几台计算机,需要几部手机,手机需要什么样的系统,什么样的型号。

    比如测一个网站的性能的时候,电脑的配置达不到测试并发5000人的标准,要么升级电脑的硬件配置,要么多机联合,多机联合时需要几台电脑,都需要提前筹划。

    6.分析测试完成目标

    我的性能目标是什么样的?我的功能目标是什么样的?我要上线达到的上线标准是什么样的?

    性能目标,比如我要达到并发5000人的时候,CPU占用率不能高于70%,内存占用率不能高于60%,响应时间不能超过5秒

    功能目标,比如整体的业务流程都跑通,所有的分支流程都没有问题,所有的接口都能够互相调用,整体的UI界面没有问题,兼容性没有问题等

    把这些问题都弄清楚,测试的思路会非常的清晰

    二、缺陷洞察能力

    1.一般缺陷的发现能力

    至少你要满足一般缺陷的发现能力,这个是最基本的,如果要连最简单的一般的缺陷都发现不了的话,别说优秀测试工程师了,你说你是测试我都不信

    2.隐性问题的发现能力

    在软件的测试过程当中有一些缺陷藏的比较深,有的是性能方面的问题,有的是功能方面的问题,它需要有一些设定特定的条件的情况下才会出现这样的问题。

    比如说买双鞋必须选择的是什么品牌,必须选择是红颜色,必须选择44号,而且必须选择用特定的支付方式才会出现这样的bug的时候,那么这种就属于特别隐性的bug,对于这样的问题的发现能力一定要比别人更强,要找到一些别人可能发现不了的bug

    3.发现连带问题的能力

    当发现了一个缺陷之后,能够想到通过这个缺陷可能会引发其他哪个地方出现问题,这就叫做连带的问题。而不是说发现这一个bug之后提了这一个就算完了,一定要有一个察觉,可能其他地方也存在这样的问题。

    4.发现问题隐患的能力

    有些软件里边可能有一些操作模块,或者是代码写的接口,表面上没有什么问题,但是它是有隐患的,比如说这个接口写的不稳定,当他传的数据有一些问题的时候,可能它最后返回的结果就是报错就是报404或者报乱码。

    5.尽早发现问题的能力

    如果你只能停留在界面级别的话,那你根本就没有办法达到尽早发现问题的这个能力

    你必须要等到前端人员把每个界面都做好了之后才能进入测试,而我能比你早一个月进入测试了,然后我比你结束测试时间快一个月,而你又比我晚一个月,那么咱俩的薪资一下就拉开了

    6.发现问题根源的能力

    需要知道这个缺陷它到底是由什么原因产生的,是属于什么类型的缺陷,是ui前端人员做的问题,还是后台接口人员做的问题?

    不仅要找到这个bug,还要知道这个bug产生的原因,这样的测试人员是非常棒的,而且很是受人尊敬,提bug的方式也就不一样了

    三、团队协作能力

    1.合理进行人员分工

    合理的进行人员分工是提高效率的重要保证

    2.协助组员解决问题

    比如说测试在赶进度,或者这个软件项目的质量把控是一个团队来把控的,协助组员解决问题就显得尤为关键

    3.配合完成测试任务

    一个团队里边的人员分工,他们的任务都是不一样的,这就是咱们说的配合。你的东西做完了,要轮到我了,我的性能测完了之后该轮到你了,所以整个的一个流程下来之后,大家应该是各司其职,配合得非常紧密的一个过程

    4.配合开发重现缺陷

    我给你提bug,你改我的bug,咱们的目的只有一个,就是让这个软件变得更好,所以在这样的情况下,咱们就一定要配合开发

    5.督促项目整体进度

    既然是一个团队协作的过程,就一定要互相的去督促对方,包括督促开发去改bug,因为开发人员他们有时候工作很忙,他们不知道要先改哪些问题,要后改哪些问题,但是往往有一些缺陷,它影响了测试的这个时间,影响了测试的进度,那么这个时候就需要测试员去督促开发人员,让他尽快的去解决你棘手的问题。这个东西能够提高咱们的测试效率

    6.出现问题勇于承担

    愿意背锅的最后都成为了领导,不愿意背锅的最后依然是员工

    四、专业技术能力

    1.掌握测试基础知识

    基础知识就是根基,根基打好了,你才能够更有效地往后期发展,也就是为了以后的学习做一个铺垫。如果根基都没打好,功能测试不会,就想直接学性能,那性能是做不好的

    2.娴熟运用测试工具

    熟悉工具和熟练使用工具完全是两个概念,熟悉工具基本上等同于不会,遇到过很多简历上写会使用什么什么工具,都没有实际能力。比如loadrunner只会一个简单的录制,增强一下脚本,觉得会用了,那知识会用了1/5,其他4/5 都不会。

    3.了解工具操作原理

    它是怎么样给服务器发送请求的,是用什么样的方式去发送请的,是用什么样的方式去监控的,它的操作原理是什么样的,咱们要把这件事情搞清楚,这样的话能有助于更好的去使用这些东西。包括一些请求的协议,每个协议代表什么意思,它是用来干什么的。

    4.自主完成测试任务

    一定要能够自己完成一个独立的内容,独立的工作,这件事情领导你交给我好了,放心我能给你搞定,要的是这样的人

    5.找出问题出现原因

    找出缺陷的时候,不仅要看它的表面,还要看它的本质

    6.提供问题解决方案

    发现问题不是能力,发现问题并提出解决方案才是真的能力

    7.提供完整测试报告

    测试报告能够说明你表达的清不清楚?领导能不能看懂?还有就是能不能够把你整个测试的过程给它梳理得非常详细,人家能够通过你的报告,能够了解到整个的项目的情况,而不是只了解一个片面的情况

    8.了解相关技术领域

    触类旁通

    五、逻辑思考能力

    1.判断逻辑的正确性

    面试官也经常会给测试人去出一些逻辑题,逻辑题能够分析出来你这个人思维有没有?活跃不活跃?还有他的维度,包括他想的问题的全面性,都能够判断得出来。

    比如说去买一样商品,它的里边逻辑就会经常会出现很多问题,比如说它的会员的级别,什么样的级别去买什么样的商品,它的价格不一样,什么情况下会给优惠券,什么样的情况下不给优惠券?达到多少钱的情况下才能够使用优惠券?如果说这里边的逻辑出现了问题的话,那么整个的业务不用再测了

    2.对可行性逻辑分析

    要去测一个网站的逻辑的时候,一定要先思考这一个业务流程可能会涉及到哪些逻辑,这些逻辑哪些是可行的,有些是正向逻辑,有些是逆向逻辑,都要考虑全面,而不是说只是把正向的逻辑测试全面了,逆向逻辑不考虑。其实往往更容易出错的地方就是逆向逻辑

    3.思维导图梳理思路

    思维导图工具能够起到什么作用,能够让你更有效的进行测试,能够让你的思路更清晰

    4.站在客观角度思考

    去测试的时候,不要仅仅只是站在测试人员的角度上去对整个网站进行测试,还更多的要站在用户的角度,要替用户考虑

    六、问题解决能力

    1.技术上的问题

    把自己的个人能力提升起来,多跟别人虚心请教,多去自己想办法解决问题

    2.工作中的问题

    在任何的企业里边去工作,肯定会遇到一些工作当中的一些不愉快的事情,而不是什么事情都会让你很顺心。所以要去处理工作上的一些不顺心的事情,不要把它带到你的工作上,或者是你的生活上,尽可能的去跟别人沟通,去解决这个工作上遇到的麻烦

    3.同事间的问题

    在工作当中可能会涉及到跟开发人员的沟通,跟产品人员的沟通,跟ui人员的沟通,跟这三方的人员去沟通的时候,就要用不同的沟通方式

    4.领导层的问题

    如果你觉得你的领导不好,或者说你觉得对你的领导一些建议,不要的去跟同事之间去说他坏话或者怎么样的,领导需要的是解决问题的人,而不是制造问题的人

    七、沟通表达能力

    1.和技术人员的沟通

    跟开发人员阐述缺陷时要简洁明了、清晰易懂。当发现严重缺陷时,也不要大惊小怪,要站在开发人员的角度思考如何解决问题。而不是踩在开发头上,炫耀自己发现问题的能力。

    2.和产品人员的沟通

    当对产品提出意见时,要站在用户的角度去说明自己的想法,而不要主观认为不好而要求产品进行修改。

    3.和上级领导的沟通

    跟领导沟通时要有大局观,不能只考虑自己部门的情况。并且与领导沟通时,尽量直奔主题,不要拐弯抹角,当与领导意见不一致时,也不要直接反驳,应该先给予认可,再阐述自己的想法。

    4.在集体会议中沟通

    在集体会议中不要一味的突出自己的个人能力,不要当话痨,也不要默默无闻。适当的提出一些自己的见解,有助于让大家更加重视你的存在。切记不要在多人会议中,去指责别人和推卸问题。各个部门的同事,都要面子~

    5.与下级员工的沟通

    与下级沟通时不要摆高姿态,不要让下级产生畏惧感,应该更多的为下级解决问题。服务好部门的同事,才能更好的产生凝聚力。

    八、宏观把控能力

    1.有效控制测试时间

    测试周期的时间控制,应当采取多种方法去衡量,例如人员能力,人员数量,项目复杂程度,同类项目的测试经验等多方面去衡量。

    2.有效控制测试成本

    测试成本指的是人员成本跟时间成本,不要浪费每个人的时间跟劳动力,要让每个人充分发挥最大的价值。

    3.有效制定测试计划

    测试计划对于一个项目是核心关键,它的存在为了让测试进行中有依据可查。所以测试计划,一定要切合实际情况,要经过思考和衡量最后得出计划安排。

    4.有效控制组员情绪

    组员的情绪可以直接影响测试进度跟测试的质量,当有组员出现思想问题时,应当及时沟通,采取一些必要的措施去解决问题。而不能装看不见。

    5.有效进行风险评估

    任何项目在进行期间都存在许多潜在的风险,例如,人员离职,生病请假,业务变更,需求变更,服务器或其他组件故障等。应当提前做出相应的解决方案,以免到时候手忙脚乱。

    6.有效控制测试方向

    测试的方向是指测试的目标和测试的范围,很多项目的测试是有针对性的,例如性能测试,所以在测试中,一定要随时清楚测试的目标和目的是什么,以免把时间浪费在无关紧要的业务上。

    展开全文
  • 深度学习入门

    万次阅读 多人点赞 2017-11-05 21:23:46
      就此,有人批评深度学习就是一个黑箱(Black Box)系统,其性能很好,却不知道为何而好,也就是说,缺乏解释性。其实,这是由于深度学习所处的知识象限决定的。从图1可以看出,深度学习,在本质上,属于可统计不...
  • 《数据库原理》— 数据库系统概论第五版习题解析

    万次阅读 多人点赞 2017-05-29 14:57:48
    实体型:具有相同属性的实体具有相同的特征和性质,用实体名及其属性名集合来抽象和刻画同类实体,称为实体型。实体集:同型实体的集合称为实体集;实体之间的联系:通常是指不同实体型的实体集之间的联系,实体之间...
  • 机器学习算法 综述(入门)

    万次阅读 多人点赞 2019-06-16 21:59:28
    在这个算法中,我们将总体分成两个或更多的同类群。这是根据 属性或者自变量 来分成尽可能不同的组别。  如图所示,根据一些 feature(特征) 进行分类,每个节点提一个问题,通过判断,将数据分为两类,再...
  • 进程和线程的区别(超详细)

    万次阅读 多人点赞 2019-10-03 21:57:46
    进程和线程 进程 一个在内存中运行的应用程序。每个进程都有自己独立的一块内存空间,一个进程可以有多个线程,比如...与进程不同的是同类的多个线程共享进程的堆和方法区资源,但每个线程有自己的程序计数器、虚拟...
  • 数据库系统概论(第五版) 王珊 第一章课后习题答案

    千次阅读 多人点赞 2019-12-23 16:18:55
    实体型:具有相同属性的实体具有相同的特征和性质,用实体名及其属性名集合来抽象和刻画同类实体,称为实体型。实体集:同型实体的集合称为实体集;实体之间的联系:通常是指不同实体型的实体集之间的联系,实体之间...
  • WPF开发教程

    万次阅读 多人点赞 2019-07-02 23:13:20
    ------WPF开发教程 目录 WPF基础入门....... 1. WPF基础之体系结构......2. WPF基础之XAML....3. WPF基础之基元素......4. WPF基础之属性系统......5. WPF基础之路由事件......6. WPF基础之布局系统......7. WPF基础之样式设置和模板...
  • 编译执行和解释执行

    万次阅读 多人点赞 2021-01-06 21:52:20
    一、编译和解释 编译:将源代码一次性转换成目标代码的过程 类似英语中的全文翻译。 执行编译过程的程序叫做编译器。 解释:将源代码逐条转换成目标代码同时逐条运行的过程。 类似英语中的同声传译。 ...
  • 解释一下:每个进程需要x个资源,初始时给每个进程 x-1 个资源,最后再给一个额外的资源,没当一个进程运行完后马上释放其占有的资源,其它进程可以申请资源继续运行。 例题 某系统采用3个并发进程,都需要同类资源3...
  • 爬取同类标签方法

    千次阅读 2018-03-07 23:09:53
    这个就简单一点,首先要会用select这个方法,这个也比较方便,然后我会在最下面放解释这个方法的链接 用的是属性查找,注意:类名查找时有 . (点),然后结尾要有一个空格 不然会报错,然后 > 是进入子标签 差不多...
  • 基于物品的协同过滤算法实现图书推荐系统

    万次阅读 多人点赞 2019-09-14 21:20:24
    5)在python的标准库中,含有多个模块来实现具体的功能,这些几乎包含了所有与操作系统解释器的交互的功能,也就是Python使用者不用手动人工造轮子,因为这些模块可以直接用于已经完全测试过的功能开发。这些已经被...
  • 心理辅导平台设计

    千次阅读 2017-12-04 10:22:57
    (3)市场相关产品、同类产品调查: 2.3、评价尺度: 系统进行评价时所使用的主要尺度为各项功能的优先次序、开发时间的长短及使用的难易程度和用户的交互性。 2.4、对现有系统的分析 当我们心情不好或者有心理...
  • 关于类成员函数中访问同类对象的私有成员   原址:http://runfeel.iteye.com/blog/1904768 关于类成员函数中访问同类对象的私有成员,主要包含以下几种场景: a. 在C++的类的...
  • 知道了事务的一些知识后,下面说一下@Transactional注解的信息(大家着重看一下4 5 6条的解释) 1.在需要事务管理的地方加@Transactional 注解。@Transactional 注解可以被应用于接口定义和接口方法、类定义和类...
  • 摘要FLoC 为基于兴趣的广告选择提供了隐私保护机制。当用户浏览网络时,浏览器会使用 FLoC 算法得出其 "兴趣同类群组"。凡是具有短期相似浏览记录的浏览器,得出的结果都...
  • 乔治敦大学继续研究学院的数据科学证书的同类群组23顶点项目 团队成员:Griffin,Ermina,Yephet和Aidan 自述内容 完成后添加内容并带有指向各节的链接 抽象的 添加摘要 背景资料 说明项目背景 资料采购 谈谈我们在...
  • 推荐算法分类

    千次阅读 2018-10-24 15:25:11
    基于物品聚类的话,则是将用户评分高物品的相似同类物品推荐给用户。 常用的聚类推荐算法有K-Means, BIRCH, DBSCAN和谱聚类。 3.3 用分类算法做协同过滤 如果我们根据用户评分的高低,将分数分成几段的话,则...
  • 机器学习的名词解释

    千次阅读 2017-10-24 14:50:37
    一种调整后期预测的结构,通常用于解释预测偏差。调整后的预期和概率必须匹配一个观察标签集的分布。 候选采样(candidate sampling) 一种优化训练时间的,使用 Softmax 等算法计算所有正标签的概率,...
  • 同类数组 1.x 当前 面向对象的功能(类,属性,方法,可能是多态等) Java互操作性 清单 字典/地图 异构数组 2.x 计划 功能特点 枚举 调试实用程序和断点 ? 计划 性能提升 自己的字节码编译器和解释器 如何使用...
  • 直至它被处理(父级对象所有同类事件都将被激活),或者它到达了对象层次的最顶层,即document对象(有些浏览器是window)。 如何来阻止Jquery事件冒泡? 通过一个小例子来解释 <!DOCTYPE ...
  • 英语单词辨析(同类单词)

    千次阅读 2008-09-16 23:00:00
    cause 指造成某一事实或现象的直接原因,后接of sth./doing sth,reason用来解释某种现象或结果的理由,后接for sth./doing sth. the reason for being late 14. exercise, exercises, practice ...
  • (四)Linux设备驱动之多个同类设备共用一套驱动 (五)Linux设备驱动模型介绍 (六)Linux驱动子系统-I2C子系统 (七)Linux驱动子系统-SPI子系统 (八)Linux驱动子系统-PWM子系统 (九)Linux驱动子系统-...
  • C++常用名词解释汇总

    千次阅读 2018-03-04 16:58:02
    C++常用名词解释汇总new运算符:对象创建的操作符。delete运算符:对象释放操作符,触发析构函数。内存泄露:操作堆内存时,如果分配了内存,就有责任回收它,否则这块内存就无法重新使用,称为内存泄漏。自动变量:...
  • 万能的图神经网络解释器 GNNExplainer

    千次阅读 2020-01-30 19:10:42
    ,即:同类节点之间共享的图模式. Experiments 作者在合成/真实数据集上验证了GNNExplainer在多种任务上的解释能力. 下图是在合成数据集上的结果.可以看出相对于Grad和Att, GNNExplainer可以更好的抽取出与...
  • 深度学习-----缺乏可解释

    千次阅读 2019-10-09 16:50:42
    深度学习在本质上属于可统计不可推理的范“可统计”是很容易理解的,就是说,对于同类数据,它具有一定的统计规律,这是一切统计学习的基本假设那“不可推理”又是什么意思?其实就是“剪不断、理还乱”的非线性状态...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 36,326
精华内容 14,530
关键字:

同类的解释