C++primer 相关学习3

1.因为一旦某个形参被赋予了默认值,那么它之后的形参都必须要有默认值。

(a) int ff(int a, int b = 0, int c = 0);           //正确
(b) char *init(int ht = 24, int wd, char bckgrnd); //错误

2.constexpr函数的返回值类型及所有形参都得是字面值类型。

3.函数匹配

void f();
void f(int);
void f(int,int);
void f(double,double = 3.14);

(a) f(2.56, 42)
(b) f(42)
(c) f(42, 0)
(d) f(2.56, 3.14)

(a) void f(int, int); 和 void f(double, double = 3.14); 是可行函数。该调用具有二义性而不合法。
(b) void f(int); 是可行函数。调用合法。
(c) void f(int, int); 和 void f(double, double = 3.14); 是可行函数。void f(int, int); 是最佳匹配。
(d) void f(int, int); 和 void f(double, double = 3.14); 是可行函数。void f(double, double = 3.14); 是最佳匹配。

4.有无const修饰的函数的重载 

(a)(b)都是合法的重载,(c)是非法的重载,顶层const不影响函数传入的对象。

(a) int calc(int&, int&); 
	int calc(const int&, const int&); 
(b) int calc(char*, char*);
	int calc(const char*, const char*);
(c) int calc(char*, char*);
	int calc(char* const, char* const);

5.函数指针

如果想要声明一个函数指针,只需要用指针替换函数名即可。

bool (*pf)(const string &,const string &)  该函数的类型是bool 
                                          观察pf前面有个*,所以pf是个指针,右侧是形参列表,所            
                                           以证明指向的是一个函数,在观察左边,发现函数的返回值 
                                           是一个bool类型的。所以,pf是指向函数的指针,其中,该 
                                           函数的参数是两个const类型的引用,返回值是bool类型。
bool *pf(const string &,const string &)// 表明pf是一个函数名,返回的是为bool指针的函数。

6.返回函数指针的函数

int (*f1(int))(int *,int);  从内向外阅读声明,f1有形参列表,f1是函数,f1前面有*,所以f1返回的是 
                            一个指针,进一步观察,指针的类型本身也包含形参列表,因 
                            为指针指向函数,该函数的返回值是int.

7.

bool is_empty(string &s){
    return s.empty();
}
一般对于不需要改变的代码,将其设置为const string &s,否则字符创和常量引用将无法赋值。

8.常量指针和常量引用不过是指针或者引用自以为是罢了,他们觉得自己指向了常量,所以自觉地不改变对象的值,但是对象可以通过其他的方式进行更改。

9.extern:在每个用到的文件中需要用多个extern 声明;
include:只需要在include各声明一次,其它使用这些变量的只需要包含该头文件即可.

大体上,你可以把extern 和 include 的区别当做是“零售”与“批发”的区别。include是批发,而extern 则是零售。

10.constexpr函数的返回值类型及所有形参都得是字面值类型。内联函数的声明和定义都放在头文件中

11.

初始化是直接初始化数据成员,而赋值是先初始化再赋值。

class constref{
public:
    constref(int ii);
private:
    int i;
    const int ci;
    int &ri;
} 

constref::constref(int ii)
{
    i = ii;//正确
    ci= ii;//错误,不能给ci赋值
    ri =i;//错误 ,没有初始化
}
//正确的方式应该是这样
constref::constref(int ii):i(ii),ci(ii),ri(i){}

12.让构造函数初始值的顺序和成员声明的顺序保持一致

13.constexpr 函数必须包含一个返回语句。constexpr函数的参数和返回值必须是字面值类型。

14.如果在一个循环中插入或者删除deque string vector 中的元素,不要缓存end返回的迭代器。

15.

程序使用动态内存有一下三种原因:
1.程序不知道自己需要使用多少对象。
2.程序不知道所需对象的精准类型
3.程序需要在多个对象间共享内存

16.

拷贝构造函数初始化发生在以下的地方
1.将一个对象作为实参传递给一个非引用类型的形参
2.从一个返回类型为非引用类型的函数返回一个对象
3.用花括号列表初始化一个数组中的元素或者一个聚合类中的成员。

17.三五法则

1。需要拷贝构造函数的类也需要拷贝和赋值操作

2。需要拷贝操作的类也需要赋值操作,反之亦然。

18.变量是左值,因此我们不能把一个右值引用直接绑定到一个变量上,即使这个变量是右值引用也不行。

19.什么情况下一定要用初始化列表

  • 常量成员,因为常量只能初始化不能赋值,所以必须放在初始化列表里面
  • 引用类型,引用必须在定义的时候初始化,并且不能重新赋值,所以也要写在初始化列表里面
  • 没有默认构造函数的类类型,因为使用初始化列表可以不必调用默认构造函数来初始化,而是直接调用拷贝构造函数初始化

20.

浅谈对象的初始化顺序

1.没有继承情况下的初始化顺序

package InitializationOrder;
/**
 * 没有继承的初始化顺序
 * @author TANJIAYI
 *
 */
