C++杂记

变量声明和定义的关系

C++支持分离式编译机制,该机制允许将程序分割为若干个文件,每个文件可被独立编译。这就需要在文件间共享代码的方法。例如:一个文件的代码可能需要使用另一个文件中定义的变量。

声明:使得名字为程序所知,一个文件如果想使用别处定义的名字则必须包含对那个名字的声明;
定义:负责创建与名字关联的实体。

变量声明规定了变量的类型和名字,这一点和定义相同。但除此之外,定义还申请了存储空间。

如果想声明一个变量而非定义他,就在变量名前添加extern关键字

extern int i;  // 声明i而非定义i
int j;         // 声明并定义了j

变量只能被定义一次,但是可以被多次声明。

作用域操作符

#include <iostream>
int reused = 42;  // reused 具有全局作用域
int main()
{
	int reused = 0;  // 新建局部变量reused,覆盖了全局变量reused
	// 输出局部变量reused
	std::cout << reused << endl;
	// 显示访问全局变量reused
	std::cout << ::reused << endl;
}

引用

  • 对象的另一个名字
  • 引用必须被初始化
  • 为引用赋值,实际上是把值赋给了与引用绑定的对象。获取引用的值,实际上是获取了与引用绑定的对象的值。同理,以引用作为初始值,实际上是以与引用绑定的对象作为初始值。
  • 引用本身不是一个对象,所以不能定义引用的引用
  • 引用只能绑定在对象上,不能与某个字面值或表达式的计算结果绑定在一起
int &refVal = 10;  // 错误:引用类型的初始值必须是一个对象

double dval = 3.14;
int &refVal5 = dval;  // 错误:引用类型要和与之绑定的对象类型匹配

指针

和引用类似:指针也可以实现对其它对象间接访问

问:指针可以指向引用吗?
答:因为引用不是对象,没有实际地址,不能定义指向引用的指针。

指针类型和它所指向的对象类型要严格匹配:

double dval;
int *pi = &dval;  // 错误:试图把double型对象的地址赋给int型指针

1. 空指针

// nullptr C++11的字面值
int *p1 = nullptr;

int *p2 = 0;

// 预处理变量NULL在头文件cstdlib中定义,它的值就是0
int *p3 = NULL;

2. 指针相等

  1. 都为空
  2. 都指向同一个对象
  3. 都指向同一个对象下一地址

3. void* 指针

特殊的指针类型,用于存放任意对象的地址。一个void* 指针存放一个地址,这个和其它指针类型一样,不同的是:void* 指向的地址中是一个什么类型的对象不了解。

4. 复合类型的两种写法

int *p1, *p2;  // p1, p2 都是指向int的指针

int *p1, p2;  // p1是指向int的指针,p2是int

这种写法把修饰符和变量标识符写在一起,着重强调变量具有的复合类型。

int* p1;
int* p2;

这种形式着重强调本次声明定义了一种复合类型。

这两种写法没有孰对孰错之分,要统一就行

5. 指向指针的指针

int iVal = 1024;
int *pi = &iVal;  // pi指向一个int型
int **ppi = &pi;  // ppi指向一个int型的指针

6. 指向指针的引用

引用本身不是一个对象,因此指针不能指向引用。但是,指针是对象,所以存在对指针的引用

int i = 42;
int *p;        // p是一个int型指针
int *&r = p;   // r是一个对指针p的引用

r = &i;       // r引用了一个指针,因此给r赋值&i,就是令p指向i
*r = 0;       // 解引用r得到i,也就是p指向的对象,将i的值改为0

注意:要理解r的类型到底是什么,最简单的方法就是从右向左阅读r的定义。离变量名最近的符号(此例中&r的符号&)对变量的类型有最直接的影响,因此r是一个引用。声明符的其余部分用以确定r引用的类型是什么,此例中的符号*说明r引用的是一个指针。最后,声明的基本数据类型部分指出r引用的是一个int指针。

const限定符

1. 常量定义

用于定义常量。const对象必须初始化

const int j = 42;          // 编译时初始化,编译器会把所有的j替换成42
const int j = get_size();  // 运行时初始化

2. 多个文件共享const变量

默认状态下,const对象仅在文件内有效, 当多个文件中出现了同名的const变量时,其实等同于在不同文件中分别定义了独立的变量。

如果想要const变量在文件之间共享,可以这么做:

