阅高质量C++/C 编程——知识小结

这几天用了晚上些空余时间,将林锐的高质量C++/C编程这本书看了一遍,为了加深印象,总结下一些之前不太清楚的知识点,和一些比较重要的知识点。

(1)文件结构:

防止头文件重复引用:

方式一:

#ifndef __SOMEFILE_H__
#define __SOMEFILE_H__
 ... ... // 声明、定义语句
#endif
方式二:

#pragma once
    ... ... // 声明、定义语句

两种存在细微的区别:

#ifndef方式受C++/C语言标准支持,其原理是根据头文件的宏判定是否有重复定义,所以编译器每次都要打开头文件,编译时间稍长

#pragma once是根据头文件在物理上的一个文件是否相同来判定重复定义的,编译时间是短了,但是可能一个头文件有多份copy,就不能排除了,而且#pragma once 有些老的编译器是不支持的,兼容性不好

(2)类中常量:

不能在类声明中初始化 const 数据成员。以下用法是错误的,因为类的对象未被创建时,编译器不知道 SIZE的值是什么。只有在类的对象创建时,才会对初始化类中的数据成员 
 

 class A 
 {… 
    const int SIZE = 100;  // 错误,企图在类声明中初始化 const 数据成员 
    int array[SIZE];    // 错误,未知的 SIZE 
 };
const 数据成员的初始化只能在类构造函数的初始化表中进行,例如 
 
 class A 
 {… 
    A(int size);    // 构造函数 
    const int SIZE ;    
 }; 
 A::A(int size) : SIZE(size)  // 构造函数的初始化表 
 { 
   … 
 } 
  A  a(100);  // 对象 a 的 SIZE 值为 100 
  A  b(200);  // 对象 b 的 SIZE 值为 200
怎样才能建立在整个类中都恒定的常量呢?别指望 const 数据成员了,应该用类中
的枚举常量来实现。例如

class A 
 {… 
    enum { SIZE1 = 100, SIZE2 = 200}; // 枚举常量 
    int array1[SIZE1];  
    int array2[SIZE2]; 
  }; 
枚举常量不会占用对象的存储空间,它们在编译时被全部求值。枚举常量的缺点是:
它的隐含数据类型是整数,其最大值有限,且不能表示浮点数(如 PI=3.14159) 

(3)函数设计是参数传递时应遵守的规则:

【1】:如果参数是指针,且仅作输入用,则应在类型前加 const,以防止该指针在函数体内被意外修改

【2】:如果输入参数以值传递的方式传递对象,则宜改用“const &”方式来传递,这样可以省去临时对象的构造和析构过程,从而提高效率

【3】:避免函数有太多的参数,参数个数尽量控制在 5 个以内。如果参数太多,在使用时容易将参数类型或顺序搞错。

(4)值传递和引用传递:

class String 
{… 
  // 赋值函数 
  String & operate=(const String &other);   
  // 相加函数,如果没有 friend 修饰则只许有一个右侧参数 
  friend String   operate+( const String &s1, const String &s2);  
private: 
  char *m_data;  
}

String 的赋值函数 operate =  的实现如下: 

String & String:: operate =(const String &other)
{
	if(this == &other) 
	{
		return *this;
	}
        delete[] m_date;
	m_date = new char[strlen(other.m_date) + 1];
	strcpy(m_date,other.m_date);
	return *this;
}

String 的赋值函数,应当用“引用传递”的方式返回 String 对象。如果用“值传递”的方式,虽然功能仍然正确,但由于 return 语句要把 *this 拷贝到保存返回值的外部存储单元之中,增加了不必要的开销,降低了赋值函数的效率

这赋值函数第一步:检查自赋值,可能你感觉多此一举,但是如果不对自赋值做处理,第二步:delete自己把自己杀了后还能复制自己吗?应马上终止函数,同时注意不要将if(this== &other) 错写成if(*this== other)

第二步:delete 这里要是不释放掉,以后就没机会了

第三步:分配新的内存资源,并复制字符串,注意函数 strlen返回的是有效字符函数 串长度,不包含结束符‘\0’ ,strcpy 则连‘\0’一起复制。

第四步:返回本对象的引用,那么能否写成 return other 呢?效果不是一样吗? 
不可以!因为我们不知道参数 other 的生命期。有可能 other 是个临时对象,在赋值结束后它马上消失,那么 return other 返回的将是垃圾。


String 的相加函数 operate +  的实现如下: 