public class Test4 {
    public static void main(String[] args) {
        new Order();
    }
}
class Order{
    AAA s = new AAA("成员变量");
    static AAA a = new AAA("静态成员变量");
    {
        System.out.println("初始化块");
    }
    static{
        System.out.println("静态初始化块");
    }
     Order(){
        System.out.println("构造方法");
    }
    
}
class AAA{
    public AAA(String str){
        System.out.println(str);
    }
}
 

  输出结果:

静态成员变量

静态初始化块

成员变量

初始化块

构造方法      

结论:在没有继承的条件下,实例化一个对象,构造的先后顺序是,静态成员变量>静态初始化块>成员变量>初始化块>构造方法

一般顺序为 先静态,后非静态,先变量,后初始化块,再是构造方法

2.经典面试题

直接上代码:

package InitializationOrder;
/**
 * 初始化顺序  静态变量  静态初始化块  成员变量 初始化块  构造函数
 * @author TANJIAYI
 *
 */
public class Test1 {
    
        public static Test1 t1 = new Test1("t1");//第1步    
        public static int k = 0;
        public static Test1 t2 = new Test1("t2");//第2步
        public static int i = print("i");//第3步
        public static int n = 99;//第4步
        public int j = print("j");//第6步
                {
                    print("构造");//第7步
                }
                static{
                    print("静态");//第5步
                }
        public Test1(String str){
            System.out.println((++k)+":"+str+"  i="+i+" n="+n);
            ++i;
            ++n;
        }
        private static int print(String str) {
            System.out.println((++k)+":"+str+"  i="+i+" n="+n);//1:j i=0 n=0
            ++n;
            return ++i;
        }
        public static void main(String[] args) {
            Test1 test = new Test1("init");//第8步
        }
}
  

输出结果为:

1:j  i=0 n=0

2:构造  i=1 n=1

3:t1  i=2 n=2

1:j  i=3 n=3

2:构造  i=4 n=4

3:t2  i=5 n=5

4:i  i=6 n=6

5:静态  i=7 n=99

6:j  i=8 n=100

7:构造  i=9 n=101

8:init  i=10 n=102

解题思路:

(1)   按照对象初始化顺序依次执行,首先静态变量从代码中的第九行到第13行依次执行。

(2)   执行第1步,调用new Test1()方法,本方法是个构造方法,在执行前,类加载的时候先把k,i和n的值加载进来,初始值为0,接着执行第14行成员变量j;调用print()方法,把j赋给str,所以27行代码打印第一条输出:1:j i=0 n=0,在执行初始化块,15—17行,打印出2:构造 i=1 n=1,最后在执行Test1()构造方法,打印出  3:t1 i=2 n=2,第1步语句执行完毕,接着执行第2步。

(3)   分析同上,逐步打印出答案。

为了好理解,下面附上一张图,挨着挨着看思路还是很清楚的哦!图画得有点儿乱:

 

总结:大家只要掌握好执行的先后顺序,仔细分析题,就没有问题。

2.继承情况下对象的初始化顺序

属性、方法、构造方法和自由块都是类中的成员,在创建类的对象时,类中各成员的执行顺序:
1.父类静态成员和静态初始化快,按在代码中出现的顺序依次执行。
2.子类静态成员和静态初始化块,按在代码中出现的顺序依次执行。
3. 父类的实例成员和实例初始化块,按在代码中出现的顺序依次执行。
4. 执行父类的构造方法。
5.子类实例成员和实例初始化块,按在代码中出现的顺序依次执行。
6.执行子类的构造方法。

package InitializationOrder;
/**
 * 继承下的初始化顺序
 * @author TANJIAYI
 *
 */
public class Test2 {
    public static void main(String[] args) {
        new Son();
    }
}
class Parent{  
      
    {  
        System.out.println("parent中的初始化块");  
    }  
    static{  
        System.out.println("parent中static初始化块");  
    }  
      
    public Parent(){  
        System.out.println("parent构造方法");  
    }  
}  
  
class Son extends Parent{  
    {  
        System.out.println("son中的初始化块");  
    }  
      
    static{  
        System.out.println("son中的static初始化块");  
    }  
      
    public Son(){  
        System.out.println("son构造方法");  
    }  
}
 
 

输出结果:

  1. 初始化块主要用于对象的初始化操作,在创建对象时调用,可以用于完成初始化属性值、加载其他的类的功能。
  2. 初始化块和构造方法功能类似,可以再创建对象的时候完成一些初始化的操作,一般的情况下,构造方法初始化和初始化块初始化可以通用。
  3. 构造方法在初始化的时候可以通过参数传递,但是初始化块不能,初始化块的初始化在构造方法之前执行,如果搞糟方法多次重载,此时可以考虑构造方法中共通的代码放到初始化块中进行初始化。

补充:

静态初始化块和非静态初始化块的区别?

  1. 非静态初始化块主要是用于对象的初始化操作,在每次创建对象的时都要调用一次,其执行顺序在构造方法之前。
  2. 在初始化块之前有static修饰,则为静态初始化块。由于非静态成员不能再静态方法中使用,同样也不能在静态初始化块中,因此,静态初始化块主要用于初始化静态变量和静态方法,静态初始化块只调用一次,是在类的第一次加载到内存时,并非一定要创建对象才执行。
  3. 静态初始化块比非静态初始化块先执行

猜你喜欢

转载自blog.csdn.net/qq_35736364/article/details/103907805
今日推荐