// file_1.cpp 定义并初始化常量,加了extern表示该常量能被其他文件访问
extern const int bufSize = fcn();
// file_1.h 头文件。声明bufSize,加上extern表名bufSize是在其它文件中定义的
extern const int bufSize;

注意:如果想在多个文件中共享const对象,必须在变量的定义之前添加extern关键字

3. 引用和const

与普通引用不同的是,对常量的引用不能被用作修改它所绑定的对象

const int ci = 1024;
const int &r1 = ci;    // 正确:引用及其对应的对象都是常量

r1 = 42;               // 错误:r1是对常量的引用,不能改变引用对象的值
int &r2 = ci;          // 错误:试图让一个非常量引用指向一个常量对象

4. 引用类型必须与其所引用对象的类型一致

但有两个例外,其中一个就是const

double dval = 3.14;
const int &ri = dval;    // 正确:const int &允许绑定到普通的double对象上
int &ri = dval;          // 错误:类型不一致,编译报错

可以这么做的原因,编译器背后做了如下工作:

const int temp = dval;  // 由double生成一个临时的整型常量
const int &ri = temp;   // 让ri绑定这个临时量

接下来探讨下如果ri不是常量,会有什么样的后果。如果ri不是常量,就允许对ri赋值,这样就会改变ri所引用对象的值。注意,此时绑定的对象是一个临时量而非dval。程序员既然让ri引用dval,就肯定想通过ri改变dval的值,否则干什么要给ri赋值呢?所以C++会把这种行为归为非法。

5. 常量引用可以引用一个非const对象

int i = 42;
int &r1 = i;       // 引用r1绑定对象i
const int &r2 = i; // 正确: 引用R2也绑定对象i,但不允许通过r2修改i的值
r1 = 0;            // 正确:r1非常量,i的值修改为0
r2 = 0;            // 错误:r2是一个常量引用

6. 指针和const

指向常量的指针不能用于改变其所指向对象的值。要想存放常量对象的地址,只能使用指向常量的指针。

const double pi = 3.14;   // pi常量
double *ptr = &pi;        // 错误:ptr是一个普通指针,不能指向常量
const double *cptr = &pt; // 正确:cptr是一个指向常量的指针,可以指向pi
*cptr = 42;               // 错误:不能给*cptr赋值

和引用类似,指针类型必须与其所指向对象的类型一致。但也有两个例外,第一个例外就是:允许令一个指向常量的指针指向一个非常量对象

double dval = 3.14;          // dval是一个double类型变量
const double *cptr = &dval;  // 正确:但不能通过cptr改变dval的值

和引用类似,指向常量的指针也没有规定其所指向的对象必须是常量

7. 常量指针

常量指针:指针本身是一个常量。必须初始化,一旦初始化完成,它的值不能再改变。
语法:把*放在const关键字之前说明指针是一个常量指针。这样书写隐含一层意味:不变的是指针本身的值而非指向的那个值

int errNumb = 0;
int *const curErr = &errNumb;  // curErr是常量指针,将一直指向errNumb

const double pi = 3.14;
const double *const pip = &pi; // pip是一个指向常量对象的常量指针

从右往左阅读法则,是理解这些声明最行之有效的方法。比如:离curErr最近的符号是const,意味着curErr本身是一个常量对象。声明符的下一个符号是*,意味着curErr是一个常量指针。最后,该声明语句的基本数据类型部分确定了常量指针指向的是一个int对象。与之相似,pip是一个常量指针,它指向的对象是一个double型常量。

string类型

1. 命名空间using声明

using namespace::name;

#include <string>
#include <iostream>

using std::cin; using std::cout; using std::endl;
using std::string;

2. string::size_type类型

string的size函数,返回的类型是 string::size_type,注意:它是一个无符号类型。所以表达式中不能混用带符号的数,否则产生意想不到的结果。

auto len = line.size();  // len的类型是string::size_type
int n = -10;
// 这个表达式可能恒为true,因为n会被转成无符号数,变成一个很大的整数。
line.size() < n;

如果表达式中已经有了size()函数就不要再使用int了,这样可以避免混用int和unsigned可能带来的问题。

3. 为string对象赋值

string str1(10, 'c'), str2;  // str1的内容是cccccccccc;str2是一个空字符串
str1 = str2;                 // 赋值:str2的副本替换str1的内容,此时str1和str2都是空字符串

