来源于慕课网教程
https://www.imooc.com/u/1349694/courses?sort=publish
一.
- 随用随定义变量
int v1 = 1;
v1 = v1 + 1;
int v2 = 2; // v1 v2在用到的时候才定义
v2 = v2 + v1;
cin >> x >> y;
不能cin >> x , y;
cout << oct << x ;
以八进制输出x
cout << dec << x ;
十进制
cout << hex << x ;
十六进制
namesapce
用于创建命名空间。using namespace xxx
用于使用某个命名空间,可以直接用里面的变量,代码,而不需要再加xxx::。如下面的fun2的使用
#include <iostream>
#include <stdlib.h>
using namespace std;
namespace A {
int x = 1;
void fun() {
cout << "A" << endl;
}
}
namespace B {
int x = 2;
void fun() {
cout << "B" << endl;
}
void fun2() {
cout << "this is fun2" << endl;
}
}
using namespace B;
int main() {
cout << "A的变量x=" << A::x << endl;
B::fun();
fun2();
}
- C++中数组作为参数传递
数组传给函数的是数组名,实际上是数组的首地址。
int fun(int a[]) {
return *(a + 1); // 2 也可以a[1]
}
int fun2(int *p) {
return p[1]; // 2 也可以p[1]
}
int main() {
int my_array[3] = { 1,2,3 };
cout << fun2(my_array) << endl;
return 0;
}
- 指针类型的引用
类型 *& 别名 = 指针; (从右往左读,首先类型是引用,然后加上*类型修饰符表示是对指针的引用)int a = 1; int *p = &a; int *&name = p;
- const限定符
除了书上的,讲一些特殊的情况。
(1)
int x = 0;
const int *p = &x;
*p = 1; // 错误,p被限定为指向常量,在p指针眼里,x是常量,所以不能用指针改变值。
(2)在一个文件中定义一个常量,然后让其他文件通用。需要在定义和声明的时候都加extern
关键字
在file1.cpp文件中定义extern const int buffersize = 100;
在file2.cpp文件中声明extern const int buffersize;
-
函数默认值
一般在声明的时候加上默认值,定义的时候不加。void fun(int i, int j = 5, int k =10); // 声明 ....... void fun(int i, int j, int k){ // 定义 ...... ..... }
-
函数重载
同一命名空间下的同名函数。
int getMax(int x, int y, int z){ // to do}
double getMax(double x, double y){ // to do}
在编译时会生成getMax_int_int_int和getMax_double_double这两个函数,根据传入的参数来调用这两个函数。 -
内联函数
内联函数在定义时加inline关键字。内联函数通常就是将它在程序中的每个调用点上“内联地”展开。调用普通的函数相对比较慢,会保存寄存器之类,调用内联函数则相当于直接把函数的代码在调用处写出来。
inline max(int a, int b); //声明
int main(){
int x = 1, y = 2, m;
m = max(x, y); //调用内联函数,与调用普通函数一样
}
/*
等价于
int main(){
int x = 1, y = 2, m;
int a, b;
a = x;
b = y;
m = a>b?a:b;
}
*/
//定义
inline max(int a, int b){
return a>b?a:b;
}
- 内存管理
(1)申请/释放内存
int *p = new int; // 申请一个字节内存, new int返回一个指针指向申请的首地址。
//也可以int *p = new int[10]; 直接为该地址初始化值10。
//申请成功了吗
if (p == NULL) {
//申请失败,异常处理
}
*p = 10; //赋值
delete p; // 释放
p = NULL; // 使指针为空,否则还会指向地址。
// 申请块内存
int *p = new int[max]; // 申请一块内存,十个字节。返回一个整形指针。
//申请成功了吗
if (p == NULL) {
//申请失败,异常处理
}
p[0] = 1; p[1] = 2;
delete []p; // 释放一块内存
p = NULL;
- 类和对象
(1)创建类和实例
访问限定符
public
private
protected
创建实例的两种方法:从栈创建,从堆(内存)创建
class TV{ // 类名后没有冒号:
public:
int number;
void fun(){pass}
}; // 分号不要忘了
//栈创建
TV tv;
tv.number = 0;
tv.fun();
//堆创建 就是申请内存
TV *p = new TV();
TV *q = new TV[20]; // 创建20个实例
p -> number = 0; // 调用成员的方式不同
p -> fun();
delete p;
p = NULL;
for(int i = 0; i < 20; i++){
q[i] -> number = 0;
q[i] -> fun();
}
delete []q;
q = NULL;
(2)数据封装
通过成员函数来实现功能,让外界尽量不能直接调用成员变量。
成员变量命名规范: m_strName m_iName m下划线类型名字(名字首字母大写)
成员函数中与成员变量对应的形参 :_name 下划线名字。
// 类的成员变量全是私有,外界不能直接访问。通过成员函数实现所有功能。
class student {
public:
void setname(string _name) {
m_strName = _name;
}
void getname(int _number) {
m_iNumber = _number;
}
private:
int m_iNumber;
string m_strSex;
string m_strName;
};
(3)类外定义
成员函数在类外定义(实现)。但函数要现在类内声明
a. 同文件类外定义
成员函数与类在同一个文件中,成员函数在类外定义。
假设在test.cpp文件中
class student {
public:
void setname(string _name); //先声明
string getname();
private:
int m_iNumber;
string m_strSex;
string m_strName;
};
void student::setname(string _name) { //类名加双冒号
m_strName = _name;
}
string student::getname() {
return m_strName;
}
b.分文件类外定义 (推荐)
把类的定义写在头文件中,类内只有成员函数的声明。文件名:类名.h
把成员函数的定义写在cpp文件中。文件名:类名.cpp
类的头文件
类的源文件
主函数文件
在主函数文件中只用包含类的头文件。不用包含类的源文件。
(3)对象结构
(4)构造函数
a. 构造函数在对象实例化时被自动调用;
b. 与类同名;
c. 没有返回值,所以连void都不用写;
d. 可以有多个重载形式;
e. 实例化对象时仅用到一个构造函数;
f. 当用户没有定义构造函数时,编译器会自动生成构造函数。
g.默认构造函数:函数没有参数或者每个参数都有默认值。
// student.h文件
#include <string>
using namespace std;
class student {
private:
int m_iAge;
string m_strName;
public:
student();
student(int _age, string _name);
int getage();
string getname();
};
// student.cpp文件
#include "student.h"
student::student() {
m_iAge = 20;
m_strName = "chen";
}
student::student(int _age, string _name) {
m_iAge = _age;
m_strName = _name;
}
int student::getage() {
return m_iAge;
}
string student::getname(){
return m_strName;
}
// 在主函数中
student s1;
student s2(120, "rui"); // 会分别调用不同的构造函数(构造函数重载)
(5)初始化列表下的构造函数(推荐)
a. 初始化列表先于构造函数执行
b. 初始化列表只能用于构造函数
c. 初始化列表可以同时初始化多个数据成员
d. 成员变量由const修饰时,要想修改成员变量的值,必须只能写在初始化列表中。
class TV{
private:
int m_iAge;
string m_strName;
const int m_iNumber = 5; // m_iNumber只能通过写在初始化列表中改变值。
public:
TV();
TV(int _age, string _name, int _number);
};
TV::TV():m_iAge(120), m_strName("chen"), m_iNumber(30) {}
// 在构造函数的括号和大括号之间加列表。
//把const常量的赋值写在构造函数内会失败。
// 成员的括号内是构造函数的形参,直接填字面量会直接给成员赋值。
TV::TV(int _age, string _name, int _number):m_iAge(_age), m_strName(_name), m_iNumber(_number) {}
(6)拷贝构造函数
是一种特殊的构造函数。如果没写,会自动生成。
类名(const 类名 & 变量名){ do something}
注意:拷贝构造函数第一个参数不一定要有const ,加const只是为了安全起见。
比如说 X::X( X &){}也是拷贝构造函数。
默认构造函数是把被拷贝对象的值一个一个复制给被初始化对象
// 默认拷贝构造函数
STUDENT::STUDENT(const STUDENT &C){
age = C.age;
name = C.name;
number = C.number;
}
拷贝构造函数的使用场景
class STUDENT{
private:
int m_iAge;
public:
STUDENT(int _age): m_iAge(_age) {}
STUDENT(const STUDENT &name){ // 引用
m_iAge = name.m_iAge; // 完成指定的复制操作
}
};
void fun1(STUDENT C){
cout << "test" << endl;
}
STUDENT fun2(){
STUDENT test(120);
return test;
}
// 使用到构造函数的场景
int main(){
// 场景一 实例通过另一个实例初始化
STUDENT s1(120); // 调用构造函数
STUDENT s2 = s1; // 调用s2的拷贝构造函数 实际上对s1产生了一次引用。
// 形参中传入的是s1对象。const STUDENT &name = s1 然后把s1的值复制给s2
// 场景二 实例(对象)本身直接传入函数形参
// fun1函数是全局函数
fun1(s1); // 此处是 STUDENT C = s1 所以也调用了拷贝构造函数
// 场景三 函数返回值是实例(对象)
fun2(); // 返回的是实例test; 实际上是创造了一个用test对象初始化的临时变量(此处调用拷贝构造函数)。
// 返回临时变量。
return 0;
}
浅拷贝与深拷贝
https://blog.youkuaiyun.com/lwbeyond/article/details/6202256
浅拷贝仅进行最简单的赋值,涉及到动态变量(比如分配一段内存,或者计数器)时,就不行了。
默认拷贝构造函数就是执行的浅拷贝,会把对象的值一个一个拷给另外一个对象。
class student{
public:
student(){
m_iAge = 120;
m_iPtr = new int(100); // 为指针创建一个字节的堆空间,值是100
}
~student(){
if(m_iPtr != NULL)
delete m_iPtr;
}
private:
int m_iAge;
int *m_iPtr;
};
int main(){
student s1;
student s2 = s1; // 此时会出错。因为类只有默认构造函数。所以把s1的指针完全拷贝给s2.
// 此时s2有一个指针,指向和s1完全一样。当析构函数销毁指针时,会对同一个堆地址delete两次,产生错误。
return 0;
}
深拷贝,对于动态成员会重新分配空间。下面通过自定义拷贝构造函数实现深拷贝
class student{
public:
student(){
m_iAge = 120;
m_iPtr = new int(100); // 为指针创建一个字节的堆空间,值是100
}
student(const student &C){
m_iAge = C.m_iAge;
m_iPtr = new int; // 重新分配一块空间
*m_iPtr = *(C.m_iPtr); // 存的值要一样
}
~student(){
if(m_iPtr != NULL)
delete m_iPtr;
}
private:
int m_iAge;
int *m_iPtr;
};
int main(){
student s1;
student s2 = s1; // 此时就不会出错了。s2和s1的指针值不同,但存的值相同。
return 0;
}
(7)析构函数
在对象销毁时会自动调用。比如用构造函数给指针分配了一段堆空间,就应该用析构函数销毁这段空间,析构函数确保了这段空间只有在对象没有利用价值时才被销毁。
a.没有返回值
b.不能重载
c.没有参数
格式:~类名()
class student{
private:
int age;
int *p;
public:
student(): age(120) {
p = new int[20];
}
~student(){ // 析构函数 释放内存
delete p;
p = NuLL;
}
};
int main(){
student *p = new student[10]; // 为指针分配十个堆空间 存放student类的实例
delete p; // 释放内存 此时会调用析构函数
p = NULL;
return 0;
}
(8)对象数组
// 从栈中实例化对象数组
// 栈中实例化的对象系统会自动回收内存
Student stu[3]; // 此时三个对象都会调用构造函数初始化
stu[1].name = "chen";
// 从堆中实例化对象数组
Student *p = new Student[3];
(p+1) -> name ="chen"; // 两种访问方式一样
p[1].name = "chen";
delete []p; // delete []p会销毁整个对象数组 delete p只会销毁数组中第一个对象
p = NULL;
(9)对象成员
对象成员是指类中的某个成员是一另一个类的对象。比如直角坐标系中线段和点的关系。点是一个类,数据成员是x,y坐标。线段是一个类,由于线段由两个点确定,所以数据成员是两个点的实例。
实例化含对象成员的类时,会先实例化对象成员,再实例化本类的对象,销毁时顺序相反,就和造车要先造轮子再造车壳,拆车要先拆车壳,再拆轮子一样。
class nod {
private:
int m_iX;
int m_iY;
public:
nod(int x, int y);
void setX(int x);
void setY(int y);
int getX();
int getY();
};
nod::nod(int x, int y): m_iX(x), m_iY(y){}
void nod::setX(int x) { m_iX = x; }
void nod::setY(int y) { m_iY = y; }
int nod::getX() { return m_iX; }
int nod::getY() { return m_iY; }
class line {
public:
line(int x1, int y1, int x2, int y2);
void setx1(int x, int y);
void setx2(int x, int y);
private:
nod m_nodX1; // 对象成员,是nod类的实例
nod m_nodX2;
};
// 构造函数中直接给对象成员初始化,会调用对象成员的构造函数
line::line(int x1, int y1, int x2, int y2):m_nodX1(x1, y1), m_nodX2(x2, y2){}
void line::setx1(int x, int y) {
m_nodX1.setX(x); // 调用对象成员的成员函数
m_nodX1.setY(y);
}
void line::setx2(int x, int y) {
m_nodX2.setX(x);
m_nodX2.setY(y);
}
(10)对象指针
// 从堆实例化指针
Student *stu1 = NULL;
stu1 = new Student(12,"chen");
// 或者
Student *stu1 = new Student(12, "chen"); //此时就可以初始化了
// 也可以用对象指针指向从栈实例化的对象
Student stu1(12, "chen");
Student *stu2 = &stu1;
stu2 -> m_iAge = 10; // 指向对象的指针可以直接操作对象的成员
stu2 -> name = "chen";
(11)对象成员指针
对象成员指针是指类的某个对象成员是另外一个类的对象指针。与对象成员有点像,只不过此时的对象变成了指针。它更加节约空间,一个Coordinate对象有两个整型成员变量,所以一个该类对象占8字节,若果做为Line的对象成员,假设Line只有两个该对象成员,则一个Line类的实例占16字节。
使用对象成员指针,由于一个指针只占4字节,所以两个对象成员指针占8字节,所以一个Line类的实例占8字节。
class Coordinate{
private:
int m_iX;
int m_iY;
public:
Coordinate(int x, int y){
m_iX = x;
m_iY = y;
}
};
class Line{
private:
Coordinate *m_coA; // 这就是对象成员指针,一个指针占4字节
Coordinate *m_coB;
public:
Line(int x1, int y1, int x2, int y2){
m_coA = new Coordinate(x1, y1);
m_coB = new Coordinate(x2, y2);
}
~Line(){
delete m_coA;
delete m_coB;
m_coA = NULL;
m_coB = NULL;
}
};
(12)this指针
this指针指向对象自身,在一个类中this指针表示类的实例化对象。
比如函数参数和成员同名时,可以用this指针区分。
函数返回引用
https://www.jianshu.com/p/4f0a892c2f89
class Student{
private:
int age;
public:
Student(int age){
this -> age = age; // this -> age是成员变量
}
void setage(int age){
this -> age = age;
}
Student& getinfo(){ // 返回的是引用,而且是对当前对象的引用
return *this; // *this就是当前对象 因为this是指向当前对象的指针
}
};
int main(){
Student stu(10);
stu.getinfo().setage(120); // stu.getinfo()得到的是一个对stu对象的引用
// 通过该引用调用setage()与stu调用setage()效果一样。
// 如果getinfo()返回的不是对象的引用,就没这个效果
return 0;
}
将getinfo函数该成返回指针一样的作用
Student* getinfo(){
return this;
}
stu.getinfo()->setage(120);
- c++中的输入
(1)cin 最原始。使用cin时的>>符号会过滤掉看不见的字符,比如空格,回车,TAB。
char a[20];
cin >> a;
输入 abcdr aeqe
输出 abcdr
当有多个变量时,会将空白符后的输入给第二个变量。
(2)getline()与cin.getline()
前者是全局函数,在<string>中。接受一串字符,包括空格等。
string str;
getline(cin, str);
输入abcdef ghi
输出abcdef ghi
后者是<istream>中的函数。第一个参数是数组名,第二个是接受字符个数。可以接受空白符。第三个参数是结束符,默认以’\0’结束。
char a[20];
cin.getline(a, 5);
输入 abcdefg
输出abcd(\0算一个字符,输出的时候实际上看不到)
- string类型
(1)初始化
#include <string>
using namespace std;
string s1; // 空字符串
string s2("abc"); string s2 = "abc"; // 字符串字面量
string s3(s2); string s3 = s2; // s3是s2的副本
string s4(n, 'c'); // s4 = n个c
(2)常用操作
注意:单纯的字符串不能用+连接。只有表达式含有string变量时才可以。
"hello" + "world";
是错误的用法。但 s1 + "world";
是正确的