c++ #include使用引号""和尖括号<>的区别:
c++中#include头文件有两种形式,一种是使用尖括号<>,一种是使用双引号""。
#include为预处理语句,在程序的其它编译处理之前,先进行这些语句的处理。
#include <xxx.h>
系统自带的头文件用尖括号括起来,这样编译器会在系统文件目录下寻找
#include "xxx.h"
用户自定义的头文件用双括号括起来,编译器首先会在用户目录下寻找,然后再到C++的安装目录中查找(Linux中可以通过环境变量来设定),最后在系统文件中查找
总结:包含C++提供的头文件时,使用尖括号。#include "XXX.h"命令则是先在当前文件所在的目录搜索是否有符合的文件,如果没有再到系统文件夹里去找对应的头文件。
#include <iostream>和#include <iostream.h>:
#include <iostream> //1998年标准化以后的标准头文件
#include <iostream.h> //标准化以前的头文件
更本质的区别是iostream把标准C++库的组件放在一个名为std的namespace里面。而相对的iostream.h则将这些标准组件放在全局空间里,同时在标准化以后旧有的C标准库也已经经过改造了。
c++标准化的过程中,解决了以下问题:
(1)C++增加了名称空间的概念,借以将原来声明在全局空间下的标识符声明在了namespace std下
(2) 统一C++各种后缀名,如.h、.hpp、.hxx等。标准化之前的头文件就是带后缀名的文件,标准化后的头文件就是不带后缀名的文件。(旧版仍兼容)
即带.h的头文件是旧标准的,如果想用新标准的头文件就不要带.h
为了和C语言兼容,C++标准化的过程中,原有C语言头文件标准化后,头文件名前带个c字母,如cstdio、cstring、ctime、ctype等。
即如果要用C++标准化了的C语言头文件,就要做如下转换:
#include <stdio.h> -----> #include <cstdio>
#include <stdlib.h> -----> #include <cstdlib>
#include <string.h> ------> #include <cstring>
#include <iostream.h>是 C语言中比较通用的
#include <iostream>
using namespace std; 是C++中比较通用的。iostream包含的基本功能和对应的旧头文件相同,但头文件的内容在名字空间std中。
c++ 栈和队列:
栈stack先进后出,队列queue先进先出
1、c++中stack和queue是容器吗?使用的stack是哪个版本的STL?
stack和queue不被归类为容器,被归类为container adapter(容器适配器)。
栈是以底层容器完成其所有的工作,对外提供统一的接口,底层容器时可插拔的(也就是说可以控制使用哪种容器来实现栈的功能)
2、使用的STL中的stack/queue是如何实现的?
栈的底层实现可以是vector、deque、list,主要就是数组和链表的底层实现。
我们常用的SGI STL,如果没有指定底层实现的话,默认是以deque为缺省情况下的栈或队列的底层结构
也可以指定vector为栈的底层实现,初始化语句:
std::stack<int, std::vector<int>> third; //使用vector为底层容器的栈
3、stack提供迭代器来遍历stack空间吗?
栈提供push和pop等等接口,所有元素必须符合先进后出规则,所以栈不提供走访功能,也不提供迭代器(iterator)。
c++类中相关知识:
创建对象:
//stu在栈上分配内存,创建对象 Student stu; Student *pStu = &stu; //在堆上创建对象,使用new关键字 //使用new在堆上创建出来的对象是匿名的,没法直接使用,必须要用一个指针指向它,再借助指针来访问它的成员变量或成员函数 Student *pStu = new Student;
栈内存是程序自动管理的,不能使用delete删除在栈上创建的对象;堆内存由程序员管理,对象使用完毕后可以通过delete删除。
new和delete往往成对出现,以保证及时删除不再使用的对象,防止无用内存堆积。
栈(Stack)和堆(Heap): 待补充
在类体中直接定义函数时,不需要在函数名前加上类名;当成员函数定义在类外时,就必须在函数名前加上类名予以限定。
:: 被称为域解析符,用来连接类名和函数名,指名当前函数属于哪个类
在类体中和类体外定义成员函数的区别:在类体中定义的成员函数会自动成为内联函数(inline),在类体外定义的不会。建议在类体内部对成员函数作声明,而在类体外部进行定义。
如果希望将函数定义在类体外部,又希望它是内联函数,可以在定义函数时加 inline 关键字。
成员(变量、函数)访问权限:public、protected、private (C++中只能修饰类的成员,不能修饰类,类没有共有私有之分)
在类的内部,无论成员被声明为public、protected、private,都是可以互相访问的,没有访问权限的限制。
在类的外部,只能通过对象访问成员,并且通过对象只能访问public属性的成员,不能访问private、protected属性的成员。
private关键字的作用在于更好的隐藏类的内部实现,该向外暴露的接口(能通过对象访问的成员)都声明为public,不希望外部知道、或者只在类内部使用的、或者对外部没有影响的成员,都声明为private。
实际项目开发中的成员变量以及只在类内部使用的成员函数都建议声明为private,只将允许通过对象调用的成员函数声明为public。(体现类的封装性,尽量隐藏类的内部实现,只向用户提供有用的成员函数)
既不写private也不写public,就默认为private。
构造函数:
如果用户自己没有定义构造函数,那么编译器会自动生成一个默认的构造函数,只是这个构造函数的函数体是空的,没有形参也不执行任何操作。一旦用户自己定义了构造函数,编译器都不再自动生成。
调用没有参数的构造函数可以省略括号。
Student stu() 或者 Student stu Student *pstu = new Student() 或者 Student *pstu = new Student
构造函数的调用时强制性的,一旦在类中定义了构造函数,那么创建函数时就一定要调用,不调用是错误的。
构造函数没有返回值,因为没有变量来接收返回值。
1、不管是声明还是定义,函数名千米那都不能出现返回值类型,即使是void也不允许。
2、函数体不能有return语句
构造函数初始化列表还有一个重要作用就是初始化const成员变量,初始化const成员变量的唯一办法就是使用初始化列表。
析构函数:
创建对象时系统会自动调用构造函数进行初始化工作,销毁对象时系统也会自动调用一个函数来进行清理工作,例如释放分配的内存、关闭打开的文件,这个函数就是析构函数(Destructor)。是一种特殊的成员函数,没有返回值,不需要程序员显式调用,而是在销毁对象时自动执行。
析构函数没有参数,不能被重载,因此一个类只有一个析构函数,如果用户没有定义,编译器会自动生成一个默认的析构函数
C++中的new和delete分别用来分配和释放内存,他们与C语言中malloc()、free()最大的一个不同之处在于:用new分配内存时会调用构造函数,用delete释放内存时会调用析构函数。
析构函数的执行时机:
析构函数在对象被销毁时调用,而对象的销毁时机与它所在的内存区域有关。
-
在所有函数之外创建的对象是全局对象,它和全局变量类似,位于内存分区中的全局数据区,程序在结束执行时会调用这些对象的析构函数。
-
在函数内部创建的对象是局部对象,它和局部变量类似,位于栈区,函数执行结束时才会调用这些对象的析构函数。
-
new创建的对象位于堆区,通过delete删除时才会调用析构函数;如果没有delete,析构函数就不会执行。
this指针:
this指针是一个const指针,指向当前对象,通过它可以访问当前对象的所有成员。this只能用在类的内部,通过this可以访问类的所有成员。只有当对象被创建后this才有意义,因此不能在static成员函数中使用。
this的实质:this实际上是成员函数的一个形参,在调用成员函数时将对象的地址作为实参传递给this。不过this这个形参是隐式的,他并不出现在代码中,而是在编译阶段由编译器将它添加到参数列表中。
this作为隐式形参,本质上是成员函数的局部变量,所以只能用在成员函数的内部,并且只有通过对象调用成员函数时才给this赋值。
static关键字:
类和结构体外的static:
类和结构体内的static:用static修饰的成员变量(成员函数)是静态成员变量(静态成员函数)。
使用静态成员变量可以实现多个对象共享数据。static成员属于类,不属于某个具体的对象,即使创建多个对象,也只为静态成员变量分配一块内存,所有对象使用的都是这份内存中的数据。当某个对象修改了 m_total,也会影响到其他对象。
const关键字:
const可以修饰成员变量和成员函数。
const成员变量:初始化const成员变量只有一种方法,就是通过构造函数的初始化列表。
const成员函数(常成员函数):const成员函数可以使用类中所有的成员变量,但是不能修改它们的值。(get函数),要在声明和定义的时候在函数头部的结尾加上const关键字。
区分const位置:
-
函数开头的const用来修饰函数的返回值,表示返回值是const类型,也就是不能被修改 const char * getname()
-
函数头部的结尾加上const表示常成员函数,这种函数只能读取常成员变量的值,不能修改成员变量的值。如char * getname() const
const也可以用来修饰对象,称为常对象。一旦将对象定义为常对象之后,就只能调用类的const成员(包括const成员变量和const成员函数)
friend关键字:友元函数和友元类
借助友元(friend),可以使得其他类中的成员函数以及全局范围内的函数访问当前类的private成员。
在当前类以外定义的、不属于当前类的函数也可以在类中声明,但在前面要加friend关键字,构成友元函数。友元函数可以是不属于任何类的非成员函数,也可以是其他类的成员函数。
友元函数可以访问当前类中的所有成员,包括public、protected、private属性的。
友元函数不同于类的成员函数,在友元函数中不能直接访问类的成员,必须要借助对象。
继承(inheritance)&派生(derive):一个类从另一个类获取成员变量和成员函数。
类的构造函数不能被继承,派生类的构造函数会调用基类的构造函数。
析构函数也不能被继承。
构造函数执行顺序:
基类构造函数->派生类构造函数
析构函数执行顺序:
派生类析构函数->基类析构函数
继承方式:
class 派生类名:[继承方式] 基类名{ 派生类新增加的成员 };
继承方式有public、private、protected,如果不写,默认为private
1、private和protected的成员不能直接使用成员访问运算符(.)来直接访问,public属性的成员可以直接用(.)来访问 (能不能直接使用对象来访问)
2、成员和类的默认访问修饰符是private
3、共有成员在程序中类的外部是可访问的,可以不使用任何成员函数来设置和获取共有变量的值。私有成员或者函数在类的外部是不可访问的,甚至是不可查看的,只有类和友元函数可以访问私有成员
protected(受保护的)成员变量或者函数与私有成员十分类似,但protected成员在子类中是可访问的。
4、三种修饰类型在继承中的特点:(基类成员在派生类中的房屋内权限)
存在继承关系时,基类中的protected成员可以再派生类中使用,基类中的private 成员不能在派生类中使用。
继承方式 | 基类public成员 | 基类protected成员 | 基类private成员 | 继承引起的访问控制关系变化 |
---|---|---|---|---|
public继承 | public | protected | private(不可见) | 基类的非私有成员在子类的访问属性不变 |
protected继承 | protected | protected | private(不可见) | 基类的非私有成员都为子类的保护成员 |
private继承 | private(不可见) | private(不可见) | private(不可见) | 基类中的非私有成员都为子类的私有成员 |
(1)private成员只能被本类成员和友元访问,不能被派生类访问
(2)protected成员可以被派生类访问
1) 基类成员在派生类中的访问权限不得高于继承方式中指定的权限。例如,当继承方式为 protected 时,那么基类成员在派生类中的访问权限最高也为 protected,高于 protected 的会降级为 protected,但低于 protected 不会升级。再如,当继承方式为 public 时,那么基类成员在派生类中的访问权限将保持不变。
也就是说,继承方式中的 public、protected、private 是用来指明基类成员在派生类中的最高访问权限的。
2) 不管继承方式如何,基类中的 private 成员在派生类中始终不能使用(不能在派生类的成员函数中访问或调用)。
3) 如果希望基类的成员能够被派生类继承并且毫无障碍地使用,那么这些成员只能声明为 public 或 protected;只有那些不希望在派生类中使用的成员才声明为 private。
4) 如果希望基类的成员既不向外暴露(不能通过对象访问),还能在派生类中使用,那么只能声明为 protected。
在派生类中访问基类 private 成员的唯一方法就是借助基类的非 private 成员函数,如果基类没有非 private 成员函数,那么该成员在派生类中将无法访问。
基类成员函数和派生类成员函数不构成重载,派生类中成员函数灰遮蔽基类中所有的同名函数。
5、拷贝构造函数:是一种特殊的构造函数,它在创建对象时,是使用同一类中之前创建的对象来初始化新创建的对象。通常用于:
(1)通过使用另一个类型的对象来初始化新创建的对象
(2)复制对象把它作为参数传递给函数
(3)复制对象,并从函数返回这个对象
classname (const classname &obj) { //构造函数 }
6、类可以看做是一种复杂的数据类型,也可以使用 sizeof 求得该类型的大小。从运行结果可以看出,在计算类这种类型的大小时,只计算了成员变量的大小,并没有把成员函数也包含在内
c++继承时的对象内存模型:
没有继承时:
成员变量和成员函数会分开存储
1、对象的内存中只包含成员变量,存储在栈区或堆区(使用new创建对象)
2、成员函数与对象内存分离,存储在代码区
继承时:
派生类的内存模型可以看成是基类成员变量和新增成员变量的总和,而所有的成员函数仍然存储在另一个区域-代码区。
编译器通过指针的指向来访问成员变量,但不通过指针的指向来访问成员函数,通过指针的类型来访问成员函数。(即通过基类指针只能访问派生类的成员变量,不能)
多态(polymorphism)可以分为编译时多态和运行时多态。
c++提供多态的目的:可以通过基类指针对所有派生类(包括直接派生和间接派生)的成员变量和成员函数进行“全方位”的访问,尤其是成员函数,没有多态,只能访问成员变量。
编译时多态:指函数的重载(包括运算符重载)、对重载函数的调用
运行时多态:继承、虚函数
虚函数的原因:使用基类指针指向派生类对象时,只能访问派生类的成员变量,但不能访问派生类的成员函数,故增加了虚函数,在函数声明前面加上virtual关键字。(函数声明加,函数定义可加可不加)
构成多态的条件:
1、存在继承关系
2、继承关系中有同名的虚函数,并且是覆盖关系(函数原型相同)
3、存在基类的指针,通过该指针调用虚函数
通过基类的指针只能访问从基类继承过去的成员,不能访问派生类新增的成员。
纯虚函数:
virtual 返回值类型 函数名 (函数参数) = 0; 只有函数声明,没有函数体,包含纯虚函数的类为抽象类(Abstract class),无法实例化创建对象。
通过指针访问类的成员函数时:
-
如果该函数是非虚函数,编译器会根据指针类型找到该函数。即指针是哪个类的类型就调用哪个类的函数
-
如果该函数是虚函数,并且派生类有同名的函数遮蔽它,编译器会根据指针的指向找到该函数。即指针指向的对象属于哪个类就调用哪个类的函数。
这就是多态(Polymorphism)。
如果一个类包含了虚函数,创建该类的对象时就会额外增加一个数组,数组中的每一个元素都是虚函数的入口地址。虚函数表(Virtual function table),vtable
RTTI机制 (C++运行时类型识别机制)
在程序运行后确定对象类型信息的机制称为RTTI机制,只有类中包含了虚函数才会启用RTTI机制,其他所有情况都可以在编译阶段确定类型信息。
CPU访问内存时需要的是地址,而不是变量名和函数名!变量名和函数名只是地址的一种助记符,当源文件被编译和执行成可执行程序后,它们都会被替换成地址。
静态绑定(Static binding) 和 动态绑定(dynamic binding):
在编译期间(包括链接期间)找到函数名对应的地址
在编译期间不能确定,必须要等到程序运行后根据具体的环境或者用户操作才能决定
防止头文件被重复包含:
例如:#include "a.h" #include "b.h" ,如果都包含了一个头文件 x.h ,那么x.h在此被包含了两次。
第一种:
使用 宏定义避免重复引入。其中,_HEADERNAME_H是宏的名称
#ifndef _HEADERNAME_H
#define _HEADERNAME_H
...//(头文件内容)
#endif
当程序中第一次#include该文件时,由于_HEADERNAME_H尚未定义,所以会定义_HEADERNAME_H并执行“头文件内容”部分的代码;当发生多次#include时,因为前面已经定义了_HEADERNAME_H,所以不会再重复执行“头文件内容”部分的代码。
第二种:
使用#pragma once避免重复引入
#pragma once指定当前文件在构建时只被包含(或打开)一次,这样可以减少构建的时间,因为加入#pragma once后,编译器在打开或读取第一个#include模块后,就不会打开或读取随后出现的同#include模块。
#pragma once只能作用于某个具体的文件,无法像#ifndef那样仅作用于指定的一段代码
两者比较:
#pragma once特点是编译效率高,但可移植性差(编译器不支持,会发出警告,但不会中断程序的执行)
#ifndef特点是可移植性高,编译效率差
关于main函数:
C99标准main函数只有两种定义方式:
int main(void) //不需要从命令行中获取参数
int main(int argc, char *argv[])
main函数的返回值必须是int,这样返回值才能传递给程序的调用者(操作系统)
main函数的返回值说明程序的退出状态,返回0表示程序正常退出;返回非0表示程序异常退出。
C++98定义:
int main()
int main(int argc, char *argv[])
两个形参的解释:第一个int argc是记录在命令行上的字符串个数;第二个*argc[]是个指针数组,存放输入在命令行上的命令(字符串)。
没有规定过void main(void)这种写法!!!
关于void和void类型的指针(void *):
void关键字的使用规则:
-
如果函数没有返回值,那么应该声明为void类型;
-
如果函数无参数,那么应声明其参数为void;
-
如果函数的参数可以是任意类型指针,那么应声明其参数为void * ;
-
void不能代表一个真实的变量
void的含义
void字面类型为"无类型",void * 则为"无类型指针",void * 可以指向任何类型的数据 void几乎只有"注释"和限制程序的作用
void的作用在于:(1)对函数返回的限定;(2)对函数参数的限定
如果指针p1和p2的类型相同,可以直接在p1和