3. 两个string相加

string s1 = "hello, ", s2 = "world\n";
string s3 = s1 + s2;  // s3的内容:hello, world\n
s1 += s2;             // 等价于s1 = s1 + s2

4. 字面值和string对象相加

注意:当把string对象和字符字面值及字符串字面值混在一条语句中使用时,必须确保每个加法运算符(+)的两侧的运算对象至少有一个是string

string s4 = s1 + ", ";             // 正确:把一个string对象和一个字面值相加
string s5 = "hello" + ", ";        // 错误:两个运算符对象都不是string
string s6 = s1 + ",  " + "world";  // 正确:s1 + ",  "结果是一个string对象
string s7 = "hello" + ", " + s2;   // 错误:"hello" + ", " 两侧都不是string

5. 范围for循环

统计给定字符串中,标点符号的个数:

int main(int argc, char const *argv[])
{
	string s("hello world!!!");
	int cnt = 0;
	for (char c : s) {
		if (ispunct(c)) {
			cnt++;
		}
	}
	cout << cnt << " punctuation characters in " << s << endl;
	
}

使用范围for循环改变字符串中的字符:引用

使用引用作为循环控制变量,这个变量实际上被依次绑定到了序列的每个元素上。

string s("Hello World!!!");
// 转换成大写形式
for (auto &c : s) {  // s中的每个字符(注意:c是引用)
	c = toupper(c);  // c是一个引用,因此赋值语句将改变s中字符的值
}
count << s << endl;

6. 下标运算符 [ ]

下标运算符接收的输入参数是string::size_type类型的值。

string对象的下标必须大于等于0而小于s.size()。使用超出此范围的下标将引发不可预知的结果,以此推断,使用下标访问空string也会引发不可预知的结果

下面代码是一种安全访问:

if (!s.empty()) {
	cout << s[0] << endl;
}

不管什么时候,只要对string使用下标,都要确认在那个位置上确实有值

例子:把一个字符串中,第一个单次改成大写

string s("some string");
for (decltype(s.size()) index = 0; index != s.size() && !isspace(s[index]); ++index) {
	s[index] = toupper(s[index]);
}

// 结果: SOME string

向量和迭代器

1. 把string对象中第一个单词改写成大写形式

string s = "hello world";
for (auto it = s.begin(); it != s.end() && !isspace(*it); ++it) {
	*it = toupper(*it);
}

2. 注意

  1. 不能在范围for循环中想vector对象中添加元素
  2. 但凡使用了迭代器的循环体,都不要向迭代器所属的容器中添加元素

数组

1. begin 和 end

C++11中引入了两个名为begin和end的函数

int ia[] = {0,1,2,3,4,5,6,7,8,9};
int *beg = begin(ia); // 指向ia首元素的指针
int *last = end(ia); // 指向arr尾元素的下一位置的指针

示例:找出arr中第一个负数:

// pbeg指向arr的首元素,pend指向arr尾元素的下一位置
int *pbeg = begin(arr), *pend = end(arr);
while(pbeg != bend && *pbeg >= 0) {
	++pbeg;
}

2. 使用数组初始化vector

int int_arr[] = {0, 1, 2, 3, 4, 5};
// ivec有6个元素,分别是int_arr中对应元素的副本
vector<int> ivec(begin(int_arr), end(int_arr));

构造函数

1. 拷贝构造函数

class Person {

public:
    Person() {
        this->name = NULL;
        this->work = NULL;
    }

    Person(char *name, int age, char *work = "none") {
        this->age = age;
        this->name = new char[strlen(name) + 1];
        strcpy(this->name, name);

        this->work = new char[strlen(work) + 1];
        strcpy(this->work, work);
    }

    ~Person() {
        cout << "~Person() ";
        if (this->name) {
            cout << "name="<<this->name<<endl;
            delete this->name;
        }
        if (this->work) {
            delete this->work;
        }
    }

    void print_info() {
        cout<<"name="<<this->name<<", age="<<age<<", work="<<work<<endl;
    }

private:
    char *name;
    char *work;
    int age;
};

void test_func() {
    Person per("zhangsan", 32);
    // 调用默认的拷贝构造函数,进行值拷贝
    Person per2(per);
    per2.print_info();
}