String  operate+(const String &s1, const String &s2)   
{ 
  String temp; 
  delete temp.data;  // temp.data 是仅含‘\0’的字符串 
  temp.data = new char[strlen(s1.data) + strlen(s2.data) +1]; 
  strcpy(temp.data, s1.data); 
  strcat(temp.data, s2.data); 
  return temp; 
} 
对于相加函数,应当用“值传递”的方式返回 String 对象。如果改用“引用传递” ,那么函数返回值是一个指向局部对象 temp 的“引用” 。由 于 temp 在函数结束时被自动销毁,将导致返回的“引用”无效

(5)内存指针的一些问题:

内存分配方式有三种: 

(1) 从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static 变量。 
(2) 在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。 
(3) 从堆上分配,亦称动态内存分配。程序在运行的时候用 malloc 或 new 申请任意多少的内存,程序员自己负责在何时用 free 或 delete 释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多。 


计算内存容量 
  用运算符 sizeof 可以计算出数组的容量(字节数) 。sizeof("hello world")的值是 12(注意别忘了’\0’) 。指针 p 指向 a,但是 sizeof(p)的值却是 4。这是因为sizeof(p)得到的是一个指针变量的字节数,相当于 sizeof(char*),而不是 p 所指的内存容量。C++/C语言没有办法知道指针所指的内存容量,除非在申请内存时记住它


【1】:return 语句不可返回指向“栈内存”的“指针”或者“引用” ,因为该内存在函体结束时被自动销毁。例如 

 char * Func(void) 
 { 
    char str[] = “hello world”;  // str 的内存位于栈上 
   … 
    return str;    // 将导致错误 
 } 
将上面的例子改成下面的,会怎么样?
char *GetString2(void) 
{ 
  char *p = "hello world";
  return p; 
} 
void Test5(void) 
{ 
  char *str = NULL; 
  str = GetString2(); 
  cout<< str << endl; 
} 

函数 Test5 运行虽然不会出错,但是函数 GetString2 的设计概念却是错误的。因为GetString2 内的“hello world”是常量字符串,位于静态存储区,它在程序生命期内恒定不变。无论什么时候调用 GetString2,它返回的始终是同一个“只读”的内存块。 如果str指向了这块静态存储区,str[0] = 'X'试图去修改静态存储区的内容就会导致错误


【2】指针操作超越了变量的作用范围。这种情况让人防不胜防

class A  
{   
public: 
  void Func(void){ cout << “Func of class A” << endl; } 
}; 
void Test(void) 
{ 
  A  *p; 
  { 
      A  a; 
      p = &a; // 注意 a 的生命期 
  } 
  p->Func();    // p 是“野指针” 
}

(6)内联函数:提高函数的执行效率 (C程序里用宏代码提高执行效率)

但是C++为啥要不继续用宏代码,相比下内联函数有啥优点:C++ 语言的函数内联机制既具备宏代码的效率,又增加了安全性,而且可以自由操作类的私有数据成员

 C++ 的“函数内联”是如何工作的。对于任何内联函数,编译器在符号表里放入函数的声明(包括名字、参数类型、返回值类型) 。如果编译器没有发现内联函数存在错误,那么该函数的代码也被放入符号表里。在调用一个内联函数时,编译器首先检查调用是否正确(进行类型安全检查,或者进行自动类型转换,当然对所有的函数都一样) 。如果正确,内联函数的代码就会直接替换函数调用,于是省去了函数调用的开销。这个过程与预处理有显著的不同,因为预处理器不能进行类型安全检查,或者进行自动类型转换。假如内联函数是成员函数,对象的地址(this)会被放在合适的地方,这也是预处理器办不到的。


inline 是一种“用于实现的关键字” ,而不是一种“用于声明的关键字”,即声明时可不加inline关键字,但是实现的时候必须加inline关键字

定义在类声明之中的成员函数将自动地成为内联函数,例如 
 class A 
 { 
public: 
    void Foo(int x, int y) { … }   // 自动地成为内联函数 
 }


内联的缺点:

内联是以代码膨胀(复制)为代价,仅仅省去了函数调用的开销,从而提高函数的执行效率。如果执行函数体内代码的时间,相比于函数调用的开销较大,那么效率的收获会很少。另一方面,每一处内联函数的调用都要复制代码,将使程序的总代码量增大,消耗更多的内存空间。


大多是C/C++上的一些基础性问题,但是你深入探究还是会发现很多问题,这本书之前看过一遍然而再次看的时候还是收获不少,在此做个总结,希望自个对一些基础性问题能更加了解,加深印象

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值