1、int型变量赋值给指针是非法的,但允许把0或编译时可获得0值的const量赋给指针
根据测试,上面那句话的意思是:
const int address=10 ;
int *ds=address;
或者
int *ds=10;
报错。
const int address=0;
int *ds=address;
或者
int *ds=0;
不报错
但是,我在DEVC++中测试指针时,以下代码出错:
int *ds=0;
cout<<*ds;
即输出空的指程序崩溃:空指针 是指向内存位置0的指针 ,输出一个空指针即 访问内存地址0 ,通常这个位置 是操作系统的, 在某些系统中 内存地址0是不可读不可写的, 在某些系统中 ,内存地址0是可读但不可写的,所以说 会是程序崩溃 这个说法有些片面
2、“野指针”不是NULL指针,是指向“垃圾”内存(不可用内存)的指针。
空指针 是指向内存位置0的指针 输出一个空指针 讲访问这个空指针 即 访问内存地址0 通常这个位置 是操作系统的 在某些系统中 内存地址0是不可读不可写的 在某些系统中 内存地址0是可读但不可写的,所以说 会是程序崩溃 这个说法有些片面
3、void* 指针只支持几种有限的操作:
与另一个指针进行比较;
向函数传递
void* 指针或从函数返回 void* 指针;
给另一个 void* 指针赋值。
不允许使用 void* 指针操纵它所指向的对象。
4、
-21 % -8; // ok: result is -5
21 % -5; // machine-dependent: result is 1 or -4
-21 / -8; // ok: result is 2
21 / -5; // machine-dependent: result -4 or -5
5、不应该串接使用关系操作符
关系操作符(<、<=、>、<=)具有左结合特性。事实上,由于关系操作符返
回bool类型的结果,因此很少使用其左结合特性。如果把多个关系操作符串接
起来使用,结果往往出乎预料:
// oops! this condition does not determine if the 3 values are unequal
if (i < j < k) { /* ... */ }
以上写法错误。
6、千万不能返回局部变量的引用,和返回局部对象的引用一样,返回指向局部对象的指针也是错误的
7、主函数 main 不能调用自身。
8、默认实参
string screenInit(string::size_type height = 24, string::size_type width = 80, char background = ' ' );
默认实参的初始化式
默认实参可以是任何适当类型的表达式:
string::size_type screenHeight();
string::size_type screenWidth(string::size_type);
char screenDefault(char = ' ');
string screenInit(
string::size_type height = screenHeight(),
string::size_type width = screenWidth(screenHeight()),
char background = screenDefault());
如果默认实参是一个表达式,而且默认值用作实参,则在调用函数时求解该表达
式。例如,每次不带第三个实参调用函数 screenInit 时,编译器都会调用函数
screenDefault 为 background 获得一个值。
注意事项:
如果在函数定义的形参表中提供默认实参,那么只有在包含该函数定义的源文件
中调用该函数时,默认实参才是有效的。
9、区别函数的多次声明和函数的重载。
函数的多次声明:如果两个函数声明的返回类型和形参表完全匹配,则将第二个函数声明视为第一个的重复声明。如果两个函数的形参表完全相同,但返回类型不同,则第二个声明是错误的。
函数重载:函数重载是指在同一作用域内,可以有一组具有相同函数名,不同参数列表的函数,这组函数被称为重载函数。重载函数通常用来命名一组功能相似的函数,这样做减少了函数名的数量,避免了名字空间的污染,对于程序的可读性有很大的好处。main()函数不能重载!函数重载,要求返回值类型一样,如果返回值类型不一样,不会报错,但已不是原来函数的重载,而是另一个函数了。
10、如果容器存储类类型的对象,那么只有当其元素类型提供默认构造函数时,容器才能使用这种构造函数。尽管有一些类没有提供默认构造函数,但大多数类类型都会有。例如,假设类 Foo 没有默认构造函数,但提供了需要一个 int 型形参的构造函数。现在,考虑下面的声明:
vector<Foo> empty; // ok: no need for element defaultconstructor
vector<Foo> bad(10); // error: no default constructor for Foo
vector<Foo> ok(10, 1); // ok: each element initialized to 1
注意,在指定容器元素为容器类型时,必须如下使用空格:
vector< vector<string> > lines; // ok: space required between close>
vector< vector<string>> lines; // error: >> treated as shiftoperator
必须用空格隔开两个相邻的 > 符号,以示这是两个分开的符 号,否则,系统会认为 >> 是单个符号,为右移操作符,并导 致编译时错误。
关键概念:容器元素都是副本
在容器中添加元素时,系统是将元素值复制到容器里。类似地,使用一段元素初始化新容器时,新容器存放的是原始元素的副本。被复制的原始值与新容器中的元素各不相关,此后,容器内元素值发生变化时,被复制的原值不会受到影响,反之亦然。
在 vector 容器中添加元素可能会导致整个容器的重新加载,这样的话,该容器涉及的所有迭代器都会失效。即使需要重新加载整个容器,指向新插入元素后面的那个元素的迭代器也会失效。 任何 insert 或 push 操作都可能导致迭代器失效。当编写循环将元素插入到 vector 或 deque 容器中时,程序必须确保迭代器在每次循环后都得到更新。
//
swap 操作实现交换两个容器内所有元素的功能。要交换的容器的类型必须匹配:操作数必须是相同类型的容器,而且所存储的元素类型也必须相同。调用了 swap 函数后,右操作数原来存储的元素被存放在左操作数中,反之亦然。
vector<string> svec1(10); // vector with 10 elements
vector<string> svec2(24); // vector with 24 elements
svec1.swap(svec2);
执行 swap 后,容器 svec1 中存储 24 个 string 类型的元素,而 svec2 则存储 10 个元素。
swap方法实现过程中可能是交换了指针,应该不是各个元素依次交换。
//
为了支持快速的随机访问,vector 容器的元素以连续的方式存放——每一个元素都紧挨着前一个元素存储。
已知元素是连续存储的,当我们在容器内添加一个元素时,想想会发生什么事情:如果容器中已经没有空间容纳新的元素,此时,由于元素必须连续存储以便索引访问,所以不能在内存中随便找个地方存储这个新元素。于是,vector 必须重新分配存储空间,用来存放原来的元素以及新添加的元素:存放在旧存储空间中的元素被复制到新存储空间里,接着插入新元素,最后撤销旧的存储空间。如果 vector 容器在在每次添加新元素时,都要这么分配和撤销内存空间,其性能将会非常慢,简直无法接受。
//
选择容器的提示
下面列举了一些选择容器类型的法则:
_ 1. 如果程序要求随机访问元素,则应使用 vector 或 deque 容器。
_2. 如果程序必须在容器的中间位置插入或删除元素,则应采用 list 容器。
_3. 如果程序不是在容器的中间位置,而是在容器首部或尾部插入或删除元素,则应采用 deque 容器。
_4. 如果只需在读取输入时在容器的中间位置插入元素,然后需要随机访问元 素,则可考虑在输入时将元素读入到一个 list 容器,接着对此容器重新 排序,使其适合顺序访问,然后将排序后的 list 容器复制到一个 vector容器。
11、使用泛型算法必须包含 algorithm 头文件:
#include <algorithm>
标准库还定义了一组泛化的算术算法(generalized numeric algorithm),其命名习惯与泛型算法相同。使用这些算法则必须包含 numeric 头文件:
#include <numeric>
泛型算法:
_1、find与find_first_of
find:
int search_value = 42; vector<int>::const_iterator result = find(vec.begin(), vec.end(), search_value);
find_first_of :
find_first_of 算法带有两对迭代器参数来标记两段元素范围,在第一段范围内查找与第二段范围中任意元素匹配的元素,然后返回一个迭代器,指向第一个匹配的元素。如果找不到元素,则返回第一个范围的 end 迭代器。
list<string>::iterator it = roster1.begin();
while ((it = find_first_of(it, roster1.end(),roster2.begin(), roster2.end())) != roster1.end()) {......操作}
_2、另一个简单的只读算法是 accumulate,该算法在 numeric 头文件中定义。假设 vec 是一个 int 型的 vector 对象,下面的代码:
// sum the elements in vec starting the summation with the value 42
int sum = accumulate(vec.begin(), vec.end(), 42);
将 sum 设置为 vec 的元素之和再加上 42。accumulate 带有三个形参。头两个形参指定要累加的元素范围。第三个形参则是累加的初值。
_3、fill函数与fill_n函数
fill函数:写入操作:fill(vec.begin(),vec.end(),10);把迭代器范围内的数据写为10.
fill_n 函数: fill_n 函数带有的参数包括:一个迭代器、一个计数器以及一个值。该函数从迭代器指向的元素开始,将指定数量的元素设置为给定的值。fill_n 函数假定对指定数量的元素做写操作是安全的。初学者常犯的错误的是:在没有元素的空容器上调用 fill_n 函数(或者类似的写元素算法)。以下代码将会出错:
vector<int> vec; // empty vector
fill_n(vec.begin(), 10, 0);
为了 确保算法有足够的元素存储输出数据的一种方法是使用插入迭代器back_inserter。插入迭代器是可以给基础容器添加元素的迭代器,使用 back_inserter 的程序必须包含 iterator 头文件。fill_n (back_inserter(vec), 10, 0);这样便不会出现上述错误。
_4、 copy、replace和replace_copy
copy:
copy (ilst.begin(), ilst.end(), back_inserter(ivec));copy 从输入范围中读取元素,然后将它们复制给目标 ivec。
当然,这个例子的效率比较差:通常,如果要以一个已存在的容器为副本创建新容器,更好的方法是直接用输入范围作为新构造容器的初始化式:
vector<int> ivec(ilst.begin(), ilst.end());
replace:
每一个等于第一值的元素替换成第二个值。 replace(ilst.begin(), ilst.end(), 0, 42);这个调用将所有值为 0 的实例替换成 42。
replace_copy:
如果不想改变原来的序列,则调用 replace_copy。这个算法接受第三个迭代器实参,指定保存调整后序列的目标位置。
vector<int> ivec; // use back_inserter to grow destination as needed
replace_copy (ilst.begin(), ilst.end(), back_inserter(ivec), 0, 42);
调用该函数后,ilst 没有改变,ivec 存储 ilst 一份副本,而 ilst 内所有的 0 在 ivec 中都变成了 42。
_5、对容器元素的重新排序算法
sort:sort 算法带有两个迭代器实参,指出要排序的元素范围。这个算法在容器各个元素之间使用小于(<)操作符比较元素。
sort(words.begin(), words.end());
unique :
让容器中的所有元素都只保留一个副本。unique 算法很适合用于解决这个问题,它带有两个指定元素范围的迭代器参数。该算法“删除”相邻的重复元素,然后重新排列输入范围内的元素,并且返回一个迭代器,表示无重复的值范围的结束。
注意:调用 unique“删除”了相邻的重复值。给“删除”加上引号是因为 unique 实际上并没有删除任何元素,而是将无重复的元素复制到序列的前端,从而覆盖相邻的重复元素。unique 返回的迭代器指向超出无重复的元素范围末端的下一位置。实际上容器中元素的个数并没有改变。
erase:用于容器中元素的删除操作算法不直接修改容器的大小。erase属于容器操作,不属于泛型算法,要添加或删除元素,则必 须使用容器操作。
words.erase(end_unique, words.end());这个函数调用从 end_unique 指向的元素开始删除,直到 words 的最后一个元素也删除掉为止。
12、 static 成员是类的组成部分但不是任何对象的组成部分,因此,static 成员函数没有 this 指针。
13、 类也可以定义 mutable 或 static 成员。mutable 成员永远都不能为const;它的值可以在 const 成员函数中修改。static 成员可以是函数或数据,独立于类类型的对象而存在。
14、不管类是否定义了自己的析构函数,编译器都自动执行类中非static 数据成员的析构函数。
15、友元可以访问类的 private 和 protected 数据。 友元关系不能继承。基类的友元对派生类的成员没有特殊访问权限。如果基类被授予友元关系,则只有基类具有特殊访问权限,该基类的派生类不能访问授予友元关系的类。
16、 如果基类定义 static 成员,则整个继承层次中只有一个这样的成员。无论从基类派生出多少个派生类,每个 static 成员只有一个实例。static 成员遵循常规访问控制:如果成员在基类中为 private,则派生类不能访问它。假定可以访问成员,则既可以通过基类访问 static 成员,也可以通过派生类访问 static 成员。一般而言,既可以使用作用域操作符也可以使用点或箭头成员访问操作符。
struct Base
{
static void statmem(); // public by default
};
struct Derived : Base
{
void f(const Derived&);
};
void Derived::f(const Derived &derived_obj)
{
Base::statmem(); // ok: Base defines statmem
Derived::statmem(); // ok: Derived in herits statmem
// ok: derived objects can be used to access static from base
derived_obj.statmem(); // accessed through Derived object
statmem(); // accessed through this class
}
17、派生类到基类的转换
像继承的成员函数一样,从派生类到基类的转换可能是也可能不是可访问的。转换是否访问取决于在派生类的派生列表中指定的访问标号。
要确定到基类的转换是否可访问,可以考虑基类的 public 成员是否访问,如果可以,转换是可访问的,否则,转换是 不可访问的。
如果是 public 继承,则用户代码和后代类都可以使用派生类到基类的转换。如果类是使用 private 或 protected 继承派生的,则用户代码不能将派生类型对象转换为基类对象。如果是 private 继承,则从 private 继承类派生的类不能转换为基类。如果是 protected 继承,则后续派生类的成员可以转换为基类类型。 无论是什么派生访问标号,派生类本身都可以访问基类的 public 成员,因此,派生类本身的成员和友元总是可以访问派生类到基类的转换。
18、纯虚函数