test_func()中 Person per2(per);会调用默认的拷贝构造函数构造per2,进行值拷贝。相当于:

    Person(Person &person) {
        cout << "Person(Person &person)" << endl;
        this->age = person.age;
        this->name = person.name;
        this->work = person.work;
    }

结果是per中的name和per2中的name指向同一块内存,work也是同理。这样导致per和per2销毁时,name和work会被delete两次,第二次delete会崩溃。

为了解决这个问题,需要重写拷贝构造函数:

    Person(Person &person) {
        cout << "Person(Person &person)" << endl;
        this->age = person.age;
        this->name = new char[strlen(person.name) + 1];
        strcpy(this->name, person.name);

        this->work = new char[strlen(person.work) + 1];
        strcpy(this->work, person.work);
    }

2. 构造函数执行顺序

阅读如下代码:

class Person {
public:
    Person() {
        this->name = NULL;
        this->work = NULL;
    }

    Person(char *name, int age, char *work = "none") {
        cout << "Person(...) " << "name=" << name << endl;
        this->age = age;
        this->name = new char[strlen(name) + 1];
        strcpy(this->name, name);

        this->work = new char[strlen(work) + 1];
        strcpy(this->work, work);
    }

    ~Person() {
        cout << "~Person() ";
        if (this->name) {
            cout << "name="<<this->name<<endl;
            delete this->name;
        }
        if (this->work) {
            delete this->work;
        }
    }

private:
    char *name;
    char *work;
    int age;
};

Person per_g("per_g", 10);

void test_func() {
    Person per_func("test_func", 32);
    static Person per_func_s("test_func_s", 32);
}

int main(int argc, char *argv[]) {
    Person per_main("per_main", 10);
    static Person per_main_s("per_main_s", 10);
    for (int i = 0; i < 2; i++) {
        test_func();
        Person per_for("per_for", 10);
    }
    return 0;
}

输出结果:

Person(...) name=per_g
Person(...) name=per_main
Person(...) name=per_main_s
Person(...) name=test_func
Person(...) name=test_func_s
~Person() name=test_func
Person(...) name=per_for
~Person() name=per_for
Person(...) name=test_func
~Person() name=test_func
Person(...) name=per_for
~Person() name=per_for
~Person() name=per_main
~Person() name=test_func_s
~Person() name=per_main_s
~Person() name=per_g

总结:

  1. 全局对象最先构造:per_g
  2. static对象只会构造一次
  3. for循环里面的对象,每次遍历都会构造,析构
  4. 程序执行结束会自动析构static对象和全局对象

3. 成员对象构造

class Person {

public:
    Person() {
        cout << "Person()" << endl;
        this->name = NULL;
        this->work = NULL;
    }

    Person(char *name, int age, char *work = "none") {
        this->age = age;
        this->name = new char[strlen(name) + 1];
        strcpy(this->name, name);

        this->work = new char[strlen(work) + 1];
        strcpy(this->work, work);
    }

    ~Person() {
        cout << "~Person()" << endl;
        if (this->name) {
            cout << "name="<<this->name<<endl;
            delete this->name;
        }
        if (this->work) {
            delete this->work;
        }
    }

    void print_info() {
        cout<<"name="<<this->name<<", age="<<age<<", work="<<work<<endl;
    }

private:
    char *name;
    char *work;
    int age;
};


class Student {
private:
    Person father;
    Person mother;
    int student_id;

public:
    Student() {
        cout << "Student()" << endl;
    }
};


int main(int argc, char *argv[]) {
	// 构造顺序?
    Student s;
    return 0;
}

Student类有两个Person成员变量,分别是father和mother,构造顺序如下:

Person()
Person()
Student()
~Person()
~Person()

结论:先构造成员变量,father和mother,再构造自己student。

4. 构造自己同时构造成员变量

class Person {

public:
    Person() {
        cout << "Person()" << endl;
        this->name = NULL;
        this->work = NULL;
    }

    Person(char *name, int age, char *work = "none") {
        cout << "(char *name, int age, char *work = \"none\")" << " name=" << name << endl;
        this->age = age;
        this->name = new char[strlen(name) + 1];
        strcpy(this->name, name);

        this->work = new char[strlen(work) + 1];
        strcpy(this->work, work);
    }

    ~Person() {
        cout << "~Person()" << endl;
        if (this->name) {
            cout << "name="<<this->name<<endl;
            delete this->name;
        }
        if (this->work) {
            delete this->work;
        }
    }

    void print_info() {
        cout<<"name="<<this->name<<", age="<<age<<", work="<<work<<endl;
    }

private:
    char *name;
    char *work;
    int age;
};


class Student {
private:
    Person father;
    Person mother;
    int student_id;

public:
    Student() {
        cout << "Student()" << endl;
    }

	// 构造自己同时,构造成员变量
    Student(int id, char *father_name, char * mother_name, int father_age, int mother_age) :
                    father(father_name, father_age),
                    mother(mother_name, mother_age) {
        cout << "Student(int id, char *father_name, char * mother_name, int father_age, int mother_age)" << endl;
    }

    ~Student() {
        cout << "~Student()" << endl;
    }
};


int main(int argc, char *argv[]) {
    Student s(111, "Bill", "Lily", 32, 30);
    return 0;
}

执行结果:

(char *name, int age, char *work = "none") name=Bill
(char *name, int age, char *work = "none") name=Lily
Student(int id, char *father_name, char * mother_name, int father_age, int mother_age)
~Student()
~Person()
name=Lily
~Person()
name=Bill

静态成员变量

需求:计算一个类有多少个对象?

可通过static成员来统计,代码如下:

class Person {
private:
    char *name;
    char *work;
    int age;
    // 统计一个类有多少个对象
    static int cnt;

public:

    static int get_count() {
        return cnt;
    }

    Person() {
        this->name = NULL;
        this->work = NULL;
        cnt++;
    }

    Person(char *name, int age, char *work = "none") {
        this->age = age;
        this->name = new char[strlen(name) + 1];
        strcpy(this->name, name);

        this->work = new char[strlen(work) + 1];
        strcpy(this->work, work);

        cnt++;
    }

    ~Person() {
        if (this->name) {
            cout << "name="<<this->name<<endl;
            delete this->name;
        }
        if (this->work) {
            delete this->work;
        }
        cnt--;
    }

    void print_info() {
        cout<<"name="<<this->name<<", age="<<age<<", work="<<work<<endl;
    }
};

// 这句很关键:Person::cnt定义和初始化
// 如果没有这句,Person::get_count()会报错,提示找不到Person::cnt
int Person::cnt = 0;

int main(int argc, char *argv[]) {
    Person per1;
    Person per2;
    Person per3;
    Person per4;
    Person per5;
    Person *per6 = new Person[10];

    cout << "person number = " << Person::get_count() << endl;
    return 0;
}

输出:

person number = 15

友元

下面程序,实现两个Point相加

class Point {
private:
    int x;
    int y;

public:

    Point() {}

    Point(int x, int y) : x(x), y(y) {

    }

    int get_x() { return x; }

    int get_y() { return y; }

    void set_x(int x) { this->x = x; }

    void set_y(int y) {this->y = y; }

    void print_info() {
        cout << "(" << x << ", " << y << ")" << endl;
    }
};

// 实现两个point相加
Point add(Point &p1, Point &p2) {
    Point ret = Point();
    ret.set_x(p1.get_x() + p2.get_x());
    ret.set_y(p1.get_y() + p2.get_y());

    return ret;
}

int main(int argc, char *argv[]) {
    Point p1(1, 2);
    Point p2(2, 3);

    Point p3 = add(p1, p2);
    p3.print_info();

    return 0;
}

看到add函数实现两个point相加,调用了Point里面很多函数,相对繁琐。

如果将add函数声明成Point类的友元,add函数就可以直接访问Point的私有成员;代码如下:

class Point {
private:
    int x;
    int y;

public:

    Point() {}

    Point(int x, int y) : x(x), y(y) {

    }

    int get_x() { return x; }

    int get_y() { return y; }

    void set_x(int x) { this->x = x; }

    void set_y(int y) {this->y = y; }

    void print_info() {
        cout << "(" << x << ", " << y << ")" << endl;
    }

    friend Point add(Point &p1, Point &p2);
};

// add函数是Point的友元,可以直接访问Point的私有成员。简化代码调用。
Point add(Point &p1, Point &p2) {
    Point ret = Point();
    ret.x = p1.x + p2.x;
    ret.y = p1.y + p2.y;

    return ret;
}

int main(int argc, char *argv[]) {
    Point p1(1, 2);
    Point p2(2, 3);

    Point p3 = add(p1, p2);
    p3.print_info();

    return 0;
}
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值